nob@lit-forge

Shell パイプ & リダイレクト 早見表

標準入出力・エラー出力のリダイレクト、パイプによるコマンド連結、ヒアドキュメント・ヒアストリング、プロセス置換(<(cmd))など、Bash / Zsh のリダイレクト機能を一枚で理解できる早見表。

#shell#パイプ#リダイレクト#Bash

ファイルデスクリプタの基本

  • fd 0 = 標準入力(stdin
  • fd 1 = 標準出力(stdout
  • fd 2 = 標準エラー出力(stderr
  • プロセスは常に 3 本の入出力チャネルを持っており、リダイレクトで差し替える

基本のリダイレクト

cmd > out.txt          # stdout を上書き
cmd >> out.txt         # stdout を追記
cmd 2> err.txt         # stderr のみ別ファイルへ
cmd > out.txt 2>&1     # stdout と stderr を同じファイルへ
cmd &> out.txt         # 同上(bash 拡張)
cmd > /dev/null        # stdout を捨てる
cmd 2>/dev/null        # stderr を捨てる
cmd &>/dev/null        # 両方捨てる
cmd < in.txt           # stdin をファイルから供給
cmd < in.txt > out.txt # 入力 + 出力

パイプ(コマンド連結)

cmd1 | cmd2                   # cmd1 の stdout → cmd2 の stdin
cmd1 |& cmd2                  # stderr も流す(bash 拡張、= 2>&1 |)
cmd1 | cmd2 | cmd3            # 多段パイプ
cmd | tee out.txt             # 分岐(画面 + ファイル)
cmd | tee -a log.txt          # 追記で分岐
cmd | tee file1 file2         # 複数ファイルへ分岐

注意: パイプの各要素はサブシェルで動くので、while read 内で代入した変数は外で使えない。解決には shopt -s lastpipe(bash)か、プロセス置換で回避。

ヒアドキュメント・ヒアストリング

cat <<EOF                      # ヒアドキュメント(展開あり)
hello $USER
path is $PWD
EOF

cat <<'EOF'                    # '...' で展開を抑制
$HOME は展開されずリテラル表示
EOF

cat <<-EOF                     # -EOF で行頭タブ除去(ネスト向け)
    インデント無視される
EOF

# ヒアストリング(1 行だけ供給)
grep "^root" <<< "$(cat /etc/passwd)"
wc -c <<< "hello"              # 6(改行含む)

プロセス置換

# <(cmd) は「cmd の出力を読み込むための疑似ファイル」
diff <(ls dir1) <(ls dir2)     # 2 ディレクトリの一覧を diff

# >(cmd) は「cmd の入力を書き込むための疑似ファイル」
tee >(gzip > out.gz) >(wc -c > size) < in.txt

# while read のサブシェル問題の回避
sum=0
while read n; do sum=$((sum+n)); done < <(seq 1 10)
echo "$sum"                    # 55

xargs と複数行処理

ls *.txt | xargs rm            # 危険: ファイル名に空白・改行があると壊れる
find . -name "*.txt" -print0 | xargs -0 rm    # NUL 区切りで安全
find . -name "*.jpg" -print0 | xargs -0 -n1 -P4 mogrify -resize 50%   # 並列 4

# -I {} で位置指定
ls | xargs -I {} cp {} backup/{}

# 対話確認(-p)
find . -name "*.tmp" | xargs -p rm

データ分岐と集約

# tee で分岐して sha256 と wc を並行計算
cat file | tee >(sha256sum > hash.txt) | wc -l > lines.txt

# 2 本の入力をマージ(paste)
paste file1 file2

# 1 行ずつ区切ってベクトル化(awk のトリック)
seq 1 10 | paste -sd, -   # "1,2,3,4,5,6,7,8,9,10"

# 複数コマンドをまとめてリダイレクト
{ echo "=== start ==="; date; ls; echo "=== end ==="; } > report.txt

# サブシェル ( ... ) は環境を分離(cd しても元に戻る)
(cd /tmp && ls)
pwd   # 元のディレクトリのまま

終了コードとエラー伝播

cmd1 && cmd2               # cmd1 成功時のみ cmd2
cmd1 || cmd2               # cmd1 失敗時のみ cmd2(フォールバック)
cmd1 ; cmd2                # 成否に関わらず実行
! cmd                      # 終了コード反転

# パイプ中の失敗を検知(bash では最後のコマンドの終了コードだけ見える)
set -o pipefail            # パイプ途中の失敗も捕まえる
cmd1 | cmd2 | cmd3         # どれかが失敗したら $? は非ゼロ

# スクリプトで安全策
set -euo pipefail          # -e 失敗で即終了 / -u 未定義変数 / -o pipefail
trap 'echo "error at $LINENO"' ERR

よくある落とし穴

  • cmd > out.txt 2>&1 の順序が重要: 2>&1 > out.txt は stderr がまだ端末を指しているので bug る
  • echo -e は非ポータブル: printf "%s\n" を使う方が一貫
  • パイプ内の変数代入が効かない: foo | while read x; do BAR=$x; done; echo $BAR は空になる。shopt -s lastpipe か、プロセス置換 < <(foo) で回避
  • cat file | grep foo は無駄: grep foo file で OK(UUOC = Useless Use Of Cat)
  • 長い行は行継続 \\ で折る: 行末の \\ の後ろに空白を入れない