web-dev-qa-db-ja.com

エコーが引用文字を無視するのはなぜですか?

echoコマンドには、指定した完全なテキストが含まれていません。たとえば、私が行う場合:

   $ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '

それは出力します:

echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk {print } `

単一引用符(')echoコマンドに含まれていたものは含まれていません。二重引用符に切り替えると:

$ echo " echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `  "

echoは何も出力しません!最初の例では単一引用符を省略し、2番目の例では何も出力しないのはなぜですか?入力した内容を正確に出力するにはどうすればよいですか?

24
Diana

シェルは、echoに到達する前に、'"の両方の引用符を解釈しています。私は通常、引数を二重引用符で囲み、不要であってもエコーするようにします。例えば:

$ echo "Hello world"
Hello world

したがって、最初の例では、出力にリテラル引用符を含める場合は、エスケープする必要があります。

$ echo \'Hello world\'
'Hello world'

または、すでに引用符で囲まれた引数内で使用する必要があります(ただし、同じ種類の引用符にすることはできません。そうしないと、エスケープする必要があります)。

$ echo "'Hello world'"
'Hello world'

$ echo '"Hello world"'
"Hello world"

2番目の例では、文字列の途中でコマンド置換を実行しています。

grep  $ARG  /var/tmp/setfile  | awk {print $2}

$で始まるものも、シェルによって特別に処理されます。シェルはそれらを変数として扱い、それらをその値に置き換えます。ほとんどの場合、これらの変数はどちらもシェルで設定されていないため、実際に実行されるだけです

grep /var/tmp/setfile | awk {print}

grepは1つの引数しか認識しないため、引数は検索対象のパターンであり、データを読み取る場所はstdinであると想定しているため、入力の待機をブロックします。これが、2番目のコマンドがハングしたように見える理由です。

これは、引数を単一引用符で囲んだ場合(最初の例がほぼ機能する理由です)には発生しないため、これは必要な出力を取得する1つの方法です。

echo \'' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '\'

二重引用符で囲むこともできますが、シェルがそれらを変数として解決しないように$sをエスケープする必要があり、シェルがコマンド置換をすぐに実行しないようにバッククォートします。

echo "' echo PARAM=\`  grep  \$ARG  /var/tmp/setfile  | awk '{print \$2}' \`    '"
33
Michael Mrozek

Michael Mrozekの回答 がこれをうまくカバーしているので、なぜあなたの試みが彼らの振る舞いを振る舞うのかについては詳しくは触れません。一言で言えば、単一引用符('…')の間のすべてが文字どおりに解釈され(特に最初の'はリテラル文字列の終わりを示します)、`および$"…"の間の特別な意味を保持します。

単一引用符内での引用はないため、単一引用符で囲まれた文字列内に単一引用符を置くことはできません。ただし、次のようなイディオムがあります。

echo 'foo'\''bar'

これはfoo'barを出力します。これは、echoへの引数が単一引用符で囲まれた3文字の文字列fooで構成され、単一文字'と連結されているためです(保護によって取得)文字'は、特別な意味から直前の\)までで、単一引用符で囲まれた3文字の文字列barと連結されます。したがって、それは舞台裏で起こることではありませんが、'\''は、単一引用符で囲まれた文字列内に単一引用符を含める方法と考えることができます。

複雑な複数行の文字列を印刷したい場合、より良いツールは ここのドキュメント です。ヒアドキュメントは、2つの文字<<と、それに続く<<EOFなどのマーカー、次にいくつかのテキスト行、そしてその行の終了マーカーのみで構成されます。マーカーが何らかの方法で引用符で囲まれている場合('EOF'または"EOF"または\EOFまたは 'E "" OF'または…)、テキストは文字どおりに解釈されます(ただし、一重引用符内と同様ですが、 'も通常の文字です)。マーカーがまったく引用されていない場合、テキストは二重引用符で囲まれた文字列のように解釈され、$\`は特別なステータスを保持します(ただし、"および改行は文字どおりに解釈されます)。

cat <<'EOF'
echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `
EOF

さて、これは動作します:-

echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '

ここでテストする:-

[asmith@yagi ~]$ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '
 echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' ` 
1
Andy Smith

ギレスの提案 hereドキュメントを使用することは本当に素晴らしいことであり、Perlのようなスクリプト言語内でも機能します。 OPの問題に対する彼の回答に基づく具体的な例として、

use strict;
my $cmd = q#cat <<'EOF' # . "\n" .
          q#echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `# . "\n" .
          q#EOF# ;
open (my $OUTPUT, "$cmd |");
print $_ while <$OUTPUT>;
close $OUTPUT;

確かに、この例は少し不自然ですが、SQLステートメントをpsqlcatではなく)に送信する場合などに便利なテクニックであることがわかりました。

(上記の一般的な引用構文では#の代わりに英数字以外の非スペース文字を使用できますが、この例ではハッシュがよく見えるようで、notコメントとして扱われます。)

0
Randall