LinuxのBashで複数コマンドを一行に書く記号は、実行条件がそれぞれ異なります。A; B はAの成否にかかわらずBへ進み、A && B はAが成功したときだけB、A || B はAが失敗したときだけBを実行します。A | B はAの標準出力をBの標準入力へ接続するパイプラインで、順次実行の区切りではありません。括弧 ( ... ) はサブシェル、波括弧 { ...; } は現在のシェルでグループ化します。短く書けること自体は安全性や可読性を保証しないため、終了ステータスと副作用を基準に選びます。 確認ポイント:;、&&、||、pipelineは終了statusの伝播と後続実行条件がそれぞれ異なります。
一行の制御演算子を終了statusで選ぶ
false; printf "second ran\n"
printf "status=%s\n" "$?"
false は失敗を表すステータス1を返しますが、セミコロンの右側は実行されるため second ran が表示されます。直後の $? は最後に実行した printf のステータスなので、期待出力は status=0 です。falseの1を確認したいなら、間に別コマンドを挟まず false; rc=$?; printf ... と保存します。
false; rc=$?; printf "false status=%s\n" "$rc"
この出力は false status=1 です。セミコロンは「両方を必ず成功させる」記号ではなく、前の結果で分岐しない区切りです。前段が成果物を作れなかったのに後段がそれを配布するような処理には使わず、成功時だけ進める && を検討します。
&&は成功時だけ、||は失敗時だけ
printf "ready\n" && printf "next\n"
false && printf "not printed\n"
false || printf "fallback\n"
期待出力は ready、next、fallback の3行です。二つ目の右辺は実行されません。Bashでは0が成功、0以外が失敗です。画面にエラーらしい文が出たかではなく、各コマンドの終了ステータスで条件が決まります。コマンド固有の0以外の意味はmanページのEXIT STATUS節を確認します。
A && B || C を単純なif-elseとみなすのは危険です。Aが成功してもBが失敗すればCが実行されます。Bの失敗時にCを動かしたくないなら、複数行の if A; then B; else C; fi で意図を明示します。短い論理リストは、各右辺の実行条件を説明できる範囲に限定します。
パイプラインはデータを接続する
printf "%s\n" alpha beta gamma | grep -F beta
printf "pipeline status=%s\n" "$?"
printfの出力がgrepへ渡り、期待出力は beta と pipeline status=0 です。既定のBashでは、パイプライン全体のステータスは最後のコマンドのステータスです。そのため前段が失敗しても最後が成功すれば、全体が0に見える場合があります。パイプはコマンドを文字列として順番に並べる記号ではなく、標準入出力を接続して各プロセスを動作させます。
pipefailで途中の失敗を反映する
set -o pipefail
false | true
printf "with pipefail=%s\n" "$?"
set +o pipefail
期待出力は with pipefail=1 です。pipefail が有効な場合、すべて成功なら0、失敗があれば最も右側で失敗したコマンドのステータスがパイプラインへ反映されます。最後の set +o pipefail は、この例で変更した現在シェルのオプションを無効へ戻します。元から有効だった環境では無条件に戻すと状態を変えるため、実務スクリプトはサブシェル内に限定します。
( set -o pipefail; false | true; printf "pipeline=%s\n" "$?" )
printf "parent pipefail: "; set -o | grep "^pipefail"
括弧内の設定はサブシェルだけに適用され、親シェルのpipefail状態を変えません。Bashにはパイプライン各段の結果を保持する PIPESTATUS 配列もありますが、別のコマンドを実行すると更新されます。利用するときはパイプライン直後の一回の展開で値を保存します。POSIX shの機能ではないため、対象シェルをBashと明示します。
括弧はサブシェル、波括弧は現在のシェル
place=outer
( place=inner; printf "subshell=%s\n" "$place" )
printf "after subshell=%s\n" "$place"
期待出力は subshell=inner、after subshell=outer です。括弧内の変数代入や cd はサブシェル内に限定され、親シェルへ戻ると消えます。一時的にディレクトリを移動して処理する場合に、現在位置を手作業で戻さず隔離できます。
place=outer
{ place=inner; printf "group=%s\n" "$place"; }
printf "after group=%s\n" "$place"
波括弧グループは現在のシェルで実行されるので、期待出力の最後は after group=inner です。{ と最初のコマンドの間には空白が必要で、閉じる } の前にはセミコロンまたは改行が必要です。波括弧は予約語として解析され、括弧とは構文もスコープも違います。
グループ全体へリダイレクトする
{ printf "one\n"; printf "two\n"; } >./group-output.txt
printf "write status=%s\n" "$?"
wc -l <./group-output.txt
二つのprintfの標準出力を一つのファイルへ保存し、期待する行数は2です。この例は group-output.txt を新規作成または上書きする変更を伴います。既存ファイルを守るなら一意な出力名を先に決めます。不要なら内容と正確なパスを確認して、この例で作った一ファイルだけを削除します。
リダイレクトの位置にも意味があります。time cmd >out、{ A; B; } >out、A | B >out では対象になる出力が異なります。標準エラーは > だけではファイルへ入りません。エラーまで同じファイルへ混ぜると後で機械処理しにくいため、必要なら 2>error.log のように別経路にします。
引用で引数の境界を守る
file="Q2 final.txt"
printf "path=<%s>\n" "$file"
printf "literal=%s\n" '$file'
二重引用符の "$file" は変数展開を行いながら一つの引数を保ち、期待出力は path=<Q2 final.txt> です。単一引用符で囲んだ変数名は展開せず、文字どおり $file を表示します。引用なしの変数展開は空白で複数語に分割され、さらにワイルドカード展開を受ける可能性があります。
セミコロン、アンパサンド、縦線、括弧、波括弧はシェルの制御演算子や予約語です。ファイル名や検索文字列として渡すなら引用します。外部入力を一行のコマンド文字列へ連結して eval する方法は、入力を別のシェル構文として再解釈させるため避け、配列でコマンドと引数を分けます。
終了ステータスを保存して判断する
grep -F -- "needle" ./sample.txt
grep_rc=$?
printf "grep status=%s\n" "$grep_rc"
GNU grepでは通常、0は一致あり、1は一致なし、2はエラーです。単に0以外をすべて同じ失敗として || の右辺へ送ると、「見つからない」と「ファイルを読めない」を混同します。case $grep_rc in 0) ...;; 1) ...;; *) ...;; esac のように、コマンド固有のステータスを分ける方が実行条件を正確に表せます。
! をパイプラインの前に置くと論理的な成否が反転しますが、元の終了値そのものを保持する用途ではありません。障害調査で元値が必要なら、反転や論理リストへ渡す前に変数へ保存します。画面出力が空でも成功するコマンド、結果を見つけられず1を返す検索コマンドなどがあるため、各コマンドの公式な終了値を読みます。
一行化するかの判断
対話操作でディレクトリ作成後に移動する程度でも、失敗時の条件を理解して記号を選びます。デプロイ、権限変更、削除、長いパイプラインは、複数行で変数名、エラーチェック、ログを明記した方がレビューしやすくなります。一行だから原子的に実行されるわけではなく、途中まで完了して後段だけ失敗することもあります。
- 前段に依存しないなら
;。 - 前段成功時だけ進むなら
&&。 - 前段失敗時の代替だけなら
||。 - 標準出力を次の標準入力へ渡すなら
|。 - 変数や現在位置の変更を隔離するなら
( ... )。 - 現在シェルでまとめてリダイレクトするなら
{ ...; }。
group出力とshell optionを元へ戻す
false、true、printf、変数代入だけの例は永続的な変更をしません。set -o pipefail は現在シェルの動作を変えるため、例では set +o pipefail で戻すかサブシェルへ限定します。リダイレクト例だけはファイルを作成・上書きするため、既存ファイルでは試さず、新規の検証用パスを使い、確認後にその一件だけを片付けます。
制御演算子を選ぶ最終確認
- 各記号の右辺が実行される条件を説明できる。
$?を別コマンドで上書きする前に保存した。- パイプ途中の失敗が重要ならpipefailまたはPIPESTATUSを使った。
- 括弧と波括弧で変数・cdのスコープが違うことを検証した。
- 引数を引用し、短い一行を原子的・本質的に安全とは扱っていない。

コメント