Bash 入門

Bash 標準 I/O ストリームとリダイレクト

Linux や Unix ライクなシステムでは、実行されるすべてのプログラムは、標準入力 (Standard Input, stdin)、標準出力 (Standard Output, stdout)、標準エラー出力 (Standard Error, stderr) という 3 つの標準 I/O ストリームを介して環境とやり取りします。

これらのストリームは、スクリプトがデータを受け取り、結果を生成し、問題を報告するための基礎となります。これらが存在するからこそ、柔軟なコマンドのチェーン呼び出し(パイプ)や入出力のリダイレクトが可能になるのです。

1. 標準ストリーム (Standard Streams) の深い理解

標準ストリームは、プログラムとその環境の間であらかじめ開かれているデータチャネルです。プログラムが起動すると、以下の 3 つの接続を自動的に継承します。

  • 標準入力 (stdin - ファイル記述子 0): プログラムが入力データを読み取るためのチャネルです。デフォルトでは、stdin はキーボードに接続されており、プログラムはユーザーからのコンソール入力を待機します。ただし、stdin はリダイレクトして、ファイルや別のコマンドの出力からデータを読み取るように変更することも可能です。
  • 標準出力 (stdout - ファイル記述子 1): プログラムが正常な実行結果を書き込むためのチャネルです。デフォルトでは、stdout はターミナル画面に接続されているため、コマンドが生成した正常な出力はコンソールに直接表示されます。stdin と同様に、stdout もファイルに書き込んだり、パイプを介して別のコマンドの入力として渡したりすることができます。
  • 標準エラー出力 (stderr - ファイル記述子 2): プログラムがエラーメッセージや診断情報を書き込むためのチャネルです。デフォルトでは、stderr もターミナル画面に接続されており、stdout と混ざって表示されます。デバッグやオートメーションタスクにおいて、エラーメッセージを通常の出力から分離することは非常に重要です。これにより、エラーを個別に処理(例えば、専用のログファイルに記録)することが可能になります。

ファイル記述子 (File Descriptors, FD): 0, 1, 2 は、オペレーティングシステムがこれらの特定の I/O チャネルを参照するために使用する数値識別子です。Bash やその他のシェル環境で I/O リダイレクトを行う際、これらの記述子が広く使われます。

1.1 実戦シナリオ:ログ記録とデバッグ

ファイルをコピーして圧縮するバックアップスクリプトを例に考えてみましょう。

  • stdout には、どのファイルが正常にバックアップされたか、圧縮結果はどうなったかという通常のメッセージが含まれます。
  • stderr には、アクセスできないファイル、権限エラー(Permission denied)、またはディスク容量不足などの警告メッセージが含まれます。
  • stdin は、スクリプトが重要な操作を実行する前に、ユーザーに y を入力して確認を求めるために使用されるかもしれません。

これらのストリームを分離することで、システム管理者は膨大な成功メッセージ (stdout) の中から苦労して探すことなく、エラーログ (stderr) を素早く確認できます。また、成功した出力結果だけをパイプで別のプログラムに渡し、統計処理を行うこともできます。

2. 標準出力 (stdout) のリダイレクト

stdout のリダイレクトを使用すると、コマンドの正常な出力をターミナル画面からファイルや別のコマンドへと「転送」できます。これは >>> オペレータを使用して実現します。

> (上書きリダイレクトオペレータ): stdout をファイルに書き込みます。ファイルが存在しない場合は作成され、既に存在する場合は内容が完全に上書き(クリア)されます。

# 例 1: ls の出力をファイルに上書き保存
ls -l /etc > system_files.txt
# 'ls -l /etc' の出力が system_files.txt に書き込まれる。既存の内容は消去される。

# 例 2: コマンド出力を後続処理のために保存
date > current_datetime.log
# 現在の日時が current_datetime.log に書き込まれる。

>> (追記リダイレクトオペレータ): stdout をファイルに書き込みます。ファイルが存在しない場合は作成されます。ファイルが既に存在する場合、新しい出力結果はファイルの末尾に追加(アペンド)され、元の内容は保持されます。

# 例 1: ログエントリを追記
echo "最初のログ記録" >> application.log
echo "2番目のログ記録" >> application.log
# 両方の行が application.log に保存される。

# 例 2: 時間の経過とともにリストを構築
ls -d */ >> directories_list.txt # すべてのディレクトリをファイルに追記
find . -maxdepth 1 -type f >> files_list.txt # カレントディレクトリのファイルを追記

2.1 総合デモ:システム巡検レポートの生成

リダイレクトを利用して、複数のシステム調査コマンドの出力を 1 つのレポートファイルにまとめることができます。

#!/bin/bash
REPORT_FILE="system_report_$(date +%Y%m%d).txt"

# '>' を使用して新しいレポートを開始(既存ファイルがあれば上書き)
echo "--- システム情報巡検レポート ---" > "$REPORT_FILE"

# 以降はすべて '>>' を使用して内容を追記
echo "生成時間: $(date)" >> "$REPORT_FILE"
echo "---------------------------------" >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"

echo "### ディスク使用状況 ###" >> "$REPORT_FILE"
df -h >> "$REPORT_FILE"

echo "" >> "$REPORT_FILE"

echo "### メモリ使用状況 ###" >> "$REPORT_FILE"
free -h >> "$REPORT_FILE"

echo "" >> "$REPORT_FILE"

echo "### メモリ使用量 Top 5 のプロセス ###" >> "$REPORT_FILE"
ps aux --sort=-%mem | head -n 6 >> "$REPORT_FILE"

echo "" >> "$REPORT_FILE"

echo "巡検レポートが $REPORT_FILE に保存されました"

3. 標準エラー出力 (stderr) のリダイレクト

stderr のリダイレクトは、エラーメッセージを分離するための強力な手法です。これにより、エラーの記録、無視、またはプログラム的な処理が非常に簡単になります。ファイル記述子 2 をリダイレクトオペレータと組み合わせて指定する必要があります。

2> (stderr のリダイレクト): stderr をファイルに上書きします。

# 例 1: エラーメッセージをキャプチャ
ls /nonexistent_directory 2> error.log
# 'ls' が生成したエラーメッセージ(例: "No such file or directory")が
# error.log に書き込まれ、ターミナルには表示されない。

# 例 2: エラーメッセージを破棄(Linux の「ブラックホール」 /dev/null へ送信)
find /etc -name "*.conf" -print 2> /dev/null
# find 実行時に発生するすべての権限不足エラー (Permission denied) が破棄される。
# ターミナルには成功したファイルパスのみが綺麗に表示される。

2>> (stderr の追記リダイレクト): stderr をファイルに追記します。

# 例 1: 永続的なエラーログを維持
cp /nonexistent_file /tmp 2>> script_errors.log
# 存在しないファイルのコピー時に発生したエラーが script_errors.log の末尾に追加される。

3.1 stdout と stderr のリダイレクトを統合する

正常な出力とエラー出力の両方をリダイレクトしたい場合、いくつかの方法があります。

方法 1:個別のファイルにリダイレクトする

some_command > output.txt 2> errors.txt
# 正常な出力は output.txt へ、エラー情報は errors.txt へ分離される。

方法 2:両方を同じファイルに統合してリダイレクトする

&> または &>> を使用(Bash 特有の構文。モダンで推奨される書き方):

some_command &> all_output.log  # stdout と stderr の両方を all_output.log へ(上書き)
some_command &>> all_output.log # stdout と stderr の両方を all_output.log へ(追記)

2>&1 を使用(伝統的な POSIX 準拠の書き方。すべてのシェルで共通): この書き方は「ファイル記述子 2 (stderr) を、ファイル記述子 1 (stdout) が現在指している場所にリダイレクトする」という意味です。注意:順序が極めて重要です!2>&1 は必ず > output.txt の後に置く必要があります。

some_command > all_output.log 2>&1
# 1. まず stdout が all_output.log を指すようにリダイレクトされる。
# 2. 次に stderr (2) が stdout (1) が現在指している場所(all_output.log)にリダイレクトされる。
# 結果:両方が同じファイルに入る。

注意点: もし some_command 2>&1 > all_output.log と書くと、stderr はまず stdout の現在の位置(この時点ではターミナル画面)にリダイレクトされ、その後に stdout がファイルにリダイレクトされます。結果として、エラーメッセージは依然として画面に表示され、正常なメッセージだけがファイルに書き込まれてしまいます。

3.2 総合デモ:高堅牢なスクリプトのログ記録

運用自動化スクリプトでは、後日の監査のためにすべての出力を記録することが重要です。

#!/bin/bash
BACKUP_DIR="/var/backups"
LOG_FILE="/var/log/backup_script.log"
DATE_SUFFIX=$(date +%Y%m%d_%H%M%S)

# バックアップディレクトリの作成。権限不足などのエラーも含めすべてログに追記
mkdir -p "$BACKUP_DIR" &>> "$LOG_FILE"

echo "--- バックアップタスク開始 ($DATE_SUFFIX) ---" &>> "$LOG_FILE"

# ディレクトリのアーカイブ。詳細リストもエラーもすべてログへ
tar -czvf "$BACKUP_DIR/my_config_$DATE_SUFFIX.tar.gz" /etc/my_app_configs &>> "$LOG_FILE"

# 存在しないファイルをコピー(エラーキャプチャのテスト用)
cp /nonexistent_file /tmp/backup_copy &>> "$LOG_FILE"

echo "--- バックアップタスク終了 ---" &>> "$LOG_FILE"

# 最後にユーザーへシンプルな通知を画面に表示
echo "バックアップスクリプトの実行が完了しました。詳細はログを確認してください: $LOG_FILE"

4. 標準入力 (stdin) とパイプ (|) の使用

標準入力 (stdin) はコマンドがデータを受け取るためのチャネルです。キーボード入力以外に、stdin はファイルからリダイレクトしたり、前のコマンドの stdout を受け取ったりできます。

< (入力リダイレクトオペレータ): ファイルから stdin をリダイレクトします。コマンドはキーボード入力を待つ代わりに、指定されたファイルから内容を読み取ります。

# 例: wc を使用してファイルの行数をカウント
wc -l < users.csv
# 'wc -l' コマンドは users.csv から内容を読み取り、行数を出力する。

パイプ | (Pipe): 前のコマンドの stdout を、後のコマンドの stdin に直接「流し込み」ます。これは Linux において複雑なデータ処理パイプライン(Pipeline)を構築するための最も核心的な魔法です。

# 例 1: フィルタリングとカウントのチェーン呼び出し
ls -l | grep "Aug" | wc -l
# 1. 'ls -l' がディレクトリの詳細を出力し、stdout を生成。
# 2. その stdout がパイプを介して 'grep "Aug"' に渡され、"Aug" を含む行が抽出される。
# 3. 抽出結果の stdout が 'wc -l' に渡され、行数がカウントされる。
# 最終結果:8月に更新されたファイルの数だけが画面に表示される。

# 例 2: 定番の Web サーバーログ解析ワンライナー
cat access.log | grep " 404 " | awk '{print $7}' | sort | uniq -c | sort -nr | head -n 5
# プロセス解析:
# 1. ログを読み込む -> 2. 404 エラーの行を抽出 -> 3. リクエスト URL フィールドを抽出 ->
# 4. URL をソート -> 5. URL ごとの出現回数をカウントして重複除去 -> 
# 6. 回数で降順(大きい順)にソート -> 7. 上位 5 件を取得。
# 結果:404 エラーを最も多く発生させている Top 5 の URL を出力。