単純なコマンドの書き方、つまりコマンドの後に引数が続く形を見てきました。 ここでは、単純なコマンドを組み合わせて、より複雑なコマンドを作る方法を見ていきます。
始める前に、入出力について簡単に紹介します。
プロセスには「標準入出力チャンネル」があります。
trコマンドはこの非常に純粋な例です。
それが行うのは、入力からテキストを読み取り、文字の変換を行い、結果のテキストをその出力に書き出すだけです。
「stdio」チャンネルの操作については、シラバスの後半で詳しく見ていきます。
これはシェルプログラミングの強力な機能の1つです。 パイプラインを使うと、テキストのストリームに対して高度な変換を行えます。
ユーザーのソート済みリストを作るには、次のようにします。
cat /etc/passwd | cut -d : -f 1 | sort
パイプ記号(|)は、あるコマンドの出力を別のコマンドの入力に接続します。
cutはcatの出力を読み取り、sortはcutの出力を読み取ります。
デフォルトでは、パイプライン内の各コマンドは別々のサブシェルで実行されます。 (サブシェルとは、現在実行中のシェルのコピーである子プロセスです。)
パイプラインの実行にはパフォーマンス上のコストがかかります。
似たようなコマンドからなる長いパイプラインがある場合は、それらを1つのコマンドにまとめることを検討してください。
たとえば、grep、cut、sed、awk、trを複数回使うパイプラインは、通常は効率化のために1つのawkコマンドにまとめられます。
パイプラインの終了ステータスは、パイプライン内の最後のコマンドの終了ステータスです。 つまり、途中のコマンドが失敗しても最後のコマンドが成功した場合、パイプライン全体の終了ステータスは0になります。
これを変更できるシェル設定があります。
「pipefail」設定が有効な場合(set -o pipefail)、パイプラインの終了ステータスは次のようになります。
コマンドリストは、;(または改行)、&&、||で区切られたパイプラインの並びです。
A; Bは、Aが完了した後にBが実行されるコマンドリストです。A && Bでは、Aが成功した場合(終了ステータス0で終了した場合)にのみBが実行されます。A || Bでは、Aが失敗した場合(終了ステータスが非ゼロで終了した場合)にのみBが実行されます。コマンドリストの終了ステータスは、最後に実行されたコマンドの終了ステータスです。
&&と||演算子は連鎖でき、前のコマンドのステータスに基づいて次のコマンドが条件付きで実行されます。
たとえば、次のとおりです。
A && B && C && D || E
これらの論理演算子は控えめに使いましょう。 あっという間に読みにくいコードや、理解しにくいロジックにつながることがあります。
たとえば、次の2つは同じだと思うかもしれません。
if A; then B; else C; fi
A && B || C
違いは、Cがいつ実行されるかです。
if文)では、CはAが失敗した場合にのみ実行されます。コマンドリストでコードを簡潔にできる例をいくつか紹介します。
三角形に関するデータを含むデータファイルがあり、1つの三角形がその辺を表す3つの別々の行で表現されているとします。
$ cat triangle.dat
3
4
5
9
12
14
条件に3つの別々のreadコマンドを指定したwhileループを使えます。
while read a && read b && read c; do
if is_pythagorean "$a" "$b" "$c"; then
echo "$a:$b:$c is pythagorean"
else
echo "$a:$b:$c is not pythagorean"
fi
done < triangle.dat
is_pythagoreanが3辺がピタゴラスの方程式を満たすかどうかを判定するコマンドだとすると、出力は次のようになります。
3:4:5 is pythagorean
9:12:14 is not pythagorean
多くのプログラミング言語には、ある条件が満たされない場合に例外をスローする形式のアサーションがあります。
assert(user == "Administrator", "wrong user");
BashでOR演算子を使ってその機能をシミュレートできます。
die () { # a little convenience function
echo "$*" >&2
exit 1
}
[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]] || die "incorrect password"
長いコマンドリストはすぐに読みにくくなります。 改行を積極的に使うと、かなり読みやすくなります。
2つの条件が満たされた場合に配列に単語を追加する以下の例を考えてみましょう。
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
Bashでは、パイプまたは論理演算子の後に改行を追加できます。
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
ただし、行末にある演算子は見落としやすいです。 _行継続_を使うと演算子を先頭に置けるため、リストが続いていることがより明確になります。
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
_行継続_とは、「バックスラッシュ」と「改行」という2文字の並び(\ + \n)です。
Bashがこの並びを見つけると、それは単にコードから削除され、それによって現在の行が次の行へ_継続_されます。
バックスラッシュと改行の間にスペースを入れないように注意してください。
もう1つの例です。
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
または
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort