args から読み取ったコマンドを使用して sh (ダッシュ) から bash を呼び出すと、「引用符で囲まれた文字列が終了していません」/「予期しない EOF」が発生します

args から読み取ったコマンドを使用して sh (ダッシュ) から bash を呼び出すと、「引用符で囲まれた文字列が終了していません」/「予期しない EOF」が発生します

これを文書化しようと思ったのですが、非常に単純なことを試しています。環境変数を設定しbash、それを出力します。

$ bash -c "a=1; echo a$a;"
a
$ bash -c "a=1; echo a\$a;"
a1

今、私はこれと同じことをしたいのですが、 の引数として呼び出しますsh(私のシステムでは にls -la $(which sh)なります/bin/sh -> dash):

$ sh -c "bash -c "a=1; echo a\$a;""
a$a

# obviously I have to escape inner quotes:

$ sh -c "bash -c \"a=1; echo a\$a;\""
a

# escape the dollar once more?

$ sh -c "bash -c \"a=1; echo a\\$a\" "
sh: Syntax error: Unterminated quoted string

# nope... inner single quotes, then?

$ sh -c "bash -c 'a=1; echo a$a;'"
a

# nope... escape the single quotes?

$ sh -c "bash -c \'a=1; echo a$a;\'"
bash: -c: line 0: unexpected EOF while looking for matching `''
bash: -c: line 1: syntax error: unexpected end of file
a
sh: ': not found

# nope... escape the dollar too?

$ sh -c "bash -c \'a=1; echo a\$a;\'"
bash: -c: line 0: unexpected EOF while looking for matching `''
bash: -c: line 1: syntax error: unexpected end of file
a
sh: ': not found

そこで質問なのですが、 とsh -c [bash -c ...]同じ結果になるようなエスケープの適切な構文は何でしょうかbash -c ...?

答え1

一重引用符の内側では、特別な意味を持つ文字はありません。二重引用符の内側では、"\$`特別な意味を持ちます。外側から内側に向​​かって考えます。まず外側のシェルによってどのような文字列が構築されるかを把握し、次に内側のシェルがそれをどのように処理するかを把握します。

例えば、変数がa外側のシェルで定義されていない場合、

sh -c "bash -c \"a=1; echo a\$a;\""

外側のシェルは、 に展開される二重引用符で囲まれた文字列を認識しbash -c "a=1; echo a$a;"、その文字列が中間の に渡されますsh。は、これをと の 2 つの引数を持つshの呼び出しとして解析します。後者は、変数が定義されていない二重引用符で囲まれた文字列を展開した結果です。bash-ca=1; echo a;"a=1; echo a$a;"a

この分析を行うと、この段階で必要なものを取得する 1 つの方法がわかります"a=1; echo a\$a;"。この追加のバックスラッシュを取得するには、二重引用符内でシェル展開の 1 段階がすでに実行されているため、元のコードに 2 つのバックスラッシュを追加する必要があります。2 プラス 1 は 3 です。

sh -c "bash -c \"a=1; echo a\\\$a;\""

外側の文字列では何も展開したくないので、一重引用符を使用する方が簡単です。

sh -c 'bash -c "a=1; echo a\$a;"'

bashどちらから呼び出しても何も展開したくないのでsh、そこでも一重引用符を使用できます。一重引用符で囲まれた文字列内に一重引用符を入れることはできませんが、これをシミュレートする方法があります: put '\''。正式には、これは一重引用符で囲まれたリテラルを終了し、リテラル一重引用符を追加し、新しい一重引用符で囲まれたリテラルを開始しますが、4 文字のシーケンスは、'\''一重引用符で囲まれた文字列内に一重引用符を入れる方法と考えることができます。

sh -c 'bash -c '\''a=1; echo a$a;'\'''

''最後にそれを省略することもできます。体系的ではありませんが、目には優しくなります。

sh -c 'bash -c '\''a=1; echo a$a;'\'

一重引用符は二重引用符内では特別な意味を持たないため、外側のシェル内で"bash -c 'a=1; echo a\$a;'"の展開を防ぐためにドル記号の前にバックスラッシュを記述する必要があります。$a

答え2

さて、実際に動作するものをいくつか見つけました (注: これは Ubuntu 11.04 です)。

$ sh -c "bash -c 'a=1; echo a\$a \'"
a1
$ sh -c "bash -c 'a=1; echo a\$a;'"
a1

... 最初の一重引用符がエスケープされず、ドルがエスケープされている限り、最後の一重引用符がエスケープされているかどうかに関係なく、動作するようです。これはまだかなり困惑しています...

関連情報