1-5. パイプとリダイレクト - stdin/stdout/stderr, |, >, xargs
所要時間: 25-50分(がっつりなら2セッション分) ゴール: 標準入出力の概念を説明できる。
>>>2>&>|xargsを意図して使い分けられる コミット内容: 今日の操作履歴を~/log/linux_day05.logに保存
この章が終わるとできること
stdin/stdout/stderrの3つを説明できる> / >> / < / 2> / 2>&1 / &>を 意図して 使い分けられる/dev/nullの使い所(エラー破棄)を即答できるcmd1 | cmd2 | cmd3の パイプライン が頭の中で組み立てられるteexargsで「画面 + ファイル」「結果を別コマンドへ」が書ける- ヒアドキュメント (
<<EOF) で複数行ファイルを生成できる
Day 1-4 とのつながり
- Day 4 でちらほら出てきた
|の正体に、今日ついに踏み込む - Day 4 の
grepawksortuniqは 「パイプで繋ぐ前提」 で設計されている - Day 1 で見た
echo "x" > file.txtの>の正体も今日明らかになる
これができると何が嬉しいか
... | tee -a deploy.logで「進捗を画面に出しつつログに残す」が書けるfind ... | xargs rmで「条件で見つけた100ファイルを一括削除」が書ける- Day 6 以降の 「コマンドを組み合わせて自動化する」 ためのベース技能が揃う
大前提: Unix哲学とパイプの思想
Unix哲学のキーフレーズ:
“Write programs that do one thing and do it well. Write programs to work together.” (1つのことを上手くやるプログラムを書け。プログラム同士が協調するように書け)
Day4 で見たように、Unix の道具は grep(検索だけ)、sort(並べ替えだけ)、uniq(重複処理だけ)と、1つの仕事に特化している。一見不便に見えるが、**「組み合わせる仕組み」**があるからこそ、無数の処理を作れる。
その「組み合わせる仕組み」こそがパイプとリダイレクト。
- リダイレクト (
>,<,2>) : コマンドの入出力先を「ファイル」に切り替える - パイプ (
|) : あるコマンドの出力を別のコマンドの入力にする
この2つを使いこなせると、**シェルが「巨大なデータ処理パイプライン」**になる。バックエンドエンジニアの作業効率を一段引き上げる、最重要スキル。
セッション①: 標準入出力とリダイレクト(25-30分)
0. 録画スタート&作業ディレクトリ
mkdir -p ~/log ~/learn/linux/day05
cd ~/learn/linux/day05
script ~/log/linux_day05.log
# 演習で使うサンプルログを生成(Day 4 と同内容、章を自己完結させるため)
cat > access.log <<'EOF'
2026-05-14 10:00:01 192.168.1.10 GET /api/users 200
2026-05-14 10:00:02 192.168.1.11 POST /api/login 200
2026-05-14 10:00:03 192.168.1.10 GET /api/users 200
2026-05-14 10:00:05 10.0.0.5 GET /api/products 404
2026-05-14 10:00:08 192.168.1.10 GET /api/orders 500
2026-05-14 10:00:10 10.0.0.5 GET /api/products 404
2026-05-14 10:00:12 192.168.1.12 POST /api/login 401
2026-05-14 10:00:15 192.168.1.10 GET /api/users 200
2026-05-14 10:00:20 10.0.0.5 GET /api/products 404
2026-05-14 10:00:25 192.168.1.13 DELETE /api/orders/1 500
2026-05-14 10:00:30 192.168.1.99 GET /health 200
2026-05-14 10:00:30 192.168.1.99 GET /health 200
2026-05-14 10:00:30 192.168.1.99 GET /health 200
2026-05-14 10:00:31 192.168.1.99 GET /health 200
2026-05-14 10:00:31 192.168.1.99 GET /health 200
EOF1. リダイレクトとは何か
普段、コマンドを実行すると結果は**画面(ターミナル)**に出る。これがデフォルトの「出力先」。
リダイレクトとは、この出力先を画面以外(ファイルなど)に切り替える操作のこと。記号は > >> < 2> &> などいろいろあるが、やっていることはどれも「入出力の行き先を変える」だけ。
なぜリダイレクトが必要か:
- ログを保存したい: 長時間動くスクリプトの結果を、後で見返せるようにファイルへ
- エラーを抑制したい: 大量の権限エラーが画面を埋め尽くすのを防ぐ
- 設定ファイルを生成したい: コマンドで動的に作った内容をファイルに書き出す
- ファイルを入力にしたい: コマンドに直接タイプする代わりに、ファイルの中身を流し込む
これからリダイレクト記号を1つずつ、「説明 → いつ使うか → 具体例」 の順に紹介していく。最後に全体まとめ表を置くので、まずは一個ずつ手を動かしながら腹落ちさせるのがコツ。
2. > - 標準出力をファイルへ(上書き)
> はコマンドの結果(標準出力)をファイルに書き出すリダイレクト。日常的な「→(こっちに送る)」と同じ感覚。既存ファイルがあれば中身は問答無用で上書きされる。
いつ使うか
- コマンドの結果を保存したい:
ls -la > filelist.txtでファイル一覧をスナップショット- 設定ファイルを新規生成したい:
echo "port=8080" > config.iniで1行設定を作る- 空ファイルを作りたい:
> empty.txtだけで0バイトのファイルを作成- 前回の結果を上書きしたい: 毎回最新だけ残したいレポート出力など
# 普通: 結果は画面に出る
echo "hello"
# リダイレクト: 結果はファイルへ(画面には何も出ない)
echo "hello" > out.txt
cat out.txt # → "hello"
# 上書きされることを確認
echo "1行目" > out.txt
echo "2行目" > out.txt # 上書き、1行目は消える
cat out.txt # → 「2行目」だけが残る3. >> - 標準出力をファイルへ(追記)
>> は > とほぼ同じだが、既存ファイルの末尾に追記する点が違う。中身は消えない。
いつ使うか
- ログを蓄積したい:
echo "$(date) deployed" >> deploy.logで毎回末尾に履歴を残す($(...)は「中のコマンドを実行してその出力をここに埋め込む」シェル記法=コマンド置換。$(date)はThu May 21 14:30:00 JST 2026のような現在日時文字列に置き換わる。詳細は Linux 2-2 で扱う)- コマンド履歴を集めたい: 複数コマンドの結果を1つのファイルに順次溜める
- 既存設定に1行足したい:
echo "alias ll='ls -la'" >> ~/.zshrcで末尾に追加- 既存ファイルを壊したくない: 上書きで事故るリスクを避けたい時のデフォルト選択
# 上書き vs 追記の違いを目で見る
echo "1行目" > out.txt
echo "2行目" >> out.txt # 追記、両方残る
cat out.txt # → 「1行目」と「2行目」
# 日時付きで追記していく定番パターン
# ↓ $(date) は前述のコマンド置換: date コマンドの出力(現在日時)が文字列としてここに埋め込まれる
echo "$(date) - 作業開始" >> work.log
echo "$(date) - 作業終了" >> work.log
cat work.log4. 「画面に出る文字」は実は2種類ある(stdout と stderr)
ここまで使ってきた > >> は「コマンドの結果」をファイルに送る記号だった。でも実は、「コマンドの結果」と一言で言っても、内部には2つの別チャンネルがある。これを知ると、残りのリダイレクト記号がスッと理解できる。
次のコマンドを試してみる:
ls /etc /nonexistent_dir画面にはこんな風に出る:
ls: /nonexistent_dir: No such file or directory ← エラーメッセージ
/etc: ← 正常な結果
<...フォルダ一覧...>
人間の目では「全部画面の文字」に見えるけど、OS の中では別々のチャンネル で流れている:
| 種類 | 略称 | 番号 | 説明 |
|---|---|---|---|
| 正常な結果 | stdout | 1 | 「ちゃんと処理できた結果」 |
| エラー出力 | stderr | 2 | 「失敗・警告・進捗メッセージ」 |
これらは OS 内部にある2本の独立したチャンネル。デフォルトでは両方とも画面に流れているから、混ざって見えるだけ。
なぜ2本に分けてあるか:
- stdout = 「次のコマンドに渡したい有用なデータ」
- stderr = 「人間に知らせたいけど、次のコマンドには流したくないメッセージ」
例えば「正常な結果だけファイルに保存、エラーは画面で見たい」が普通にやれる:
# 1番のチャンネル(stdout)だけファイルに送る
ls /etc /nonexistent_dir > result.txt
# result.txt には /etc の内容だけ入っている
cat result.txt
# エラーメッセージは実行時点で画面に出たまま、ファイルには入っていないこれが Unix の 「2チャンネルを別々に扱える」 という強み。
ファイルディスクリプタ(fd)という概念
Unix では「ファイル」「キーボード」「画面」「ネットワーク」など、入出力できるものはすべて「ファイル」として抽象化される。プロセスはそれぞれ番号(ファイルディスクリプタ)でアクセスする。
番号 名前 デフォルトの接続先 0 stdin(標準入力) キーボード 1 stdout(標準出力) 画面 2 stderr(標準エラー出力) 画面 3以降 自分でファイルを開いたら割り当てられる ユーザー指定 リダイレクト記号は、この番号を使って「どのチャンネルをどこへ送るか」を指定する仕組み。
5. 2> - 標準エラー出力をファイルへ
2> は stderr(2番チャンネル)をファイルに送るリダイレクト。> の前に 2 を付けるだけ。「何も書かない > は実は 1> の省略形」だと知ると、2> は自然に読める。
いつ使うか
- エラーログだけ別ファイルに保存したい: バッチ処理で「正常結果」と「エラー」を分けて記録
- エラーだけ捨てたい:
find / -name "*.log" 2> /dev/nullで大量の権限エラーを抑制- 失敗時の原因調査をしやすくしたい: stderr を独立したファイルに溜めておけば、後で grep しやすい
- stdout と stderr を別々に処理したい: 正常結果をパイプで次に流しつつ、エラーはファイルへ
# stderr だけ別ファイルに
ls /nonexistent_dir 2> err.txt
cat err.txt # → エラーメッセージ
# stdout と stderr を別々のファイルに
ls /etc /nonexistent_dir > stdout.txt 2> stderr.txt
cat stdout.txt # → 正常な結果だけ
cat stderr.txt # → エラーだけ
# 追記版: `2>>`
ls /nonexistent_dir 2>> err.txt
ls /another_missing 2>> err.txt
cat err.txt # → 2回分のエラーが追記されている6. 2>&1 と &> - stdout と stderr の両方を同じ場所へ
「stdout も stderr も両方まとめてファイルに保存したい」場面はめちゃくちゃ多い(デプロイログ、CI出力など)。これを書く方法が2つある。
2>&1 を分解すると: 「チャンネル2を、チャンネル1が今向いている場所と同じ場所へ送る」。
ここで重要なのが & の意味: 「この後ろはファイル名じゃなく、チャンネル番号です」とシェルに教える印。これがないと 2>1 は「stderr を 1 という名前のファイルへ送る」と解釈されてしまう(1 という名のファイルが作られる)。
&> は新しい書き方で、「stdout と stderr の両方をこのファイルへ」を1記号で表現できる。読みやすく、順序ミスも起きないので現代では &> が推奨。
いつ使うか
- デプロイ・ビルドの全出力をログに集約:
./deploy.sh &> deploy.log- 長時間タスクを背景実行してログだけ残す:
./batch.sh &> batch.log &- CI/CD で完全な実行記録を残したい: 成功も失敗も同じファイルに時系列で並ぶ
- 「成否を気にせず全部捨てたい」時:
command &> /dev/null
# 古い書き方: 2>&1
ls /etc /nonexistent_dir > all.txt 2>&1
cat all.txt # → 正常結果もエラーも両方入っている
# 新しい書き方(推奨): &>
ls /etc /nonexistent_dir &> all.txt
cat all.txt # → 同じ結果
# 追記版
ls /etc /nonexistent_dir &>> all.txt
2>&1の「書く順序」が大事command > file 2>&1 # OK: 「[1]をfileへ」→「[2]を[1]の今の場所(=file)へ」=両方fileへ command 2>&1 > file # NG: 「[2]を[1]の今の場所(=画面)へ」→「[1]をfileへ」= stderrは画面に残るリダイレクトは 左から右に順番に処理 されるので、書く順序を逆にすると意図と違う動きになる。混乱の元なので、現代では
&>の方が推奨される。
7. < - ファイルを標準入力として読む
ここまで見てきた > >> 2> &> は全部、コマンドの「出力」をファイルに送る 方向だった。
< だけ矢印の向きが逆で、ファイルの「中身」をコマンドの入力にする リダイレクトになる。
> コマンド ───→ ファイル (出力を書き出す)
< ファイル ───→ コマンド (入力として読み込む)
まず動かしてみる
# wc -l access.log と wc -l < access.log は、どちらも「access.log の行数を数える」
wc -l access.log
# → 15 access.log ← ファイル名が一緒に表示される
wc -l < access.log
# → 15 ← 行数だけ結果はほぼ同じだけど、< を使った方は「ファイル名」が出力に出てこない。これは:
- 引数で渡す (
wc -l access.log) → コマンドは「access.log というファイルを見ている」ことを知っている <で流し込む (wc -l < access.log) → コマンドは「どこからかデータが流れてきた」としか認識しない(中身だけ来る、名前は来ない)
「< がないと困る」場面
多くのコマンドは引数でファイル名を受け取れる(wc, cat, grep などここまで習ったコマンドのほとんど)ので、< を使わなくても困らないことが多い。ただし以下のような 引数でファイルを取らないコマンド には < が必須:
# bc は「計算式を stdin から読み取って結果を返す電卓コマンド」
# 引数でファイル名を受け取らないので < で流し込む必要がある
echo "100 + 200" > calc.txt
echo "300 * 4" >> calc.txt
bc < calc.txt
# → 300
# 1200
rm calc.txtbc calc.txt と書いても動かない(bc はファイル名引数を受け付けない)。こういう時に < の出番。
いつ
<を使うか
- 引数でファイル名を取れないコマンドに食わせたい時:
bc(電卓) など- DB に SQL を流し込みたい時:
mysql -u root mydb < schema.sqlで SQL ファイルを一括実行- シェルスクリプトでファイル内容を「行ごとに処理」したい時:
while read line; do ...; done < file.txt(while ループは 2-3 制御構文 で扱う)cat file | cmdを1プロセス減らしたい時:cmd < fileの方がcatプロセスを起動しないぶん効率的(“Useless Use of Cat” として有名)
「< 使うべき or 引数でいい?」の判断
迷ったら 「引数」を優先 でOK。引数で受け取れるコマンドなら、その方が読みやすく、出力にもファイル名が出る。
< を使うのは:
- 引数を取らないコマンドに食わせる時(
bcのような) < fileの出力にファイル名を入れたくない時(スクリプトの整形等)while readのような シェルの構文 で必須な時
これくらいの感覚でOK。
8. リダイレクト記号の全体まとめ表
ここまで紹介した記号は、すべて3パーツの組み合わせとして読める。これを覚えると新しい記号が出てきても怖くない。
[どのチャンネル] [向き] [先]
2 > err.txt
- どのチャンネル: 何も書かなければ
1(stdout)、2と書けば stderr、&で「両方」 - 向き:
>上書き />>追記 /<入力 - 先: ファイル名、または
&1(1番が今向いてる場所) /&2(2番が今向いてる場所)
| 記号 | パーツ分解 | 意味 |
|---|---|---|
> file | [1] > file | stdout をファイルへ(上書き) |
>> file | [1] >> file | stdout を追記 |
2> file | [2] > file | stderr をファイルへ(上書き) |
2>> file | [2] >> file | stderr を追記 |
2>&1 | [2] > &1 | stderr を、1番が今向いてる場所へ |
&> file | [1+2] > file | stdout と stderr の両方をファイルへ |
&>> file | [1+2] >> file | stdout と stderr の両方を追記 |
< file | stdin < file | ファイルを stdin として読む |
<< EOF | stdin << EOF | ヒアドキュメント(次節で扱う) |
リダイレクトの実務ユースケース総まとめ
- ログを保存:
./long_running_task.sh &> task.log- エラーだけ捨てる:
find / -name "*.log" 2>/dev/null- 進捗を画面に出しつつログにも残す:
teeを使う(セッション② で扱う)- デプロイ結果を日付付きで記録:
./deploy.sh &> deploy_$(date +%Y%m%d).log
>で大事故が起きる
- 既存ファイルが問答無用で上書きされる:
cat report.txt > report.txtのような自己参照は 空ファイルになる(シェルが先にreport.txtを空にしてから cat する)>のスペースを間違えがち:echo "x">fileでも動くが、echo "x" > fileの方が安全- noclobber オプションで防御:
set -o noclobberを.zshrcに書くと、既存ファイルへの>はエラーになる。強制上書きは>|(set -o <名前>は bash/zsh の動作モードを切り替えるシェル組込みコマンド。詳細は Linux 2-2 で扱う。ここでは「.zshrcに1行足せば上書き事故を防げる安全装置がある」という発想だけ覚えればOK)
9. /dev/null - 出力の捨て場
/dev/null は 「Unix のゴミ箱」 とも呼ばれる特殊なファイル。書き込んだものはすべて消える、読み出しても何も出ない。
「不要な出力を捨てたい」「進捗メッセージを黙らせたい」時に使う。
# 出力を全部捨てる
command > /dev/null
# エラーだけ捨てる(正常な結果は画面に残る)
find / -name "*.log" 2> /dev/null
# 全部捨てる(成功・失敗を気にせず実行したい時)
command > /dev/null 2>&1
command &> /dev/null
# /dev/null から読み出すと「空」が返る
cat /dev/null # 何も出ない
/dev/nullの使い所
- 不要なエラーメッセージを消す:
find / 2>/dev/nullで権限エラーを抑制- コマンドの成否だけ知りたい時:
if curl -s example.com > /dev/null; then echo "OK"; fi(if <コマンド>; then ...; fiは「コマンドが成功(終了ステータス0)なら then 以降を実行」する bash の制御構文。詳細は Linux 2-3 で扱う。ここでは「curl が成功したら OK と表示」のイメージだけ持てればOK)- 空ファイルを作る:
> file.txtまたはcat /dev/null > file.txt- 進捗表示の煩わしいコマンドを静かにする:
wget URL -q > /dev/null 2>&1
似たデバイスファイル
/dev/null: 書いたら消える、読んだら空/dev/zero: 読むと永遠に 0x00 が返る(ファイル埋め立て用:dd if=/dev/zero of=test bs=1M count=100で100MBの空ファイル)/dev/random//dev/urandom: 乱数の源(パスワード生成などに)
10. ヒアドキュメント << - 長文をコマンドに流し込む
<< は 「長文の文字列を、コマンドの stdin に流し込む」 リダイレクト。<<EOF から EOF までが文字列としてコマンドに渡される。設定ファイル生成や SQL 実行で頻出。
# 複数行の文字列を一気にコマンドに渡す
cat <<EOF
1行目
2行目
3行目
EOF
# 変数展開も働く
NAME="takato"
cat <<EOF > greeting.txt
Hello, $NAME!
Today is $(date).
EOF
cat greeting.txt
# 変数展開を止めたい時はクォート('EOF' のようにシングル)
cat <<'EOF' > script.sh
echo "$HOME" # ← これがそのまま出る(展開されない)
EOF
cat script.shヒアドキュメントの2つのモード
「EOF」は単なる目印(区切り文字)で、好きな名前に変えられる:
<<END,<<__SCRIPT__など。
<<EOF(クォートなし): 内部で$VARや$(...)が展開される<<'EOF'(クォート付き): 何も展開されず、そのまま渡される使い分け: 動的な値を埋め込みたいならクォートなし、スクリプトのテンプレートをそのまま渡したいならクォート付き。
ヒアドキュメントの実務ユースケース
- 設定ファイルを生成:
cat > /etc/nginx/sites-available/myapp <<EOF ... EOF- SQLを直接叩く:
mysql -u root <<EOF \n SELECT * FROM users; \n EOF- 複数行のメッセージ送信:
mail -s "Report" admin@example.com <<EOF ... EOF- Dockerfile内でmultilineを書く:
RUN cat > /etc/config <<EOF ... EOF
ヒアドキュメントの落とし穴
- 終端のEOFは行頭から書く: スペース・タブを入れるとエラー。
<<-EOF(ハイフン付き)にすればタブインデントは無視される$を含むスクリプトを書くなら必ずクォート: クォートなしだと展開されて意図しないものに置き換わる<<<(ヒアストリング)と混同しない:<<<は単一文字列を渡す:wc -l <<< "hello"→1
セッション②: パイプ・tee・xargs(25-30分)
11. パイプ | の本質
| は 「左のコマンドの stdout を、右のコマンドの stdin に直接繋ぐ」 仕組み。ファイルを経由せず、コマンド同士を直結する。Unix で最も使われる記号の1つ。
# 「pythonプロセスが何個動いているか」
ps aux | grep python | grep -v grep | wc -l
# 「ファイル数の多い順にディレクトリを並べる」
find . -type f | awk -F'/' '{print $2}' | sort | uniq -c | sort -rn | head
# 「コマンド履歴の頻度ランキング」
history | awk '{print $2}' | sort | uniq -c | sort -rn | headパイプの仕組み
A | Bは「Aの標準出力を、Bの標準入力に繋ぐ」。┌─ stdout ─┐ ┌─ stdin ──┐ [A] -│ │ pipe → │ │- [B] └──────────┘ └───────────┘重要な性質:
- AとBは同時に動く(並列実行)。Aが全部終わってからBが動くのではない
- Aの出力が大きくても、メモリに全部溜め込まずストリーミングで処理される
- 何個でも繋げる:
A | B | C | D | ...これにより、巨大ファイル(数GB)でも、メモリを使わずに集計できる。
リダイレクトとの違い
- リダイレクト (
>,<,2>) : コマンドの入出力先を ファイル に切り替える- パイプ (
|) : コマンドの出力を 次のコマンド に直結するどちらも「コマンドの入出力先を変える」点では同じ仲間だが、相手が ファイルかコマンドか が違う。
パイプの実務ユースケース
- ログ集計:
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head- プロセス検索:
ps aux | grep nginx- ネットワーク監視:
netstat -an | grep LISTEN- ディスク使用量ランキング:
du -sh */ | sort -rh | head
パイプの落とし穴
- エラーは流れない(stdout だけ):
A | Bの|は stdout のみ繋ぐ。stderr は画面に出る。両方繋ぎたければA 2>&1 | BまたはA |& B- パイプの終了ステータス:
A | Bの終了ステータスはデフォルトでBのもの。Aの成否を知りたければset -o pipefailまたは${PIPESTATUS[0]}を使う(set -o pipefailは「パイプの途中でコケたら全体を失敗扱いにする」bash のオプション、${PIPESTATUS[N]}はパイプ各段の終了ステータスを格納した配列の参照。どちらも詳細は Linux 2-2 で扱う)grep grep問題:ps aux | grep nginxにはgrep nginx自身もマッチする。grep -v grepを付ける orgrep "[n]ginx"のトリック- 巨大ファイルでも cat 不要: 入力ファイルを取れるコマンドなら
cat file | grep xではなくgrep x fileの方が速い(Useless Use of Cat と揶揄される)
12. tee - 「画面表示」と「ファイル保存」を両立
tee は T字管のように、入力を2方向に分岐するコマンド。画面(stdout)にそのまま流しつつ、同時に指定ファイルにも書き込む。命名の由来は「T」の形(パイプを2方向に分岐するから)。
# 結果を画面に出しつつファイルにも保存
ls -la | tee filelist.txt
# 追記モード
echo "added" | tee -a filelist.txt
# パイプの途中経過を覗き見
cat access.log | grep "500" | tee errors.log | wc -l
# ↑ 500エラーの行を errors.log に保存しつつ、件数を画面表示
# sudo が必要なファイルに書き込む(リダイレクトでは無理なケース)
echo "127.0.0.1 myapp.local" | sudo tee -a /etc/hoststee の実務ユースケース
- 長時間タスクの進捗を見つつログ保存:
./deploy.sh 2>&1 | tee deploy.logsudo経由でファイルに書く:>はリダイレクトする側のシェルが処理するのでsudoの効果が及ばない。sudo teeならtee自体がrootで動く- パイプの中間結果を保存しつつ次のコマンドへ:
... | tee step1.log | ... | tee step2.log | ...
tee の落とし穴
- デフォルトは上書き: 追記したいなら
-aを忘れずに>>と勘違いしない:tee単体は上書き、tee -aで追記
13. xargs - 標準入力を「引数」に変換
xargs は stdin に流れてきた文字列を、コマンドの「引数」に変換するコマンド。rm, mv, cp, echo などの多くのコマンドは 引数からファイル名を受け取る のであって、stdin から読まない。だから find の結果を直接 rm に渡すには xargs が必要になる。
# echo はパイプから入力を受け取らない
ls | echo # 何も出ない(echo は stdin を読まない)
ls | xargs echo # ファイル名が引数として echo に渡る
# find と組み合わせて削除
find . -name "*.tmp" | xargs rm
# 1コマンドずつ実行(並列化)
find . -name "*.log" | xargs -P 4 gzip # 4並列で圧縮
# スペース・特殊文字に強い版(find -print0 と xargs -0)
find . -name "*.log" -print0 | xargs -0 rm
# 引数の位置を指定({} で挿入)
ls *.txt | xargs -I {} cp {} backup/
# 確認モード(実行前に y/n を聞く)
ls | xargs -p rmなぜ
xargsが必要かecho "file.txt" # 「file.txt」と表示 echo < file.txt # 何も表示しない(echo は stdin を読まないので)パイプで前のコマンドから渡されるのは stdin。しかし
echo,rm,mvなどは引数で動く。この「stdin → 引数」の変換がxargsの仕事。echo "file.txt" | xargs cat # → cat file.txt と同じ動作
xargs の主要オプション
オプション 意味 -I {}{}の位置に各入力を埋める(順序入れ替え可能)-n 11個ずつコマンドを呼ぶ(バッチではなく逐次) -P 4並列実行(4個同時に) -0NULL区切りで読む( find -print0と組み合わせる)-p各コマンドを実行前に確認 -t実行するコマンドを画面表示してから実行
xargs の実務ユースケース
findの結果を一括操作:find . -name "*.tmp" -print0 | xargs -0 rm- CSVのIPリストに一斉ping:
cat ips.txt | xargs -n 1 -P 10 ping -c 1- 複数ファイルを別ディレクトリに移動:
ls *.log | xargs -I {} mv {} archive/{}_$(date +%Y%m%d)- 大量のDockerイメージを削除:
docker images -q | xargs docker rmi
xargs の落とし穴
- スペース入りファイル名で爆発する:
My Document.txtのようなファイルがあると、xargs はスペースを区切りとして扱い「My」と「Document.txt」の2引数だと思って失敗する。find -print0 | xargs -0をデフォルトに- 空入力で「不思議な動作」: 入力が空でも xargs は1回コマンドを呼ぶ場合がある(GNU版
-rで抑制可能、macOSのBSD版は対応していないため空入力チェックを別途)findの-execと xargs の違い:
find ... -exec rm {} \;: 1ファイルずつ rm を起動(遅い)find ... -exec rm {} +: まとめて起動(速い)find ... | xargs rm: まとめて起動(速い、ただしスペース問題に注意)- 結論: 安全と速度を両立するなら
find ... -exec rm {} +が最も安心
14. 実務でよく見る組み合わせパターン
# パターン1: ログから特定IPの500エラーだけを抽出してファイル保存
grep "500" access.log | awk '$3 == "192.168.1.10"' | tee errors_for_ip.log
# パターン2: 大量ファイルをまとめて移動(スペース対応)
find /backup -name "*.tar.gz" -mtime +30 -print0 | xargs -0 mv -t /old_backup/
# パターン3: ヒアドキュメントで設定ファイル生成
cat > nginx.conf <<EOF
server {
listen 80;
server_name myapp.local;
location / {
proxy_pass http://localhost:3000;
}
}
EOF
# パターン4: 進捗を見つつ結果をログに残す
# ↓ `$(コマンド)` は「中のコマンドを実行して、その出力をここに埋め込む」シェル記法=コマンド置換(詳細は Linux 2-2)。
# `$(date +%Y%m%d_%H%M)` は `20260521_1430` のような日時文字列に展開される。
./long_task.sh 2>&1 | tee task_$(date +%Y%m%d_%H%M).log
# パターン5: エラーを捨てつつ正常結果だけパイプ処理
find / -name "*.conf" 2>/dev/null | xargs grep -l "listen" 2>/dev/null15. 練習課題
# 課題1: 500エラーの行を errors.log に保存しつつ件数を画面に出す
grep "500" access.log | tee errors.log | wc -l
# 課題2: アクセスIPの上位3つを抽出して ranking.txt に保存
awk '{print $3}' access.log | sort | uniq -c | sort -rn | head -3 | tee ranking.txt
# 課題3: stderr 抑制つきで /etc 配下から ".conf" を探す
find /etc -name "*.conf" 2>/dev/null | head -5
# 課題4: 練習用ファイルを5個作って、xargs でまとめて削除
touch trash_{01..05}.tmp
ls trash_*.tmp
ls trash_*.tmp | xargs rm # 一括削除
ls trash_*.tmp 2>/dev/null # 何も出なければ成功
# 課題5: ヒアドキュメントで簡単なシェルスクリプトを生成
# ↓ 1行目の `#!/bin/bash` は「shebang」と呼ばれ、「このファイルを bash で実行してね」と OS に教える宣言行(詳細は Linux 2-2)。
# `$(whoami)` `$(date)` はコマンド置換: 中のコマンドを実行して、その出力を文字列としてここに埋め込む(詳細は Linux 2-2)。
# クォート付きヒア (<<'EOF') を使っているのでファイル生成時には展開されず、後で ./greet.sh を実行した瞬間に展開される。
cat > greet.sh <<'EOF'
#!/bin/bash
echo "Hello from $(whoami) at $(date)"
EOF
chmod +x greet.sh
./greet.sh締め: 振り返り(10分)
1. セッション録画を終了
exit2. 今日の発見
このノートに追記:
- stdout と stderr が分かれている意味、納得できたシーン:
成功結果とエラー結果を分けたい場合がある。とくにエラーログなどはまとめておきたいので2>を使うのは納得
- xargs を初めて使った感想:
findのexecでもいい気がする
- 「これパイプで一発じゃん」と気付いた処理:
アクセスの多いIPアドレスとかをランキングにするとは楽そう
- 明日やりたいこと:
チェックリスト
-
>>><2>2>&1&>をそれぞれ実際に試した -
/dev/nullでエラー出力を捨てる場面を体験した - パイプで3つ以上のコマンドを繋いだ
-
teeで「画面表示+ファイル保存」を同時にやった -
xargsでfindの結果を別コマンドに渡せた - ヒアドキュメントで複数行のファイルを生成した
詰まった時のチートシート
| やりたいこと | 書き方 |
|---|---|
| 標準出力をファイルへ | command > file |
| 標準出力を追記 | command >> file |
| 標準エラーをファイルへ | command 2> file |
| 両方を同じファイルへ | command &> file または command > file 2>&1 |
| エラーだけ捨てる | command 2>/dev/null |
| 全部捨てる | command &>/dev/null |
| ファイルを入力に | command < file |
| パイプで連結 | cmdA | cmdB |
| パイプ+ファイル保存 | cmdA | tee file | cmdB |
| パイプ+追記保存 | cmdA | tee -a file |
| sudo でファイル書き込み | echo "x" | sudo tee -a /etc/file |
| stdin を引数に変換 | ... | xargs <command> |
| 並列実行 | ... | xargs -P 4 <command> |
| スペース対応 | find ... -print0 | xargs -0 ... |
| ヒアドキュメント | cat <<EOF ... EOF |
| 変数展開なしのヒア | cat <<'EOF' ... EOF |
「実務OK」基準
- stdin / stdout / stderr の3つを意識して、リダイレクトを書ける
2>/dev/nullで不要なエラーを抑制し、欲しい情報だけ拾える- 長いパイプチェーン(5段以上)を組み立てて集計できる
teeで「進捗を見つつログ保存」のパターンが書けるxargsを使ったfindとの連携が書ける
ここまで来れば「シェルでデータ加工パイプラインを組める人」。
アンチパターン / 初心者やらかし事例
NG 1: command > file 2>&1 の順序を間違える
command 2>&1 > file # NG: stderr が「画面」へ、stdout だけ file へ行く
command > file 2>&1 # OK: 両方 file へ→ シェルは左から右に解釈する。2>&1 は 「現時点の stdout の向き先に stderr を流す」 の意味。> を先に書いてから 2>&1 が鉄則。
NG 2: cat file | grep x で「ファイルなくしてる?」
cat large.log | grep ERROR # 動くが UUOC(Useless Use of Cat)
grep ERROR large.log # こちらでOK→ パイプは便利だが、1ファイルなら直接渡せる。grep / awk / sort は引数にファイルを取れる。
NG 3: > で既存ファイルを上書きしてしまう
sort access.log > access.log # 空になる!シェルが先に file を開いて 0 にしてから sort が読みに行く→ 対策: 入力と出力を同じファイルにしない。一時ファイル経由 で sort access.log > tmp && mv tmp access.log。または sort -o access.log access.log(-o は sort 専用、安全)。
NG 4: xargs でスペース入りファイル名が壊れる
find . -name "*.log" | xargs rm # "My Log.txt" が「My」「Log.txt」と分割される→ 対策: -print0 + xargs -0 をセットで使う: find . -name "*.log" -print0 | xargs -0 rm。
自己評価チェックリスト
-
stdoutとstderrがなぜ分かれているか、実例で説明できる -
2>/dev/nullの意味を即答できる -
teeを使った「画面 + ファイル保存」のシーンが思い浮かぶ -
xargsを使うべき場面とfind -execで済む場面の区別がつく - ヒアドキュメント
<<EOFと<<'EOF'(変数展開しない版)の違いが言える
次のレッスン: Day 6 - プロセス管理
明日は 動いているプログラムを見る・操る がテーマ。
今日まで「ファイル」と「テキスト」を扱ってきたが、Linux にはもうひとつの主役 ─ 「プロセス」 がいる。ps / top / htop / kill / & (バックグラウンド) で「Webサーバーが落ちた」「重いプロセスが居座っている」みたいな現場対応の基本を覚える。
→ Day 6: プロセス管理 へ