私はいくつかの理由でかなり移植性の高いシェルスクリプトで書こうとする小さなオープンソースプロジェクトを持っています。自動化された統合テストは、パス式の敵対文字が正しく処理されているかどうかを確認します。
/bin/sh
プロバイダが提供したユーザーを使用したbash
テストでエラーが発生しました。これを次のように単純化しました。
echo "A bug\\'s life"
echo "A bug\\\\'s life"
Bash では、次のような予測結果が生成されます。
A bug\'s life
A bug\\'s life
私が開発したダッシュを使用して、次のことを行います。
A bug\'s life
A bug\'s life
まだダッシュでバグが見つからず、何かが抜けている可能性があります。これについて合理的な説明はありますか?
答え1
存在する
echo "A bug\\'s life"
二重引用符\
で二重引用符内で特別なので、最初のものは\
シェルで次のように理解されます。逃げる/引用する第二\
。したがって、A bug\'s life
引数がに渡されますecho
。
echo "A bug\'s life"
同じ効果を得るでしょう。'
二重引用符の中には特別な内容がないため\
削除されないため、渡されたパラメータとまったく同じですecho
。
説明どおりなぜprintfがechoより優れているのですか?echo
、実装間には多くの違いがあります。
UNIX準拠の実装では、dash
組み込みのecho
改行文字、バックスペース文字、オクテット文字シーケンス...およびバックスラッシュ自体の\
エスケープシーケンスを導入するために使用されます。\n
\b
\0123
\\
一部(非POSIX)では、-e
オプションが必要な場合、または適合モードでのみこれを実行します(たとえば、OS / Xなどの正しいオプションを使用してビルドするとき、またはbash
環境でsh
呼び出すときなど)。SHELLOPTS=xpg_echo
したがって、標準(Unix標準のみ、POSIXは動作を指定しません)echo
で
echo '\\'
それは次のとおりです。
echo "\\\\"
出力一つバックスラッシュ(bash
一貫性モードでない場合):
echo '\\'
出力されます二つバックスラッシュ。
避けてecho
使用するのが最善ですprintf
。
$ printf '%s\n' "A bug\'s life"
A bug\'s life
この場合、すべての実装で同じように機能しますprintf
。
dash
1はこの点で互換性がありますが、UNIX仕様(POSIX + XSI)で必要なものは出力しません。echo
echo -n
-n<newline>
答え2
echoとprintfの問題は、バックティック文字が「特殊文字」の場合を理解することにのみ関連しています。
最も簡単なのは文字列を使用することですprintf '%s' "$string"
。
この場合、処理する必要がある特殊文字はなく、2番目の引数でprintfコマンドが受け取るすべての内容がそのまま印刷されます。
一重引用符のみが使用されることに注意してください。
$ printf '%s\n' '\\\\\\\\\T ' # nine \
\\\\\\\\\T # nine \
文字列が最初の引数として使用される場合、特定の文字は特殊です。
ペア\\
は単一\
と\T
単数を表しますT
。
$ printf '\\\\\\\\\T ' # nine \
\\\\T # four \
4つのペアはそれぞれ1つ\\
に変換され\
、最後の\T
ペアは1つに変換されますT
。
$ printf '\\\\\\\\\a ' # nine \
\\\\ # four \
4つのペアのそれぞれは\\
単一の文字に変換され、\
最後の文字は\a
ベル(BEL)文字(印刷できません)に変換されます。
でも同じ状況が発生します一部エコ実装。
ダッシュ実装は常に特殊なバックスラッシュ文字を変換します。
このコードをスクリプトに入れると:
set -- '\g ' '\\g ' '\\\g ' '\\\\g ' '\\\\\g ' '\\\\\\g ' '\\\\\\\g ' '\\\\\\\\g ' '\\\\\\\\\g '
for i ; do
printf '<%-14s> \t<%-9s> \t<%-14s> \t<%-12s>\n' \
"$(printf '%s ' "|$i|")" \
"$(printf "|$i|")" \
"$(echo "|$i|")" \
"$(echo -e "|$i|")" ;
done
その後、ダッシュが印刷されます(dash ./script
):
<|\g | > <|\g | > <|\g | > <-e |\g | >
<|\\g | > <|\g | > <|\g | > <-e |\g | >
<|\\\g | > <|\\g | > <|\\g | > <-e |\\g | >
<|\\\\g | > <|\\g | > <|\\g | > <-e |\\g | >
<|\\\\\g | > <|\\\g | > <|\\\g | > <-e |\\\g | >
<|\\\\\\g | > <|\\\g | > <|\\\g | > <-e |\\\g | >
<|\\\\\\\g | > <|\\\\g | > <|\\\\g | > <-e |\\\\g | >
<|\\\\\\\\g | > <|\\\\g | > <|\\\\g | > <-e |\\\\g | >
<|\\\\\\\\\g | > <|\\\\\g |> <|\\\\\g | > <-e |\\\\\g |>
最初の2つの列はすべてのシェル(printf)に対して同じです。
残りの2つは、使用されるエコーの特定の実装によって異なります。
例: ash ./script
(Busybox グレー):
<|\g | > <|\g | > <|\g | > <|\g | >
<|\\g | > <|\g | > <|\\g | > <|\g | >
<|\\\g | > <|\\g | > <|\\\g | > <|\\g | >
<|\\\\g | > <|\\g | > <|\\\\g | > <|\\g | >
<|\\\\\g | > <|\\\g | > <|\\\\\g | > <|\\\g | >
<|\\\\\\g | > <|\\\g | > <|\\\\\\g | > <|\\\g | >
<|\\\\\\\g | > <|\\\\g | > <|\\\\\\\g | > <|\\\\g | >
<|\\\\\\\\g | > <|\\\\g | > <|\\\\\\\\g | > <|\\\\g | >
<|\\\\\\\\\g | > <|\\\\\g |> <|\\\\\\\\\g | > <|\\\\\g | >
使用されている文字がある場合はa
ダッシュを示します。
<|\a | > <| | > <| | > <-e | | >
<|\\a | > <|\a | > <|\a | > <-e |\a | >
<|\\\a | > <|\ | > <|\ | > <-e |\ | >
<|\\\\a | > <|\\a | > <|\\a | > <-e |\\a | >
<|\\\\\a | > <|\\ | > <|\\ | > <-e |\\ | >
<|\\\\\\a | > <|\\\a | > <|\\\a | > <-e |\\\a | >
<|\\\\\\\a | > <|\\\ | > <|\\\ | > <-e |\\\ | >
<|\\\\\\\\a | > <|\\\\a | > <|\\\\a | > <-e |\\\\a | >
<|\\\\\\\\\a | > <|\\\\ | > <|\\\\ | > <-e |\\\\ | >
バッシュの場合:
<|\a | > <| | > <|\a | > <| | >
<|\\a | > <|\a | > <|\\a | > <|\a | >
<|\\\a | > <|\ | > <|\\\a | > <|\ | >
<|\\\\a | > <|\\a | > <|\\\\a | > <|\\a | >
<|\\\\\a | > <|\\ | > <|\\\\\a | > <|\\ | >
<|\\\\\\a | > <|\\\a | > <|\\\\\\a | > <|\\\a | >
<|\\\\\\\a | > <|\\\ | > <|\\\\\\\a | > <|\\\ | >
<|\\\\\\\\a | > <|\\\\a | > <|\\\\\\\\a | > <|\\\\a | >
<|\\\\\\\\\a | > <|\\\\ | > <|\\\\\\\\\a | > <|\\\\ | >
これを行うには、コマンドを実行するシェルが文字列でも機能できるという説明を追加する必要があります。
$ printf '%s\n' '\\\\T '
\\\\T
$ printf '%s\n' "\\\\T "
\\T
シェルは二重引用符内にバックスラッシュを使用して操作を実行します。
このコードを使用すると:
tab=' '
say(){ echo "$(printf '%s' "$a") $tab $(echo "$a") $tab $(echo -e "$a")"; }
a="one \a " ; say
a="two \\a " ; say
a="t33 \\\a " ; say
a="f44 \\\\a " ; say
a="f55 \\\\\a " ; say
a="s66 \\\\\\a " ; say
a="s77 \\\\\\\a " ; say
a="e88 \\\\\\\\a " ; say
a="n99 \\\\\\\\\a " ; say
両方の効果が追加され、次のような結果が得られます。
$ bash ./script
one \a one \a one
two \a two \a two
t33 \\a t33 \\a t33 \a
f44 \\a f44 \\a f44 \a
f55 \\\a f55 \\\a f55 \
s66 \\\a s66 \\\a s66 \
s77 \\\\a s77 \\\\a s77 \\a
e88 \\\\a e88 \\\\a e88 \\a
n99 \\\\\a n99 \\\\\a n99 \\
ダッシュの場合、状況はさらに深刻です。
$ dash ./script
one one -e one
two two -e two
t33 \a t33 -e t33
f44 \a f44 -e f44
f55 \ f55 \ -e f55 \
s66 \ s66 \ -e s66 \
s77 \\a s77 \a -e s77 \a
e88 \\a e88 \a -e e88 \a
n99 \\ n99 \ -e n99 \