From: Akinori MUSHA Date: 2007-09-10T15:10:13+09:00 Subject: [ruby-dev:31768] shellescape --pgp-sign-Multipart_Mon_Sep_10_15:10:13_2007-1 Content-Type: text/plain; charset=ISO-2022-JP  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 # 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 --pgp-sign-Multipart_Mon_Sep_10_15:10:13_2007-1 Content-Type: application/pgp-signature Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (FreeBSD) iD8DBQBG5N/Fkgvvx5/Z4e4RAvYPAJ0cA/3n6a6PR1pZk54//NX6p3Ep9ACg2DQp wBVWXaQKi7btF4YfU4lJJDw= =yGvc -----END PGP SIGNATURE----- --pgp-sign-Multipart_Mon_Sep_10_15:10:13_2007-1--