Shellescape

 shellwords.rb に shellescape() ç­‰ã‚’è¿½åŠ ã—ã‚ˆã†ã¨æ€ã„ã¾ã™ã€‚

 理由としては、 system(), open(“|…”), IO.popen() 等でコマンド
ラインを記述する際、メタ文字等をエスケープする適当な関数が標準で
提供されていないためです。

 仕様としては、Bourneシェルに渡すことを想定し、安全でない文字を
バックスラッシュ()でエスケープします。文字列はシングルバイト列
として扱います。理由は以下の通りです。

  • shellwords はそもそもBourneシェル(POSIX)の文法に則った処理を行う
    関数を提供するライブラリである。

  • (Plan9の) rc 等を含め多くのシェルで使えるのが望ましいという声も
    [ruby-list:30049] 等で出た。しかし、Bourneシェルと rc では引用符
    (')のエスケープの仕方に互換性がなく、 Windows の cmd.exe も変数
    展開や引用符の扱いの差異が大きく、ひとつで全対応するエスケープ
    方法がない。

    ただし、 (t)csh は改行(LF)のエスケープが効かない(そもそもできる
    のか不明)点を除けば互換性がある。

  • 引用符(‘)で括る方式だと、感嘆符(!)の扱いの差異のため (t)csh で
    使えなくなってしまう。また、マルチバイト文字の中に引用符(’)が
    ã‚ã£ãŸå ´åˆã€ãƒžãƒ«ãƒãƒã‚¤ãƒˆå¯¾å¿œã®ã‚·ã‚§ãƒ«ã§ ‘…’'‘…’ が誤解釈
    される可能性が高い。

  • 二重引用符(")で括る方式だと、バックスラッシュ()の扱いの差異の
    ため (t)csh で使えなくなってしまう。

 パッチを添付しました。以下がサマリーです。

  • Shellwords.shellescape ã‚’è¿½åŠ :
    Bourneシェルに解釈される可能性のある文字をエスケープして返す
    (仕様は実装およびコメントを参照のこと)

  • Shellwords.shelljoin ã‚’è¿½åŠ :
    与えられた引数列から、Bourneシェルコマンドライン文字列を生成
    して返す (shellescape を利用)

  • Shellwords.shellwords の別名 shellsplit を定義

  • 利用の便のため String ã‚„ Array にインスタンスメソッドを追åŠ

 cmd.exe ã‚„ rc ã®å¯¾å¿œã«ã¤ã„ã¦ã¯ã€å°†æ¥çš„ã«ã‚ªãƒ—ã‚·ãƒ§ãƒ³å¼•æ•°ã‚’è¿½åŠ ã—ã€
プラットフォームごとに妥当なデフォルト値を設定することで対応でき
ればと思います。


/
/__ __ Akinori.org / MUSHA.org
/ ) ) ) ) / FreeBSD.org / Ruby-lang.org
Akinori MUSHA aka / (_ / ( (__( @ iDaemons.org / and.or.jp

“Different eyes see different things,
Different hearts beat on different strings –
But there are times for you and me when all such things agree”

Index: shellwords.rb

— shellwords.rb (revision 13420)
+++ shellwords.rb (working copy)
@@ -1,29 +1,31 @@

-# shellwords.rb: Split text into an array of tokens a la UNIX shell
+# shellwords.rb: Manipulates strings a la UNIX Bourne shell

-# This module is originally a port of shellwords.pl, but modified to
-# conform to POSIX / SUSv3 (IEEE Std 1003.1-2001).
+# This module manipulates strings according to the word parsing rules
+# of the UNIX Bourne shell.

-# Examples:
+# The shellwords() function was originally a port of shellwords.pl,
+# but modified to conform to POSIX / SUSv3 (IEEE Std 1003.1-2001).

-# require ‘shellwords’
-# words = Shellwords.shellwords(line)
-#
-# or
-#
-# require ‘shellwords’
-# include Shellwords
-# words = shellwords(line)
+# Authors:
+# - Wakou Aoyama
+# - Akinori MUSHA [email protected]

module Shellwords

  • Split text into an array of tokens in the same way the UNIX Bourne

  • shell does.

  • Splits a string into an array of tokens in the same way the UNIX

  • Bourne shell does.

  • argv = Shellwords.shellwords(‘here are “two words”’)

  • argv #=> [“here”, “are”, “two words”]

  • See the +Shellwords+ module documentation for an example.

  • +String#shellwords+ is a shorthand for this function.

  • argv = ‘here are “two words”’.shellwords

  • argv #=> [“here”, “are”, “two words”]

    def shellwords(line)
    words = []
    @@ -39,6 +41,98 @@
    end
    words
    end
  • alias shellsplit shellwords
  • Escapes a string so that it can be safely used in a Bourne shell

  • command line.

  • Note that a resulted string should be used unquoted and is not

  • intended for use in double quotes nor in single quotes.

  • open(“| grep #{Shellwords.shellescape(pattern)} file”) { |pipe|

  • # …

  • }

  • +String#shellescape+ is a shorthand for this function.

  • open(“| grep #{pattern.shellescape} file”) { |pipe|

  • # …

  • }

  • def shellescape(str)
  • An empty argument will be skipped, so return empty quotes.

  • return “‘’” if str.empty?
  • str = str.dup
  • Process as a single byte sequence because not all shell

  • implementations are multibyte aware.

  • str.gsub!(/([^A-Za-z0-9_-.,:/@\n])/n, “\\\1”)
  • A LF cannot be escaped with a backslash because a backslash + LF

  • combo is regarded as line continuation and simply ignored.

  • str.gsub!(/\n/, “‘\n’”)
  • return str
  • end
  • Builds a command line string from an argument list +array+ joining

  • all elements escaped for Bourne shell and separated by a space.

  • open(‘|’ + Shellwords.shelljoin([‘grep’, pattern, *files])) {

|pipe|

  • # …

  • }

  • +Array#shelljoin+ is a shorthand for this function.

  • open(‘|’ + [‘grep’, pattern, *files].shelljoin) { |pipe|

  • # …

  • }

  • def shelljoin(array)
  • array.map { |arg| shellescape(arg) }.join(’ ')
  • end
  • module_function :shellwords, :shellsplit, :shellescape, :shelljoin
    +end

+class String

  • call-seq:

  • str.shellwords => array

  • Splits +str+ into an array of tokens in the same way the UNIX

  • Bourne shell does. See +Shellwords::shellwords+ for details.

  • def shellwords
  • Shellwords.shellwords(self)
  • end
  • alias shellsplit shellwords
  • module_function :shellwords
  • call-seq:

  • str.shellescape => string

  • Escapes +str+ so that it can be safely used in a Bourne shell

  • command line. See +Shellwords::shellescape+ for details.

  • def shellescape
  • Shellwords.shellescape(self)
  • end
    +end

+class Array

  • call-seq:

  • array.shelljoin => string

  • Builds a command line string from an argument list +array+ joining

  • all elements escaped for Bourne shell and separated by a space.

  • See +Shellwords::shelljoin+ for details.

  • def shelljoin
  • Shellwords.shelljoin(self)
  • end
    end

e$B$J$+$@$G$9!#e(B

At Mon, 10 Sep 2007 15:10:13 +0900,
Akinori MUSHA wrote in [ruby-dev:31768]:

  • Shellwords.shellescape e$B$rDI2Ce(B:
    Bournee$B%7%’%k$K2r<a$5$l$k2DG=@-$N$"$kJ8;z$r%(%9%1!<%W$7$FJV$9e(B
    (e$B;EMM$O<BAu$*$h$S%3%a%s%H$r;2>H$N$3$He(B)

  • Shellwords.shelljoin e$B$rDI2Ce(B:
    e$BM?$($i$l$?0z?tNs$+$i!"e(BBournee$B%7%’%k%3%^%s%I%i%$%sJ8;zNs$r@[email protected](B
    e$B$7$FJV$9e(B (shellescape e$B$rMxMQe(B)

  • Shellwords.shellwords e$B$NJLL>e(B shellsplit e$B$rDj5Ae(B

e$B%b%8%e!<%k4X?t$K4X$9$k8B$j!"e(Bshelle$B$H$$$&e(Bprefixe$B$O$J$/$F$b$$$$$s$8$ce(B
e$B$J$$$+$H;W$&$N$G$9$,!"e(BShellwords.escapee$B$de(BShellwords.join,
Shellwords.splite$B$J$I$G$O$^$:$$$G$7$g$&$+!#e(B

At Mon, 10 Sep 2007 16:01:47 +0900,
Nobuyoshi N. wrote:

  • Shellwords.shellwords の別名 shellsplit を定義

モジュール関数に関する限り、shellというprefixはなくてもいいんじゃ
ないかと思うのですが、Shellwords.escapeやShellwords.join,
Shellwords.splitなどではまずいでしょうか。

 私もそう思いました。しかし、現行のサンプルで include Shellwords
する用例が載っているため、それを踏襲したコードが名前空間の汚染で
動かなくなる可能性があります。(特に split)

 included() を定義して長い名前の方だけ落とすという手段はあるかも
しれませんが…。


/
/__ __ Akinori.org / MUSHA.org
/ ) ) ) ) / FreeBSD.org / Ruby-lang.org
Akinori MUSHA aka / (_ / ( (__( @ iDaemons.org / and.or.jp

“Different eyes see different things,
Different hearts beat on different strings –
But there are times for you and me when all such things agree”

At Mon, 10 Sep 2007 17:29:08 +0900,
Nobuyoshi N. wrote:

 included() を定義して長い名前の方だけ落とすという手段はあるかも
しれませんが…。

インスタンスメソッドは長いものだけでいいと思います。特異メソッド
の方だけaliasすればいいのでは。

 そうか。include で降りてくるのはインスタンスメソッドの方だ。
採用します。

さすがにwordsは単独では意味不明なのでいらないと思います。

 メソッド名としての shellwords() はいまいちですよね。
shellsplit を本名にして shellwords ã¯åˆ¥åã«æ ¼ä¸‹ã’ã™ã‚‹ã‹ã€‚


/
/__ __ Akinori.org / MUSHA.org
/ ) ) ) ) / FreeBSD.org / Ruby-lang.org
Akinori MUSHA aka / (_ / ( (__( @ iDaemons.org / and.or.jp

“Different eyes see different things,
Different hearts beat on different strings –
But there are times for you and me when all such things agree”

e$B$J$+$@$G$9!#e(B

At Mon, 10 Sep 2007 16:12:58 +0900,
Akinori MUSHA wrote in [ruby-dev:31770]:

e$B%b%8%e!<%k4X?t$K4X$9$k8B$j!"e(Bshelle$B$H$$$&e(Bprefixe$B$O$J$/$F$b$$$$$s$8$ce(B
e$B$J$$$+$H;W$&$N$G$9$,!"e(BShellwords.escapee$B$de(BShellwords.join,
Shellwords.splite$B$J$I$G$O$^$:$$$G$7$g$&$+!#e(B

e$B!!;d$b$=$&;W$$$^$7$?!#$7$+$7!“8=9T$N%5%s%W%k$Ge(B include Shellwords
e$B$9$kMQNc$,:$C$F$$$k$?$a!”$=$l$rF’=1$7$?%3!<%I$,L>A06u4V$N1x@w$Ge(B
e$BF0$+$J$/$J$k2DG=@-$,$"$j$^$9!#e(B(e$BFC$Ke(B split)

e$B!!e(Bincluded() e$B$rDj5A$7$FD9$$L>A0$NJ}$@$1Mn$H$9$H$$$&<jCJ$O$"$k$+$be(B
e$B$7$l$^$;$s$,!D!#e(B

e$B%$%s%9%?%s%9%a%=%C%I$OD9$$$b$N$@$1$G$$$$$H;W$$$^$9!#FC0[%a%=%C%Ie(B
e$B$NJ}$@$1e(Baliase$B$9$l$P$$$$$N$G$O!#e(B

module Shellwords
module_function :shellwords, :shellsplit, :shellescape, :shelljoin

class << self
  alias split shellsplit
  alias escape shellescape
  alias join shelljoin
end

end

e$B$5$9$,$Ke(Bwordse$B$OC1FH$G$O0UL#ITL@$J$N$G$$$i$J$$$H;W$$$^$9!#e(B