スタートアップCTOによるITエンジニアのためのブログ

Bashリダイレクトの使い方と仕組み

Bashスクリプトでリダイレクト記号が複数出てきた時の読み方がわからない、パイプとどう使い分けたら良いかわからない、と言った経験はないでしょうか。

本記事では、リダイレクトの使い方や複数個使用時の処理順、裏側の仕組みやパイプとの違いまで、徹底解説します。

目次

  • リダイレクトとは
  • オペレータとオペランド
  • 標準入力のリダイレクト
  • 標準出力のリダイレクト
  • 標準エラー出力のリダイレクト
  • リダイレクトの評価順
  • リダイレクトの仕組みとファイルディスクリプタ
  • パイプとの違いと、組み合わせ時の実行順
  • まとめ

リダイレクトとは

Bashのリダイレクト(Redirection)は、コマンドの入力や出力をファイルや他の場所に切り替えるための非常に強力な機能です。

通常、コマンドの結果は画面(標準出力)に表示され、入力はキーボード(標準入力)から受け取りますが、これらを自由自在にコントロールできるようになります。

基本の3要素:標準入出力

Bashでは、すべてのデータフローに「ファイル記述子(ファイルディスクリプタ / FD)」という番号が割り当てられています。

番号名称デフォルトの参照先
0標準入力 (stdin)キーボード
1標準出力 (stdout)画面(ターミナル)
2標準エラー出力 (stderr)画面(ターミナル)

これらのファイルディスクリプタの参照先を、デフォルト値から別の場所(ファイルなど)に変更する操作がリダイレクトです。

オペレータとオペランド

一般的に、リダイレクトは以下のオペレータとオペランド(左辺、右辺)の組み合わせで表現されます。

  • 左辺オペランド: 参照先を変更するファイルディスクリプタ番号(0, 1, 2など)。0と1は省略可能。
  • オペレータ:入出力を表す矢印記号(<> など)と複製を表すアンパサンド記号(&)の組み合わせ。
  • 右辺オペランド:新しい参照先(ファイル名やファイルディスクリプタ番号)。

例:2>&1 の意味

これは「標準エラー出力(2)の参照先を、標準出力(1)の現在の参照先と同じにする」という意味です。

  • 左辺: 2(標準エラー出力)
  • オペレータ: >& (左辺の出力を右辺の複製とする)
  • 右辺: 1(標準出力)

Tip:オペレータの覚え方

矢印の向きはデータの流れる方向です。cmd < file はファイルからコマンドへ、cmd > file はコマンドからファイルへ。 & は「次に続く数字はファイル名ではなく、FD番号ですよ」という合図だと考えると分かりやすいです(2>1 と書くと、「1」という名前のファイルができてしまいます)。

標準入力のリダイレクト

ファイルの内容をコマンドに読み込ませる方法です。

< : ファイルから入力

キーボードの代わりに、ファイルの内容をコマンドに渡します。

# Bash
sort < list.txt

<< : ヒアドキュメント

スクリプト内で、複数行のテキストをまとめて入力としたいときに使います。

# Bash
sort << EOF
  orange
  banana
  apple
EOF

<<< : ヒアストリング

一行のテキストをコマンド入力とします。

# Bash
# 文字列をクリップボードにコピー
xclip -selection clipboard <<< "Hello world"

Tip: <, <<, <<<は、それぞれ0<, 0<<, 0<<の省略形です。0は標準入力のファイルディスクリプタ番号です。

標準出力のリダイレクト

コマンドの結果をファイルに保存する方法です。

> : 新規書き込み(上書き)

ファイルが既に存在する場合は、中身が消去(切り詰め)されてから書き込まれます。

# Bash
ls > files.txt

>> : 追記

既存の内容を保持したまま、末尾に結果を追加します。

# Bash
date >> log.txt

Tip: >>>は、それぞれ1>, 1>>の省略形です。1はファイルディスクリプタ番号で、標準出力を指します。

標準エラー出力のリダイレクト

「正常な結果」と「エラーメッセージ」を別々に扱いたい場合に使います。

標準エラーのみをファイルへ (2>)

# Bash
ls non_existent_dir 2> error.log

標準出力と標準エラー出力を両方同じファイルへ (&>, &>>)

# Bash
# 上書き
command &> all_output.txt

# 追記
command &>> all_output.txt

注:&> はBash独自の記法です。互換性を重視する場合は > file 2>&1 を使用します。

リダイレクトの評価順

Bashは以下の順番で処理を行います。

  1. リダイレクトを左から右に出現順に評価。
  2. コマンド実行。

よくあるミス1:記載順序

標準エラー出力と標準出力を同じファイルにまとめたい場合:

  • 誤:ls /root 2>&1 > file
    1. 2>&1: FD2の参照先を、現在のFD1(ターミナル)に向ける。
    2. > file: FD1の参照先をファイルに向ける。
    3. 結果: エラーはターミナルへ、標準出力はファイルへ。
  • 正:ls /root > file 2>&1
    1. > file: FD1の参照先をファイルに向ける。
    2. 2>&1: FD2の参照先を、現在のFD1(ファイル)に向ける。
    3. 結果: 両方ともファイルへ。

よくあるミス2:読み書き同時指定

# Bash
# 危険!ファイルの中身が消去されます。
cat < file > file

一見、ファイルを読み込んで自分自身に書き込んでいるように見えますが、コマンド実行前に > file が評価された瞬間、ファイルの中身が空(サイズ0)にされます。 その後 cat が実行されるため、空のファイルを読んで空を書き込むことになります。

リダイレクトの仕組みとファイルディスクリプタ

リダイレクトはコマンドそのものの動作を変えているのではありません。コマンドが実行される直前に、シェルのプロセスがFDの「向き先」をすり替えているだけです。

コマンド自身は、自分がどこに出力しているかを知りません。ただ「FD 1番に書き出す」という命令を実行するだけです。

リダイレクト前の状態:

  • cmd <— 標準入力(fd: 0) <— キーボード
  • cmd —> 標準出力(fd: 1) —> ターミナル画面
  • cmd —> 標準エラー (fd:2) —> ターミナル画面

例1:標準入力のリダイレクト(cmd < file)

  • cmd <— 標準入力(fd: 0) <— file
  • cmd —> 標準出力(fd: 1) —> ターミナル画面
  • cmd —> 標準エラー (fd:2) —> ターミナル画面

例2: 標準出力のリダイレクト (cmd > file)

  • cmd <— 標準入力(fd: 0) <— キーボード
  • cmd —> 標準出力(fd: 1) —> file
  • cmd —> 標準エラー (fd:2) —> ターミナル画面

例3:標準エラー出力のリダイレクト( cmd 2> file)

  • cmd <— 標準入力(fd: 0) <— キーボード
  • cmd —> 標準出力(fd: 1) —> ターミナル画面
  • cmd —> 標準エラー (fd:2) —> file

例4:標準出力と標準エラーのリダイレクト (cmd > file 2>&1)

  • cmd <— 標準入力(fd: 0) <— キーボード
  • cmd —> 標準出力(fd: 1) —> file
  • cmd —> 標準エラー (fd:2) —> file

そのため、> fileを「コマンドの出力先を、標準出力からファイルに変更する」と理解したり、> file 2>&1を「標準エラー出力の参照先を標準出力に変更する」と理解するのは正しくありませんので、気をつけましょう。

パイプとの違いと、組み合わせ時の実行順

パイプ (|) との違い

  • リダイレクト (>): 出力先を「ファイル(またはデバイス)」に変更する。
  • パイプ (|): コマンドAの出力を、コマンドBの入力として繋ぐ。

裏側の仕組みとしては、両者ともファイルディスクリプタの参照先を変更しているのは一緒ですが、参照先が異なります。

  • リダイレクト:任意のファイルディスクリプタの参照先をファイルに変更。
  • パイプ:データの通り道(パイプ)を作り、コマンドAのファイルディスクリプタ1(標準出力)の参照先とコマンドBのファイルディスクリプタ0(標準入力)の参照先をその通り道とする。

リダイレクトとパイプのファイルディスクリプタ参照先の例

# Bash

# デフォルト参照先
ls -l /proc/self/fd
# 出力例
# /dev/pts/0は仮想ターミナル(キーボード&画面)
# lrwx------ ... 0 -> /dev/pts/0
# lrwx------ ... 1 -> /dev/pts/0
# lrwx------ ... 2 -> /dev/pts/0
# lr-x------ ... 3 -> /proc/192458/fd
# lrwx------ ... 6 -> /dev/pts/0

# 標準出力リダイレクト時
ls -l /proc/self/fd > output.txt
cat output.txt
# lrwx------ ... 0 -> /dev/pts/0
# lrwx------ ... 1 -> /home/user/output.txt
# lrwx------ ... 2 -> /dev/pts/0
# lr-x------ ... 3 -> /proc/192458/fd
# lrwx------ ... 6 -> /dev/pts/0

# パイプ
ls -l /proc/self/fd  | cat
# lrwx------ ... 0 -> /dev/pts/0
# lrwx------ ... 1 -> pipe:[2341174]
# lrwx------ ... 2 -> /dev/pts/0
# lr-x------ ... 3 -> /proc/192458/fd
# lrwx------ ... 6 -> /dev/pts/0

組み合わせ時の実行順

リダイレクトとパイプを組み合わせた場合、Bashは以下の順番で処理します。

  1. パイプが作成され、コマンドAの標準出力とコマンドBの標準入力の接続先がパイプに変更される
  2. 各コマンド内でリダイレクトが評価される。
  3. コマンドAとコマンドBが実行される。

この順序があるからこそ、cmdA 2>&1 | cmdB という書き方で、標準エラー出力もまとめてパイプに流すことができるのです。

  1. パイプが作成され、cmdAの標準出力とcmdBの標準入力の接続先がパイプに変更される
  2. リダイレクト評価により、標準エラー出力の参照先が標準出力の参照先であるパイプと同じになる。
  3. 両コマンドが実行される。
# Bash
ls -l /proc/self/fd 2>&1 | cat
# 出力例
# lrwx------ ... 0 -> /dev/pts/0
# l-wx------ ... 1 -> pipe:[2352474]
# l-wx------ ... 2 -> pipe:[2352474]
# lr-x------ ... 3 -> /proc/193395/fd
# lrwx------ ... 6 -> /dev/pts/0

まとめ

  • リダイレクトはファイルディスクリプタの参照先を変更する。
  • リダイレクトは左から右に、コマンド実行前に処理される。
  • > は評価された瞬間にファイルを切り詰めるため、同一ファイルの読み書きには注意。

リダイレクトの仕組みを理解すると、複雑なスクリプトのデータフローも一目で読み解けるようになります。ぜひ活用してみてください!