トラック
/
Bash
Bash
/
シラバス
/
パイプラインとコマンドリスト
パイ

パイプラインとコマンドリスト の Bash

{other: "%{count}個の演習"}

パイプラインとコマンドリストについて

単純なコマンドの書き方、つまりコマンドの後に引数が続く形を見てきました。 ここでは、単純なコマンドを組み合わせて、より複雑なコマンドを作る方法を見ていきます。

入力と出力

始める前に、入出力について簡単に紹介します。

プロセスには「標準入出力チャンネル」があります。

  • プロセスは「標準入力」から_入力_を受け取ることができます。
  • プロセスは「標準出力」に_出力_を送信できます。
  • プロセスは「標準エラー出力」に_エラー出力_を送信できます。

trコマンドはこの非常に純粋な例です。 それが行うのは、入力からテキストを読み取り、文字の変換を行い、結果のテキストをその出力に書き出すだけです。

「stdio」チャンネルの操作については、シラバスの後半で詳しく見ていきます。

パイプライン

これはシェルプログラミングの強力な機能の1つです。 パイプラインを使うと、テキストのストリームに対して高度な変換を行えます。

ユーザーのソート済みリストを作るには、次のようにします。

cat /etc/passwd | cut -d : -f 1 | sort

パイプ記号(|)は、あるコマンドの出力を別のコマンドの入力に接続します。 cutはcatの出力を読み取り、sortはcutの出力を読み取ります。

Advanced
  • デフォルトでは、パイプライン内の各コマンドは別々のサブシェルで実行されます。 (サブシェルとは、現在実行中のシェルのコピーである子プロセスです。)

  • パイプラインの実行にはパフォーマンス上のコストがかかります。 似たようなコマンドからなる長いパイプラインがある場合は、それらを1つのコマンドにまとめることを検討してください。 たとえば、grep、cut、sed、awk、trを複数回使うパイプラインは、通常は効率化のために1つのawkコマンドにまとめられます。

  • パイプラインの終了ステータスは、パイプライン内の最後のコマンドの終了ステータスです。 つまり、途中のコマンドが失敗しても最後のコマンドが成功した場合、パイプライン全体の終了ステータスは0になります。

    これを変更できるシェル設定があります。 「pipefail」設定が有効な場合(set -o pipefail)、パイプラインの終了ステータスは次のようになります。

    • すべてのコマンドが成功した場合は0、そうでなければ
    • パイプライン内のコマンドの_最後の非ゼロ終了ステータス_。

コマンドリスト

コマンドリストは、;(または改行)、&&、||で区切られたパイプラインの並びです。

  • A; Bは、Aが完了した後にBが実行されるコマンドリストです。
  • A && Bでは、Aが成功した場合(終了ステータス0で終了した場合)にのみBが実行されます。
  • A || Bでは、Aが失敗した場合(終了ステータスが非ゼロで終了した場合)にのみBが実行されます。

コマンドリストの終了ステータスは、最後に実行されたコマンドの終了ステータスです。

&&と||演算子は連鎖でき、前のコマンドのステータスに基づいて次のコマンドが条件付きで実行されます。 たとえば、次のとおりです。

A && B && C && D || E
  • Aが成功した場合、Bが実行されます。
  • AとBが成功した場合、Cが実行されます。
  • AとBとCが成功した場合、Dが実行されます。
  • A、B、C、Dのいずれかが失敗した場合、Eが実行されます。
Caution

これらの論理演算子は控えめに使いましょう。 あっという間に読みにくいコードや、理解しにくいロジックにつながることがあります。

たとえば、次の2つは同じだと思うかもしれません。

if A; then B; else C; fi
A && B || C

違いは、Cがいつ実行されるかです。

  • 最初のコード(if文)では、CはAが失敗した場合にのみ実行されます。
  • 2つ目のコードでは、CはAが失敗した場合_、またはAが成功してBが失敗した場合_に実行されます!

コマンドリストの用途

コマンドリストでコードを簡潔にできる例をいくつか紹介します。

ファイルから行のブロックを読み取る

三角形に関するデータを含むデータファイルがあり、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")
Note

_行継続_とは、「バックスラッシュ」と「改行」という2文字の並び(\ + \n)です。 Bashがこの並びを見つけると、それは単にコードから削除され、それによって現在の行が次の行へ_継続_されます。 バックスラッシュと改行の間にスペースを入れないように注意してください。

もう1つの例です。

printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort

または

printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort
GitHubで編集 リンクは新しいウィンドウまたはタブで開きます