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

標準入出力・パイプ・終了コード

出力データと終了判定を別々に扱い、処理のつなぎ方を確かめます。

説明

コマンドには標準入力・標準出力・標準エラーの三つの基本的な入出力先があります。出力の内容とは別に、終了コードで成功や失敗を後続処理へ伝えます。

データを出しながら未完了の終了コードを返すスクリプトを使います。データと警告を分離して集計し、パイプの途中の失敗、条件付き実行、リダイレクトの順序、上書きと追記を同じファイルで確認します。

1標準出力と標準エラーを持つ処理

producer.shは標準出力へ3行、標準エラーへ警告を1行出し、終了コード3を返します。先にソースを読みます。

cat producer.sh出力先と終了値を確認

通常のprintfは標準出力へ書き、>&2付きは標準エラーへ書きます。exit 3はこのスクリプトで未完了を示すために決めた値です。0が成功、0以外が失敗を示す慣習はありますが、各数値の意味はコマンドごとに確認します。

2二つの出力を別々に保存

標準出力をdata.txt、標準エラーをerror.txtに保存し、直後の終了コードを表示します。

sh producer.sh > data.txt標準出力を保存 2> error.txt標準エラーを保存; printf 'status=%s\n' "$?"直前の終了コード; cat data.txt; cat error.txt保存内容を読む

終了コードは3です。データ3行と警告1行は別ファイルに残ります。$?は直前のコマンドの終了コードで、別のコマンドを実行すると値が更新されます。結果が出力されていても、処理が成功したとは限りません。

3標準入力からデータを受け取る

data.txtを標準入力へ接続し、重複する行の出現回数を数えます。

sort < data.txtファイルを標準入力へ | uniq -c隣り合う同一行を数える

sortが行を並べ替え、その標準出力をuniqの標準入力へ渡します。uniqは隣接する同じ行をまとめるため、先に並べ替えています。パイプを流れるのはファイル名ではなくデータのバイト列です。

4最後の成功で上流の失敗が隠れる

保存を挟まずproducerから直接パイプでつなぎます。Bashのpipefailは無効にして、既定の終了コードを調べます。

set +o pipefail既定の終了判定; sh producer.sh 2> error.txt | sort | uniq -c警告を分離して集計; printf 'pipeline=%s\n' "$?"パイプ全体の終了コード

集計は表示され、パイプラインの終了コードは0です。既定では最後のuniqの終了コードが使われるため、producerの3は全体の終了コードに現れません。

5各処理の終了コードを取り出す

同じパイプをもう一度実行し、BashのPIPESTATUSを直後に表示します。

sh producer.sh 2> error.txt | sort | uniq -c同じ処理を実行; printf 'stages=%s\n' "${PIPESTATUS[*]}"各段階の終了コード

3 0 0の順で、producer、sort、uniqの終了コードが並びます。PIPESTATUSも次のコマンドで更新されるため、必要なら直後に変数へ保存します。どの段階が失敗したかを個別に確認できます。

6pipefailで上流の失敗を反映

pipefailを有効にして再実行します。

set -o pipefail途中の失敗も反映; sh producer.sh 2> error.txt | sort | uniq -c同じパイプ; printf 'pipeline=%s\n' "$?"変更後の終了判定

今度は3です。pipefailでは右側から見て最後に現れる非0の終了コードを使い、すべて0なら0になります。出力が同じでも、終了判定を変えると後続処理を実行する条件が変わります。

7失敗したときだけ後続処理を実行

終了コードで保存完了の扱いを変えます。producerが失敗した場合には、成功メッセージを出さず再確認のメッセージを出します。

sh producer.sh > data.txt 2> error.txt出力と終了コードを分離 && printf 'accepted\n'成功時のみ || printf 'needs review\n'非0のとき

needs reviewだけが表示されます。&&の右側は直前が0の場合、||の右側は直前が非0の場合に実行されます。一般にA && B || Cはif文と完全に同じではなく、Aが成功してもBが失敗すればCが実行されます。

8リダイレクトの指定順

標準出力をファイルにしてから標準エラーを同じ先へ複製します。その後、順序を逆にして比較します。

sh producer.sh > both.txt 2>&1両方をファイルへ; cat both.txt4行を確認; sh producer.sh 2>&1 > only-data.txt複製後に1番だけ変更; cat only-data.txtデータだけ確認

最初は4行ともboth.txtへ入ります。後半は警告が直接表示され、only-data.txtにはデータ3行だけが入ります。2>&1はその時点の1番の行き先を2番へ複製する指定です。後から1番の行き先を変更しても、2番が自動追従するわけではありません。

9上書きと追記の違い

集計結果をファイルへ出し、追記を一回行った後、上書きでもう一度出します。

sort data.txt | uniq -c > summary.txt集計を書き込む; sort data.txt | uniq -c >> summary.txt末尾に追加; cat summary.txt二組を確認; sort data.txt | uniq -c > summary.txt内容を置き換える; cat summary.txt一組を確認

追記後は同じ集計が二組、上書き後は一組です。>はコマンドが動く前に既存ファイルを切り詰めます。そのため入力と出力に同じファイルを指定すると、元の内容を読む前に失う場合があります。更新時は別ファイルに書き、成功を確認して置き換えます。

まとめ

パイプは標準出力を次の標準入力へ接続し、標準エラーは別に扱います。

パイプの出力と成功判定は別です。pipefailとPIPESTATUSで途中の失敗を確認できます。

リダイレクトの順序と上書き・追記の指定によって、残る内容が変わります。

操作履歴と解説を振り返れます。

Bash / シェル展開と入出力

1
cat producer.sh
#!/bin/sh
printf 'apple\nbanana\napple\n'
printf 'warning: sample is incomplete\n' >&2
exit 3
2
sh producer.sh > data.txt 2> error.txt; printf 'status=%s\n' "$?"; cat data.txt; cat error.txt
status=3
apple
banana
apple
warning: sample is incomplete
3
sort < data.txt | uniq -c
   2 apple
   1 banana
4
set +o pipefail; sh producer.sh 2> error.txt | sort | uniq -c; printf 'pipeline=%s\n' "$?"
   2 apple
   1 banana
pipeline=0
5
sh producer.sh 2> error.txt | sort | uniq -c; printf 'stages=%s\n' "${PIPESTATUS[*]}"
   2 apple
   1 banana
stages=3 0 0
6
set -o pipefail; sh producer.sh 2> error.txt | sort | uniq -c; printf 'pipeline=%s\n' "$?"
   2 apple
   1 banana
pipeline=3
7
sh producer.sh > data.txt 2> error.txt && printf 'accepted\n' || printf 'needs review\n'
needs review
8
sh producer.sh > both.txt 2>&1; cat both.txt; sh producer.sh 2>&1 > only-data.txt; cat only-data.txt
apple
banana
apple
warning: sample is incomplete
warning: sample is incomplete
apple
banana
apple
9
sort data.txt | uniq -c > summary.txt; sort data.txt | uniq -c >> summary.txt; cat summary.txt; sort data.txt | uniq -c > summary.txt; cat summary.txt
   2 apple
   1 banana
   2 apple
   1 banana
   2 apple
   1 banana

演習完了