一覧OS-12-1 · シェルと標準入出力

シェルの引用符・引数・コマンド検索

入力した文字列が引数と実行ファイルに変わる過程を確認します。

説明

シェルは入力をそのままプログラムに渡すだけでなく、変数やワイルドカードを展開し、実行するコマンドを探します。引用符の付け方が変わると、同じ値でも引数の数や内容が変わります。

引数を一覧する短いスクリプトで空白、空文字列、ワイルドカードを比較します。続いてコマンド置換で値を取り込み、同名の実行ファイルを二つ用意して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 / シェル展開と入出力

1
cat args.sh
#!/bin/sh
printf 'argc=%s\n' "$#"
i=1
for arg do
  printf '%s=<%s>\n' "$i" "$arg"
  i=$((i + 1))
done
2
name='notes final.txt'; sh args.sh $name
argc=2
1=<notes>
2=<final.txt>
3
sh args.sh "$name"
argc=1
1=<notes final.txt>
4
sh args.sh '$name'
argc=1
1=<$name>
5
empty=''; sh args.sh $empty; sh args.sh "$empty"
argc=0
argc=1
1=<>
6
sh args.sh *.txt; sh args.sh '*.txt'
argc=3
1=<a.txt>
2=<b.txt>
3=<notes final.txt>
argc=1
1=<*.txt>
7
picked=$(printf 'notes final.txt\n\n'); sh args.sh "$picked"
argc=1
1=<notes final.txt>
8
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
9
PATH=bin-second:bin-first:/usr/bin:/bin; hash -r; command -v where; where; ./bin-first/where
bin-second/where
second
first

演習完了