シェルの引用符・引数・コマンド検索
入力した文字列が引数と実行ファイルに変わる過程を確認します。
説明
シェルは入力をそのままプログラムに渡すだけでなく、変数やワイルドカードを展開し、実行するコマンドを探します。引用符の付け方が変わると、同じ値でも引数の数や内容が変わります。
引数を一覧する短いスクリプトで空白、空文字列、ワイルドカードを比較します。続いてコマンド置換で値を取り込み、同名の実行ファイルを二つ用意してPATHの検索順と直接指定の違いを確かめます。
1プログラムへ渡った引数を表示
args.shは引数の数と内容を表示する短いスクリプトです。先に中身を確認します。
cat args.sh引数表示用のソース"$#"は引数の個数です。for argは渡された引数を一つずつ取り出し、番号と山括弧付きで表示します。空白を含む値や空の値が一つの引数なのか、出力から確認できます。
2空白を含む変数を展開
教材フォルダーにはa.txt、b.txt、notes final.txtがあります。空白を含むファイル名を変数に代入し、引用符なしで渡します。
name='notes final.txt'空白を含む値を代入; sh args.sh $name引用符なしで展開代入では空白を含めて一つの値が保存されます。しかし引用符なしの$nameは展開後に単語分割され、この条件ではnotesとfinal.txtの二つの引数になります。コマンドが受け取る引数は、入力欄に見える単語数だけでは決まりません。
3二重引用符で一つの引数を保つ
同じ変数を二重引用符で囲みます。
sh args.sh引数を確認 "$name"展開後も一つの引数argc=1となり、notes final.txt全体が一つの引数です。二重引用符の内側でも変数展開は行われますが、その結果の単語分割とファイル名展開は抑えられます。
4単一引用符は変数名もそのまま渡す
単一引用符に変えると、ドル記号と変数名がどうなるかを確認します。
sh args.sh引数を確認 '$name'ドル記号も文字として扱う中身は$nameという5文字です。単一引用符内では変数展開が行われません。引用符自体はシェルが取り除き、プログラムには渡しません。
5空の変数と空の引数
空文字列を、引用符あり・なしで順に渡します。
empty=''空文字列を保存; sh args.sh $empty引用符なし; sh args.sh "$empty"空の引数を保持引用符なしでは空の展開結果が消えてargc=0、引用符ありでは長さ0の引数が残ってargc=1です。引数なしと空文字列の指定を別扱いするプログラムでは、この違いが動作に影響します。
6ワイルドカードからファイル名へ
*.txtを引用符なしで渡し、続けて引用符で囲んで渡します。
sh args.sh *.txtファイル名展開; sh args.sh '*.txt'パターン文字列をそのまま渡す引用符なしでは一致した3ファイルが別々の引数になります。ファイル名に含まれる空白は、この段階で再び単語分割されません。引用符ありでは*.txtそのものが一つの引数です。
7コマンド置換で得た値にも引用符を使う
別のコマンドの標準出力を変数に取り込みます。出力末尾の改行が取り除かれる点も確認します。
picked=$(printf 'notes final.txt\n\n')標準出力を取り込む; sh args.sh "$picked"一つの引数として渡す末尾の二つの改行が除去され、ファイル名だけが一つの引数として残ります。$(...)はコマンド置換です。取り込んだ値を再利用するときも、空白を含む可能性があれば引用符を付けます。
8PATHの検索順
bin-firstとbin-secondに、出力だけが違う同名コマンドwhereを作ります。この演習用のシェルのPATHを先頭から検索させます。
mkdir bin-first bin-second比較用の置き場所; printf '#!/bin/sh\nprintf "first\\n"\n' > bin-first/where一つ目のコマンド; printf '#!/bin/sh\nprintf "second\\n"\n' > bin-second/where二つ目のコマンド; chmod +x bin-first/where bin-second/where実行可能にする; PATH=bin-first:bin-second:/usr/bin:/bin検索順を指定; command -v where; where解決先と出力bin-first/whereが見つかり、firstが表示されます。スラッシュを含まない外部コマンド名はPATHの各ディレクトリから探します。PATHに相対パスを入れると作業ディレクトリの影響を受けるため、実運用では信頼する場所を指定します。
9検索順を変える場合とパスを直接指定する場合
PATHの順を逆にし、検索履歴を消して再実行します。最後に一つ目をパスで指定します。
PATH=bin-second:bin-first:/usr/bin:/bin検索順を逆にする; hash -r記憶した実行先を消す; command -v where; where検索による実行; ./bin-first/whereパスを指定した実行検索で選ばれるのはbin-second/whereで、secondが表示されます。./bin-first/whereはスラッシュを含むのでPATHを検索せず、指定したファイルを実行します。変数や引用符が引数を決め、コマンド名の形式とPATHが実行先を決めます。
まとめ
二重引用符は変数を展開しつつ引数のまとまりを保ち、単一引用符は文字列をそのまま扱います。
単語分割とファイル名展開の順序により、最終的な引数が決まります。
PATHによる検索と、スラッシュを含むパスの直接指定では、実行先の決め方が違います。
操作履歴と解説を振り返れます。
Bash / シェル展開と入出力
cat args.sh#!/bin/sh printf 'argc=%s\n' "$#" i=1 for arg do printf '%s=<%s>\n' "$i" "$arg" i=$((i + 1)) done
name='notes final.txt'; sh args.sh $nameargc=2 1=<notes> 2=<final.txt>
sh args.sh "$name"argc=1 1=<notes final.txt>
sh args.sh '$name'argc=1 1=<$name>
empty=''; sh args.sh $empty; sh args.sh "$empty"argc=0 argc=1 1=<>
sh args.sh *.txt; sh args.sh '*.txt'argc=3 1=<a.txt> 2=<b.txt> 3=<notes final.txt> argc=1 1=<*.txt>
picked=$(printf 'notes final.txt\n\n'); sh args.sh "$picked"argc=1 1=<notes final.txt>
mkdir bin-first bin-second; printf '#!/bin/sh\nprintf "first\\n"\n' > bin-first/where; printf '#!/bin/sh\nprintf "second\\n"\n' > bin-second/where; chmod +x bin-first/where bin-second/where; PATH=bin-first:bin-second:/usr/bin:/bin; command -v where; wherebin-first/where first
PATH=bin-second:bin-first:/usr/bin:/bin; hash -r; command -v where; where; ./bin-first/wherebin-second/where second first
演習完了