変数の置換とコマンドの置換には常に二重引用符を使用します:"$foo"
、"$(foo)"
$foo
引用符で囲まずに使用すると、スクリプトは$(foo)
空白またはを含む入力またはパラメーター(または、コマンド出力)で停止します\[*?
。
そこで、読むのをやめることができます。さて、OK、さらにいくつかあります:
read
— ビルトインを使用して1行read
ずつ入力を読み取るには、while IFS= read -r line; do …
Plain read
を使用してバックスラッシュと空白を特別に処理します。
xargs
— 避けてくださいxargs
。使用する必要がある場合はxargs
、それを作成しxargs -0
ます。代わりにfind … | xargs
、好みfind … -exec …
ます。
xargs
空白と文字を\"'
特別に扱います。
この回答は、Bourne / POSIXスタイルのシェルに適用されます(sh
、ash
、dash
、bash
、ksh
、mksh
、yash
...)。Zshユーザーはこれをスキップして、二重引用符が必要な場合の終わりを読む必要がありますか?代わりに。全体の核心が必要な場合は、標準またはシェルのマニュアルをお読みください。
以下の説明には、いくつかの近似値が含まれていることに注意してください(ほとんどの条件に当てはまりますが、周囲のコンテキストまたは構成によって影響を受ける可能性があるステートメント)。
なぜ書く必要があるの"$foo"
ですか?引用符がないとどうなりますか?
$foo
「変数の値を取る」という意味ではありませんfoo
。それはもっと複雑なことを意味します:
- まず、変数の値を取得します。
- フィールド分割:その値を空白で区切られたフィールドのリストとして扱い、結果のリストを作成します。変数が含まれている場合、例えば、
foo * bar
このステップの結果は、3つの要素のリストですfoo
、*
、bar
。
- ファイル名の生成:各フィールドをグロブ、つまりワイルドカードパターンとして扱い、このパターンに一致するファイル名のリストに置き換えます。パターンがどのファイルとも一致しない場合、変更されません。この例では、これにより
foo
、現在のディレクトリ内のファイルのリスト、最後にを含むリストが作成されますbar
。現在のディレクトリが空の場合、結果はfoo
、*
、bar
。
結果は文字列のリストであることに注意してください。シェル構文には、リストコンテキストと文字列コンテキストの2つのコンテキストがあります。フィールドの分割とファイル名の生成はリストコンテキストでのみ行われますが、ほとんどの場合はそうです。二重引用符は、文字列コンテキストを区切ります。二重引用符で囲まれた文字列全体は、分割されない単一の文字列です。(例外:"$@"
位置パラメーターのリストに展開すること。たとえば、3つの位置パラメーターがある場合"$@"
と同等"$1" "$2" "$3"
です。$ *と$ @の違いは何ですか?を参照してください。)
$(foo)
またはを使用したコマンド置換でも同じことが起こり`foo`
ます。サイドノートでは、使用しないでください`foo`
:そのクォート規則は奇妙で移植性がなく、すべての最新のシェル$(foo)
は直感的なクォート規則を除いて完全に同等です。
算術置換の出力にも同じ展開が行われますが、展開IFS
できない文字のみが含まれているため、通常は問題になりません(数字やが含まれていないことを前提としています-
)。
二重引用符が必要な場合を参照してください。引用符を省略できる場合の詳細については。
このすべてのリマロールが発生することを意味する場合を除き、変数とコマンドの置換を常に二重引用符で囲むようにしてください。注意してください:引用符を省略すると、エラーだけでなくセキュリティホールにつながる可能性があります。
ファイル名のリストを処理するにはどうすればよいですか?
myfiles="file1 file2"
ファイルを区切るスペースを使用してを記述した場合、スペースを含むファイル名では機能しません。Unixファイル名には、/
(常にディレクトリ区切り文字である)およびnullバイト(ほとんどのシェルでシェルスクリプトで使用できない)以外の任意の文字を含めることができます。
と同じ問題myfiles=*.txt; … process $myfiles
。これを行うと、変数myfiles
には5文字の文字列が含まれ、ワイルドカードが展開さ*.txt
れることを記述$myfiles
します。スクリプトをに変更するまで、この例は実際に機能しますmyfiles="$someprefix*.txt"; … process $myfiles
。someprefix
がに設定されている場合final report
、これは機能しません。
任意の種類(ファイル名など)のリストを処理するには、配列に入れます。これにはmksh、ksh93、yashまたはbash(またはzsh、これらのすべての引用の問題がない)が必要です。単純なPOSIXシェル(ashやdashなど)には配列変数がありません。
myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"
Ksh88には、異なる割り当て構文の配列変数がありますset -A myfiles "someprefix"*.txt
(ksh88 / bashの移植性が必要な場合は、異なるksh環境での割り当て変数を参照してください)。Bourne / POSIXスタイルのシェルには、単一の1つの配列があります。"$@"
これは、設定した位置パラメーターの配列でありset
、関数に対してローカルです。
set -- "$someprefix"*.txt
process -- "$@"
で始まるファイル名は-
どうですか?
関連する注意事項として、ファイル名は-
(ダッシュ/マイナス)で始まることができることに注意してください。ほとんどのコマンドはオプションを示すものとして解釈します。可変部分で始まるファイル名がある場合は、--
上記のスニペットのように、その前に必ず渡すようにしてください。これは、コマンドの最後にオプションが到達したことを示すので、それ以降は、で始まっていてもファイル名-
です。
または、ファイル名が以外の文字で始まっていることを確認できます-
。絶対ファイル名はで始まり、相対名の先頭に/
追加でき./
ます。次のスニペットは、変数の内容を、f
で始まらないことが保証されている同じファイルを参照する「安全な」方法に変えます-
。
case "$f" in -*) "f=./$f";; esac
このトピックに関する最後の注意事項では、一部のコマンドは-
、--
。という名前の実際のファイルを参照する必要がある場合-
、またはそのようなプログラムを呼び出しており、stdinからの読み取りやstdoutへの書き込みを望まない場合は-
、上記のように書き換えてください。「du -sh *」と「du -sh ./*」の違いをご覧ください。さらなる議論のために。
コマンドを変数に保存するにはどうすればよいですか?
「コマンド」とは、コマンド名(実行可能ファイルとしての名前、フルパスの有無にかかわらず、または関数、ビルトインまたはエイリアスの名前)、引数付きのコマンド名、またはシェルコードの3つを意味します。それに応じて、変数にそれらを保存するさまざまな方法があります。
コマンド名がある場合は、それを保存し、通常どおり二重引用符で変数を使用します。
command_path="$1"
…
"$command_path" --option --message="hello world"
引数付きのコマンドがある場合、問題は上記のファイル名のリストと同じです。これは文字列ではなく文字列のリストです。引数をスペースで区切った単一の文字列に詰め込むことはできません。その場合、引数の一部であるスペースと引数を区切るスペースの違いを区別できないためです。シェルに配列がある場合は、それらを使用できます。
cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"
配列なしのシェルを使用している場合はどうなりますか?それらを変更することを気にしない場合は、引き続き位置パラメータを使用できます。
set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"
リダイレクトやパイプなどの複雑なシェルコマンドを保存する必要がある場合はどうなりますか?または、位置パラメータを変更したくない場合は?次に、コマンドを含む文字列を作成し、eval
組み込みコマンドを使用できます。
code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"
定義のネストされた引用符に注意してくださいcode
:単一引用符'…'
は文字列リテラルを区切るので、変数の値はcode
string /path/to/executable --option --message="hello world" -- /path/to/file1
です。eval
組み込みは、それがスクリプトに登場したかのように、引数として渡された文字列を解析するためにシェルに指示しますので、その時点での引用符やパイプ等、解析され
使用にeval
は注意が必要です。いつ解析されるかについて慎重に考えてください。特に、コードにファイル名を詰め込むことはできません。ソースコードファイルにある場合と同様に、引用符で囲む必要があります。それを行う直接的な方法はありません。何かのようにcode="$code $filename"
ファイル名が任意のシェルの特殊文字が含まれている場合改行(空白を、$
、;
、|
、<
、>
、など)。code="$code \"$filename\""
まだ壊れてい"$\`
ます。でも、code="$code '$filename'"
ファイル名が含まれている場合は壊れます'
。2つの解決策があります。
ファイル名の周りに引用符のレイヤーを追加します。それを行う最も簡単な方法は、その周りに一重引用符を追加し、一重引用符をで置き換えること'\''
です。
quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
code="$code '${quoted_filename%.}'"
コード内に変数展開を保持し、コードフラグメントが構築されたときではなく、コードが評価されたときに参照されるようにします。これは簡単ですが、コードがループで構築されている場合などではなく、コードが実行されたときに変数が同じ値である場合にのみ機能します。
code="$code \"\$filename\""
最後に、コードを含む変数が本当に必要ですか?コードブロックに名前を付ける最も自然な方法は、関数を定義することです。
どうしたread
?
なし-r
でread
、継続行を許可します—これは入力の単一の論理行です:
hello \
world
read
入力行をで文字で区切られたフィールドに分割します$IFS
(-r
、バックスラッシュもこれらをエスケープします)。入力三個の単語を含む行である場合、例えば、次にread first second third
設定first
、入力の最初の単語にsecond
第2ワードおよびthird
第三のワード。さらに単語がある場合、最後の変数には、前の単語を設定した後に残っているすべてが含まれます。先頭および末尾の空白は削除されます。
IFS
空の文字列に設定すると、トリミングが回避されます。「IFS =ではなく、なぜIFS = readが頻繁に使用されるのか」を参照してください。読みながら..`?より長い説明のため。
何が問題なのxargs
ですか?
の入力形式xargs
は、空白で区切られた文字列で、オプションで単一引用符または二重引用符で囲むことができます。この形式を出力する標準ツールはありません。
xargs -L1
またはへの入力xargs -l
はほとんど行のリストですが、完全ではありません—行の終わりにスペースがある場合、次の行は継続行です。
xargs -0
該当する場所(および利用可能な場所:GNU(Linux、Cygwin)、BusyBox、BSD、OSX を使用できますが、POSIXにはありません)。nullファイルはほとんどのデータ、特にファイル名には表示されないため、これは安全です。ファイル名のヌル区切りリストを作成するには、を使用しますfind … -print0
(または、find … -exec …
以下で説明するように使用できます)。
で見つかったファイルを処理するにはどうすればよいfind
ですか?
find … -exec some_command a_parameter another_parameter {} +
some_command
外部コマンドである必要があります。シェル関数またはエイリアスにすることはできません。ファイルを処理するためにシェルを呼び出す必要がある場合は、sh
明示的に呼び出します。
find … -exec sh -c '
for x do
… # process the file "$x"
done
' find-sh {} +
他に質問があります
ブラウズ引用このサイト上のタグを、またはシェルまたはシェルスクリプト。(「詳細...」をクリックすると、一般的なヒントと一般的な質問の選択リストが表示されます。)検索して回答が見つからない場合は、質問してください。
shellcheck
プログラムの品質を改善するのに役立ちます。