Bashを使用する場合、どの文字をエスケープする必要がありますか?


206

バッシュでエスケープする必要があるキャラクターの包括的なリストはありますか?それだけで確認できますsedか?

特に、%エスケープする必要があるかどうかを確認していました。私は試した

echo "h%h" | sed 's/%/i/g'

エスケープせずにうまくいきました%%エスケープする必要がないという意味ですか?これは必要性をチェックする良い方法でしたか?

そしてより一般的です:それらはエスケープするのshellと同じ文字bashですか?


4
一般的に、気にすれば、それは間違っています。データの処理には、コードに使用される解析および評価プロセスを実行する必要がなく、エスケープが無効になります。これはSQLのベストプラクティスと非常によく似ています。正しいことはバインド変数を使用することであり、間違ったことは文字列置換によって注入されたデータを「サニタイズ」することです。
Charles Duffy


8
@CharlesDuffyええ、でも、Prepared Statementsエンジンがバックエンドで実行していることが、単にエスケープしているだけの場合もあります。ブラウザーで表示する前にユーザーが送信したコメントをエスケープするので、SOは「間違っている」のでしょうか。いいえ。XSSを妨げています。まったく気にしないことはそれを間違っています。
パルティアンショット

@ParthianShot、準備されたステートメントエンジンがコードからデータを完全にアウトオブバンドに保っていない場合、それを書いた人は撃たれるべきです。はい、MySQLのワイヤプロトコルがそのように実装されていることは知っています。私の発言は立っています。
Charles Duffy

@CharlesDuffyそして私のポイント-時々あなたのオプションは、純粋主義者をうんざりさせるツールチェーンを使用して何かを安全に機能させることです、またはそれをきれいにするために時間と労力を8倍に沈めることです-また、まだ立っています。
パルティアンショット

回答:


282

だけでshなくでも機能する2つの簡単で安全なルールがありbashます。

1.文字列全体を一重引用符で囲みます

これは、単一引用符自体を除くすべての文字で機能します。単一引用符をエスケープするには、その前の引用符を閉じ、単一引用符を挿入して、引用符を再度開きます。

'I'\''m a s@fe $tring which ends in newline
'

sedコマンド: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2.すべての文字をバックスラッシュでエスケープします

これは、改行を除くすべての文字で機能します。改行文字には、一重引用符または二重引用符を使用します。空の文字列は引き続き処理する必要があります-に置き換えます""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

sedコマンド:sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'

2b。2のより読みやすいバージョン

のような安全な文字セットがあり、[a-zA-Z0-9,._+:@%/-]エスケープせずにそのままにして読みやすくすることができます

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

sedコマンド:LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'


sedプログラムでは、入力の最後の行が改行バイトで終了しているかどうかを確認できないことに注意してください(空の場合を除く)。これが、上記の両方のsedコマンドがそうではないと想定している理由です。引用符で囲まれた改行を手動で追加できます。

シェル変数は、POSIXの意味でのテキストに対してのみ定義されることに注意してください。バイナリデータの処理は定義されていません。重要な実装では、バイナリはNULバイトを除いて機能します(変数はC文字列で実装され、C文字列、つまりプログラム引数として使用されるため)が、latin1などの「バイナリ」ロケールに切り替える必要があります。


(のPOSIX仕様を読むことで、ルールを簡単に検証できますsh。bashについては、@ AustinPhillipsによってリンクされているリファレンスマニュアルを確認してください)


注:#1の良いバリエーションは、github.com / scop / bash-completion / blob / …で確認できます。実行する必要はありませんがsed、必要ですbash
jwd

4
これらを動作させるのに苦労している他の誰か(私のような!)への注意... OSXで取得したsedのフレーバーは、これらのsedコマンドを適切に実行しないようです。Linuxでも問題なく動作します。
dalelane 2017年

@dalelane:ここではテストできません。両方で動作するバージョンがある場合は編集してください。
Jo So

文字列が '-'(マイナス)で始まる場合、見逃したようですが、それはファイル名にのみ適用されますか?-後者の場合、前に「./」が必要です。
slashmais 2017

どういう意味かわかりません。これらのsedコマンドでは、入力文字列はstdinから取得されます。
Jo So

59

シェル入力として再利用できる形式

この種のリクエスト用に作成された特別な printfフォーマットディレクティブ(%q)があります。

printf [-v var]形式[引数]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

いくつかのサンプル:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

これは変数でも使用できます。

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

すべての(128)ASCIIバイトでのクイックチェック:

128から255までのすべてのバイトはエスケープする必要があることに注意してください。

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

これは次のようなものをレンダリングする必要があります:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

最初のフィールドがバイトのヘキサ値であるE場合、2番目のフィールドは文字をエスケープする必要がある場合に含まれ、3番目のフィールドはエスケープされた文字の表示を示します。

なんで,

やのように、常にエスケープする必要がない文字がいくつか表示されることがあります。,}{

だから常にではなく、いつか

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

または

echo test { 1, 2, 3 }
test { 1, 2, 3 }

しかし注意:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 

これはbashの/ shを経由してpritnfを呼び出し、文字列は最初のシェルはbashの/ shのためにエスケープする必要があります、という問題がある
ThorSummoner

1
@ThorSummoner、文字列をリテラル引数として別の言語からシェルに渡す場合(おそらく、引用の方法がすでにわかっている場合)。Pythonの場合:subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()はシェルで引用された適切なバージョンのを提供しarbitrary_stringます。
Charles Duffy、

1
FYI bash %qは長い間壊れていました-私の心がうまくいくなら、エラーが修正されました(ただし、まだ壊れている可能性があります)は、約10年間壊れていました。したがって、それに依存しないでください。
Jo So

@CharlesDuffyもちろん、Pythonの土地に入るとshlex.quote()(> = 3.3、-undocumented-for old pipes.quote()versions)、この作業も行い、ほとんどの文字列のより人間が読めるバージョン(必要に応じて引用符とエスケープを追加)を生成します。シェルを生成する必要はありません。
Thomas Perl、

1
に関する特別なメモを追加していただき、ありがとうございます,。組み込みのBash printf -- %q ','はを提供\,するが/usr/bin/printf -- %q ',',(エスケープされていない)を提供することを知って驚いた。他の文字も同じ:{|}~
kevinarpe

34

他人がRTFMをしなくても済むように... bashで

二重引用符で文字を囲むことを除いて、全ての文字のリテラル値を保持し$`\、および、履歴展開が有効になっている場合、!

...したがって、それら(そしてもちろん引用自体)をエスケープする場合は、おそらく大丈夫です。

より慎重な「疑わしいときはエスケープする」アプローチをとる場合、識別子文字(ASCII文字、数字、または '_')をエスケープしないことで、代わりに特別な意味を持つ文字を取得しないようにすることができます。これらが(たとえば、奇妙なPOSIX風のシェルでは)特別な意味を持つことはほとんどないため、エスケープする必要があります。


1
ここに上記の引用されたマニュアルがあります:gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk

これは短くて甘い、そしてほぼ正しい答えです(そのための+1)が、単一引用符を使用するのがさらに良いかもしれません-私の長い答えを見てください。
Jo So

26

print '%q' テクニックを使用して、ループを実行して、どの文字が特殊であるかを見つけることができます。

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

それはこの出力を与えます:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

結果の一部は、,少し疑わしいように見えます。これに関する@CharlesDuffyの入力を取得することは興味深いでしょう。


2
あなたは答読むことが,少し不審に見えるの最後の段落で、私の答え
F. HAURI

2
心に留めておく%qことがすべての可能なシェルコンテキストで特別な意味を持つことができるすべての文字をエスケープしますので、シェル内あなたは、文字を使用するために滑走しているところを知りません。,それ自体は彼女のシェルにとって特別な意味はありませんが、@ F.Hauriが彼の返信で指摘したように、{...}ブレース展開内では特別な意味があります:gnu.org/savannah-checkouts/gnu/bash/manual/…これは!これはまた、一般的にではなく、特定の状況でのみ拡張が必要です。echo Hello World!うまくecho test!test機能しますが失敗します。
メッキー

18

エスケープが必要な文字は、BourneまたはPOSIXシェルではBashとは異なります。一般に(非常に)Bashはこれらのシェルのスーパーセットであるため、エスケープするものshellはすべてBashでエスケープする必要があります。

良い一般的なルールは、「疑わしい場合は、それを回避する」ことです。ただし、一部の文字をエスケープすると、などの特別な意味が与えられます\n。これらは、およびのman bash下のページにリストされています。Quotingecho

それ以外は、英数字以外の文字はエスケープしてください。安全です。私は単一の決定的なリストを知りません。

マニュアルページはそれらをどこかにリストアップしますが、一箇所にはリストしません。言語を学びましょう。それが確実な方法です。

私を見つけたのは!です。これは、Bash(およびcsh)の特殊文字(履歴拡張)ですが、Kornシェルの特殊文字ではありません。echo "Hello world!"問題さえ与える。通常どおり、単一引用符を使用すると、特別な意味がなくなります。


1
私が特に気に入っているのは、「疑わしい場合は、それを回避する」というアドバイスである素晴らしい規則です。それでもsedエスケープする必要があるかどうかを確認するのに十分なものであるかどうかに疑問がある。ご回答有難うございます!
fedorqui 'SO stop harming' 2013

2
@fedorqui:でチェックするsed必要はありません。ほとんど何でもチェックできます。 sed問題ではないbashです。一重引用符の中に特殊文字はありません(一重引用符を除く)。そこに文字をエスケープすることもできません。sedREのメタキャラクタは安全であるとシェルのメタキャラクタと非常に多くの重複を持っているので、コマンドは通常、単一引用符内にある必要があります。例外は、シェル変数を埋め込む場合で、慎重に行う必要があります。
cdarke 2013

5
に確認してくださいecho。あなたが入れたものを取り出したら、それをエスケープする必要はありません。:)
マーク・リード

6

私はあなたがbash文字列について話していると思います。エスケープの要件が異なる文字列には、さまざまなタイプがあります。例えば。一重引用符の文字列は、二重引用符の付いた文字列とは異なります。

最良のリファレンスは、bashマニュアルの引用セクションです。

エスケープが必要な文字について説明します。一部の文字は、履歴の拡張など、有効になっているオプションによってはエスケープが必要になる場合があります。


3
だからそれは簡単な解決策なしで脱出がそのようなジャングルであることを確認し、それぞれのケースをチェックする必要があります。ありがとう!
fedorqui 'SO stop harming' 2013

@fedorqui他の言語と同様に、従うべき一連のルールがあります。bash文字列のエスケープの場合、マニュアルで説明されているように、一連のルールは非常に小さくなります。エスケープする必要がないため、使用する最も簡単な文字列は一重引用符です。ただし、一重引用符で囲まれた文字列に一重引用符を含める方法はありません。
オースティンフィリップス

@fedorqui。それはだないジャングル。エスケープはかなり可能です。私の新しい投稿を見てください。
Jo So

@fedorquiあなたは、単一引用符で囲まれた文字列内の単一引用符を使用することはできませんが、あなたのようなもので、それを「エスケープ」することができます:「テキスト」、「'」 『複数のテキスト』
CR。

4

オートコンプリートを使用すると、bashが一部の文字を自動的にエスケープすることに気付きました。

たとえば、という名前のディレクトリがある場合dir:A、bashは次のようにオートコンプリートしますdir\:A

これを使用して、ASCIIテーブルの文字を使用していくつかの実験を実行し、以下のリストを導き出しました。

オートコンプリートでbashエスケープする文字:(スペースを含む)

 !"$&'()*,:;<=>?@[\]^`{|}

bashがエスケープしない文字

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

/ディレクトリ名では使用できないため、除外しました)


2
本当に包括的なリストprintf %qが必要な場合は、引数として渡された場合に変更される文字と変更されない文字を確認することをお勧めします。理想的には、文字セット全体を調べます。
Charles Duffy、2016年

アポストロフィ文字列でも、文字や数字をエスケープして特殊文字を生成したい場合があります。例:tr '\ n' '\ t'は、改行文字をタブ文字に変換します。
ディックガーティン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.