他のスクリプトの内容として使用するために変数をエスケープする

他のスクリプトの内容として使用するために変数をエスケープする

質問は〜です。いいえ正しくエスケープされた文字列リテラルを作成する方法について説明します。スクリプトや他のプログラムで直接使用するために変数をエスケープする方法に関連しない質問はありません。

私の目標は、あるスクリプトが別のスクリプトを生成できるようにすることです。これは、生成されたスクリプトの作業範囲が0からN別のコンピュータで複数回実行すると、それを生成するデータが再実行される前に変更される可能性があるため、ネットワーク経由で直接実行することは機能しません。

特殊文字(一重引用符など)を含むことができる既知の変数がある場合は、完全にエスケープされた文字列リテラルとして作成する必要があります。たとえば、foo含まれる変数は、bar'baz生成されたスクリプトに次のように表示されます。

qux='bar'\''baz'

これは"qux=$foo_esc"スクリプトに追加の行を追加することによって作成されます。私はPerlを使って次のようにしました。

foo_esc="'`perl -pe 's/('\'')/\\1\\\\\\1\\1/g' <<<"$foo"`'"

しかし、これはあまりにも過度のようです。

私は成功せずにbashを単独で使用しました。私は次のようなさまざまなバリエーションを試しました。

foo_esc="'${file//\'/\'\\\'\'}'"
foo_esc="'${file//\'/'\\''}'"

ただし、追加のスラッシュが出力に表示されるか(これを実行するとecho "$foo")、構文エラーが発生します(シェルで実行した場合は追加の入力が期待されます)。

答え1

Bashには、このような状況のパラメータ拡張オプションがあります。:

${parameter@Q}拡張子は、値が次のような文字列です。範囲入力として再利用できる形式で引用してください。

したがって、この場合:

foo_esc="${foo@Q}"

Bash 4.4以降はこの機能をサポートしています。他の形式の拡張、特に完全な割り当てステートメントを生成するためのさまざまなオプションがあります()@A

答え2

Bashは、Bashの以前のバージョン(<4.0)でもシェルエスケープを実行できる書式指定子printfとともに組み込み関数を提供します。%q

printf '[%q]\n' "Ne'er do well"
# Prints [Ne\'er\ do\ well]

printf '[%q]\n' 'Sneaky injection $( whoami ) `ls /root`'
# Prints [Sneaky\ injection\ \$\(\ whoami\ \)\ \`ls\ /root\`]

このトリックは、関数からデータ配列を返すためにも使用できます。

function getData()
{
  printf '%q ' "He'll say hi" 'or `whoami`' 'and then $( byebye )'
}

declare -a DATA="( $( getData ) )"
printf 'DATA: [%q]\n' "${DATA[@]}"
# Prints:
# DATA: [He\'ll\ say\ hi]
# DATA: [or\ \`whoami\`]
# DATA: [and\ then\ \$\(\ byebye\ \)]

Bash 組み込みコマンドは、printfほとんどの Unix 系オペレーティングシステムにバンドルされているユーティリティとは異なります。printf何らかの理由でprintfコマンドが組み込みコマンドの代わりにユーティリティを呼び出す場合は、いつでも実行できますbuiltin printf

答え3

要約:結論にスキップしてください。

いくつかのシェル/ツールには引用符演算子が組み込まれていますが、そのいくつかはいくつかの答えで言及されていますが、ここでは強調したいと思います。多くのものが使用するのに安全ではありませんによると:

  • 引用された内容
  • 引用符付き文字列が使用されるコンテキスト。
  • 引用された出力を生成するロケール
  • 結果として引用された出力は、後でそのロケールに使用されます。

考慮すべきいくつかの点:

  • 場合によっては、''空の文字列をまたはで表すことが重要です""。たとえば、で使用したい場合は、sh -c "cmd $quoted_output"参照内容をに引数として渡しても構いませんcmdsh -c "var=$quoted_output; ..."''""

    $var:qの演算子は空の文字列を、ではzshなく空の文字列として表します。''""$''

    演算子(自己コピー、これとは異なる${var@Q}動作)は、空の文字列をで表しますが空の文字列に設定されていないことを示します。bashmksh$var''$var

    $ empty_var= bash -c 'printf "<%s>\n" "${empty_var@Q}" "${unset_var@Q}"'
    <''>
    <>
    $ empty_var= mksh -c 'printf "<%s>\n" "${empty_var@Q}" "${unset_var@Q}"'
    <''>
    <''>
    $ empty_var= zsh -c 'printf "<%s>\n" "${empty_var:q}" "${unset_var:q}"'
    <>
    <>
    
  • '...'\これらの参照演算子の一部は、"..."またはの組み合わせを使用します$'...'。後者の構文は、シェルとそのシェルのバージョンによって異なります。したがって、それを使用するか、入力に応じて使用できる演算子の場合は、同じシェル(および同じバージョン)で結果を使用することが重要です。これは少なくとも次に適用されます。

    • printf %qGNU printfbashksh93zsh
    • zshの ,,,,$var:q${(q)var}${(q+)var}${(qqqq)var}
    • mksh~の${var@Q}
    • bash${var@Q},
    • typeset// declare、、、、の出力export -p(以前のバージョンのスカラー変数には適用されません)ksh93mkshzshbash
    • alias/set出力bash、、、、ksh93mkshzsh
    • xtrace出力ksh93、、、mkshzsh

    それにもかかわらず、これは$'...'(まだ)標準sh参照演算子であり、Bourneに似たシェルではなく、、、rcはすでに完全に含まれています。esakangafishその他の引用構文。存在するすべてのシェルと互換性のある方法で文字列を引用する方法はありません。もう一つのQ&Aですいくつかの回避策)。

  • 一部のシェルは内部コードを解釈する前に入力を文字としてデコードします。

    一部のシェル(例bash:)は、その構文をロケールに従って条件付きにします。たとえば、構文のトークン区切り文字は、yashロケールでスペースとして扱われる文字ですbash(ただしbash、では単一バイト文字にのみ適用されます)。一部のシェルは、ロケールの文字分類にも依存して、変数名に有効な文字を決定します。たとえば、Stéphane=1あるロケールでは割り当てとして解釈でき、Stéphane=1別のロケールではコマンド呼び出しとして解釈できます。

    バイトシーケンス0xa3 0x5cは、£\ISO-8859-1(latin1とも呼ばれる)文字セットの文字列、αBIG5の文字、またはUTF-8の無効なバイトシーケンスを表します。 inside や を\含むシェル構文の特殊文字です。また、一部のロケールの他の文字エンコーディングでエンコーディングを見つけることができる(危険な)文字です。"..."$'...'`

    byteは、0xa0次のように処理される大型シングルバイト文字セットの改行なしの空白文字です。スペースbashまたは構文yashのトークン区切り文字など、一部のシステムの一部のロケールで。

    このバイトは、多くのアルファベット文字を含む何千もの文字をUTF-8でエンコードする場合にも見つかります(たとえば、à0xc3 0xa0でエンコードされています)。

    このエンコーディングを含む文字のエンコードを持つASCIIベースのシステムのすべてのロケールで使用される文字セットを認識しません'

    たとえば、一部のシェル引用演算子の出力$'\u00e9'または$'\u[e9]'文字です。é順番に、使用する場合は、シェルとロケールによって、それを使用するコードが解釈または実行されるときにUTF-8エンコードまたはロケールエンコードに拡張されます(ロケールが異なる場合は動作が異なります)。このキャラクター)。

    したがって、生成された文字列が同じシェルバージョンとシェルバージョンだけでなく同じロケールでも使用されることが重要です(少なくとも一部の文字エンコード/デコードを実行するシェルの場合)。それにもかかわらず、一部のシェル(含まれているbash)には、これに関してまだバグがあったり、存在していました。

    $'...'"..."引用のために、バックスラッシュを使用するか、ASCII以外の特定の文字を引用しないままにする引用演算子は安全ではない可能性があります。

    つまり、'...'この点ではそれを使う人だけが安全です。維持する:

    • zsh${(qq)var}オペレーター
    • / alias(少なくとも現在のバージョン)。dashbashbosh
    • export -p/ dashbosh少なくとも現在のバージョン)。
    • (少なくとも現在のバージョン)setdash

    これらの最初の項目のみが文書化されており、常に一重引用符を使用することをお約束します(下記の警告に注意してくださいrcquotes)。

    また、yashロケールの文字セットでデコードできないデータは処理できないため、任意のデータをこのシェルに渡すことはできません(少なくとも現在のバージョンでは)。

    皮肉なことに、ユーティリティの出力に問題があります(出力にlocale使用する必要があるため)。"..."暗黙的にlocale設定)は、呼び出された場所とは異なるロケールにコードを入力するためによく使用されます(ロケールを復元するため)。

  • NUL文字(0バイト)は、環境変数またはexecve()システム呼び出しを介して実行されるコマンドの引数には現れません。これは、これらのenv文字列と引数文字列をCスタイルのNUL区切り文字列として扱うシステムコールの制限です。 )))。内部を除いて、zshNULはシェル変数、組み込みパラメータ、またはより一般的なシェルコードにはありません。

    ただし、0バイトは可能です。読むそして苦いファイルやパイプ、またはすべてのI / Oメカニズムとやり取りできます。

    現代のプログラミング言語(例:または)と同様に、変数に保存、読み書きすることができ、組み込みzsh関数に引数として渡すことができます。pythonperl

    ただし、NULをそのままにする方法(たとえば$'\0'、、$'\x0'$'\u0000'除く$'\C@')を使用してNULを参照する場合、参照方法に関係なく結果をパラメータまたは環境変数に渡すことはできません。処刑されたコマンドであり、NUL文字は他のシェルでは使用できません。

    zsh(as in)で外部入力を許可する場合は、この点に留意してくださいIFS= read -r var。 stdinから読み取られた行にNULバイトが含まれていると、それらを含む可能な操作が制限される可能性があります$var${(qq)var}

    この場合、$'...'参照フォームを使用する方が良い場合があります(該当する参照フォーム(上記参照)に関する他の考慮事項を解決できる場合)。

  • 生成された引用テキストがバックティック内のシェルコードで使用される場合は、バックスラッシュ解釈の追加の階層があることに注意してください。常に$(...)代わりに使用してください`...`

  • 一部の文字は特定の状況でのみ特別です。たとえば、=コマンド名の前の単語は特別ですが(例a=1 cmd arg:)、コマンド名の後の単語は特別ではありません(例:)。しかし、一部のシェルにはcmd a=1...などのコマンドにいくつかの特別なケースがあります。exportreadonly

    ~場合によっては特別ですが、他の場合ではそうではありません。

    すべての参照演算子がこれを参照するわけではありません。

    一部の文字は一部のシェルでは特別ですが、他のシェルでは特別ではない場合、または特定のオプションが有効になっている場合にのみ...

    偶数は場合によっては特別です。たとえば、引用符がない場合、引用符付きsh -c "echo ${quoted_text}>file"テキストは出力されません。file2'2'

  • zshこのオプションはrcquotes、単一引用符で囲まれた文字列の解釈方法(および引用符演算子によって生成される方法)に影響します。有効にすると、一重引用符が''シェルに一重引用符文字列として表示されることがありますrc。たとえば、で書くことも"foo'bar"できます'foo''bar'

    rcquotesしたがって、アクティブになったときに生成された引用符付き文字列は、zshアクティブなインスタンスでのみ解釈できることが重要ですrcquotes

    ${(qq)var}zsh の有無にかかわらずビルドはrcquotesで使用するのが安全でなければなりません。ただし、一重引用符で囲まれた文字列を連結すると、それらの間に一重引用符が挿入されることzsh -o rcquotesに注意してください。zsh -o rcquotes

    $ quoted_text="'*'"
    $ zsh -o rcquotes -c "echo $quoted_text$quoted_text"
    *'*
    

    それは次のとおりです。

    $ rc -c "echo $quoted_text$quoted_text"
    *'*
    

    ""次の 2 つの間に挿入することでこの問題を解決できます。

    $ zsh -o rcquotes -c "echo $quoted_text\"\"$quoted_text"
    **
    

    inrcと派生語("..."参照演算子ではなく、唯一の引用タイプであるため挿入'...'できるはずです)では、次のものを使用できます。'^

    $ rc -c "echo $quoted_text^$quoted_text"
    **
    

要約すると

引用する唯一の安全な方法は(Bourneのようなシェルに制限され、悪意のあるロケールを無視するか、yashデータ`...`にNUL文字が含まれていないと仮定する場合)、すべてを一重引用符で囲むことです(空の文字列も含む)。あなたは文字が欲しい)それは決して問題ではないと想像し、質問の元の意図のように一重引用符文字自体を一重引用符または一重引用符の\'外側に示します。"'"

これを行うには、次のものを使用できます。

  • zsh演算子${(qq)var}(または"${(qq@)array}"配列の場合)、このrcquotesオプションが有効になっていないと仮定します。

  • 次の関数:

    shquote() {
      LC_ALL=C awk -v q="'" '
        BEGIN{
          for (i=1; i<ARGC; i++) {
            gsub(q, q "\\" q q, ARGV[i])
            printf "%s ", q ARGV[i] q
          }
          print ""
        }' "$@"
    }
    

    または

    shquote() {
      perl -le "print join ' ', map {q(') . s/'/'\\\\''/gr . q(')} @ARGV" -- "$@"
    }
    
  • ksh93/// zsh:bashmksh

    quoted_text=\'${1//\'/\'\\\'\'}\'
    

    (拡張子を二重引用符で囲むのではなく、スカラー変数の割り当て以外で使用しないでください。そうしないと、バージョン間の互換性の問題が発生しますbash(オプションの説明を参照compat41)。


^POSIX仕様$'...'もともと目標は単一のUNIX仕様8号であり、早ければ2021年に発売されると予想されますが、そうではないようです(時々ソリューションに対する合意は行われませんでした)。したがって、$'...'標準に追加されるまで少なくとも10年は待つ必要があります。

² Bourneシェルと一部の派生製品の-k()オプションが有効になっていない場合keyword

答え4

var値を参照する方法はいくつかあります。

  1. エイリアスエイリアス
    を使用できるほとんどのシェル(csh、tcsh、および他のcsh様シェルを除く):

    $ alias qux=bar\'baz
    $ alias qux
    qux='bar'\''baz'
    

    はい、これはshダッシュや灰などの多くの同様のシェルで機能します。

  2. setは
    ほとんどのシェルでも動作します(cshではありません)。

    $ qux=bar\'baz
    $ set | grep '^qux='
    qux='bar'\''baz'
    
  3. 一部のシェル
    (少なくともksh、bash、zsh)では、次のことを行います。

    $ qux=bar\'baz
    $ typeset -p qux
    typeset qux='bar'\''baz'             # this is zsh, quoting style may
                                         # be different for other shells.
    
  4. エクスポート
    まず実行する作業:

    export qux=bar\'baz
    

    次に、次を使用します。
    export -p | grep 'qux=' export -p | grep 'qux='
    export -p qux

  5. 引用する
    echo "${qux@Q}"
    echo "${(qq)qux}" #1〜4個のqを使用できます。

関連情報