リダイレクトとファイルディスクリプタ
シェルスクリプトやコマンドで入出力の向き先を変更したいときには リダイレクト を使います。リダイレクトはファイルディスクリプタの参照先を変更することで入出力の向き先を変更します。
Unix系OSではファイルディスクリプタ(ファイル記述子のこと、以下 FD
)が3種類あります。それぞれ 0,1,2 の数で表されます。
FD(整数値) | 名前、参照先 | 説明 |
---|---|---|
0 | 標準入力(stdin) | 入力ストリーム。キーボード入力などを受け取る。 |
1 | 標準出力(stdout) | 出力ストリーム。画面に出力される。 |
2 | 標準エラー出力(stderr) | エラーに関連した出力ストリーム。画面に出力される。 |
例えば標準出力(FD1)をファイルに出力したい場合はリダイレクトで FD1 の参照先をファイルに変更するようなイメージです。
これを使えば、ファイル内容を標準入力として受け取ったり、ファイルに標準出力を書き出したり、標準エラー出力を /dev/null
に捨てたり、標準出力も標準エラーもまとめてファイルに出力したりと、いろいろなことができるようになります。
リダイレクトの記号(>, >>, <)
リダイレクトはファイルディスクリプタの参照先を変更することで入出力の向き先を変更します。変更のために使用する記号(コマンド?演算子?)が3つあり、>
, >>
, <
です。
それぞれ使い方を見ていきます。
'>' リダイレクトの基本
n>&m
とすることでファイルディスクリプタ n
を m
にリダイレクトします。つまり 2>&1
とすると標準エラー(FD2)の出力先が標準出力(FD1)にマージされます。つまり標準出力(FD1)も標準エラー(FD2)もまとめて標準出力(FD1)として出力されることになります。これがリダイレクトの基本です。
あるいはリダイレクト先にファイルディスクリプタではなく、ファイルを指定することでファイルを出力先とできます。
>
でファイルにリダイレクトすると、実行時にファイルをリダイレクト内容で上書きします。ファイルが存在しなければ新しく作成されます。
例として以下のようなスクリプト my_script.sh
を作成して動作を確認します。
#!/bin/bash
# 標準出力
echo 'This is stdout'
# 標準エラー出力するようにエラーを起こす
あああ
標準エラー出力は、適当なコマンドを実行して "my_script.sh: line 7: あああ: command not found" みたいな内容を出力するようにします。実行すると以下のようになります。
$ bash my_script.sh
This is stdout
my_script.sh: line 7: あああ: command not found
1>output.txt 標準出力をファイルにリダイレクト
標準出力をファイルにリダイレクトするには 1>[ファイル]
とします。
# 標準出力のみをファイルにリダイレクト
# 標準エラー出力はそのまま画面に
$ bash my_script.sh 1>output.txt
my_script.sh: line 7: あああ: command not found
# 標準出力だけが書き込まれてる
$ cat output.txt
This is stdout
ファイルディスクリプタ 1
の指定は省略可能で省略すると 1
が指定された扱いになります。
$ bash my_script.sh >output.txt
2>output.txt 標準エラー出力をファイルにリダイレクト
# 標準エラーのみをファイルにリダイレクト
# 標準出力はそのまま画面に
$ bash my_script.sh 2>output.txt
This is stdout
# 標準エラーだけが書き込まれてる
$ cat output.txt
my_script.sh: line 7: あああ: command not found
1>output.txt 2>error.txt 標準出力と標準エラーを別ファイルにリダイレクト
もちろん複数のディスクリプタについてリダイレクト設定を指定できます。
# 標準出力と標準エラーを別々にリダイレクト
$ bash my_script.sh 1>output.txt 2>error.txt
$ cat output.txt
This is stdout
$ cat error.txt
my_script.sh: line 7: あああ: command not found
2>&1 標準エラーを標準出力にマージする
# 標準エラーを標準出力にマージし、標準出力をファイルに出力
$ bash my_script.sh 1>output.txt 2>&1
$ cat output.txt
This is stdout
my_script.sh: line 7: あああ: command not found
2>&1
とすると FD2(標準エラー)をFD1(標準出力)にリダイレクトできます。これで標準エラーの出力内容も標準出力にマージされて出力されるようになります。
1>output.txt
と合わせて指定することで標準出力も標準エラーもまとめてファイルに出力できます。
リダイレクトを指定する順番は 1>output.txt 2>&1
の順でなければいけません。逆だと想定通りには動きません。
リダイレクトの設定順
基本的にコマンドの実行は左から順番に実行されます。リダイレクトも同じく左からファイルディスクリプトの参照先が変更されます。この指定順を理解するために変更順を以下に記します。
初期状態として
- FD1=標準出力
- FD2=標準エラー出力
となっています。
最初に 1>output.txt
のリダイレクトが設定されます。
- FD1=output.txt
- FD2=標準エラー出力
次に 2>&1
が設定されます。FD1=output.txt
なので以下のようになります。
- FD1=output.txt
- FD2=output.txt
これで両方が output.txt
に出力されるというわけです。
これが逆に 2>&1 1>output.txt
とすると、まず 2>&1
が設定されて
- FD1=標準出力
- FD2=標準出力
となり、1>output.txt
が設定されて
- FD1=output.txt
- FD2=標準出力
となってしまいます。変数の代入みたいなイメージですね。リダイレクトの設定順序はちゃんと考えて設定しましょう。
1>/dev/null 2>&1 で標準出力も標準エラーもまとめて破棄するリダイレクト
# my_script.sh の結果をすべて破棄する
$ bash my_script.sh 1>/dev/null 2>&1
リダイレクト先を /dev/null
にするとデータは破棄されます。
&> アンパサンド、アンドマーク
&>output.txt
とすると、標準出力と標準エラー両方を同じファイルにリダイレクトできます。1>output.txt 2>output.txt
と同じように動作します。
# 標準出力と標準エラーをファイルにリダイレクト
$ bash my_script.sh &>output.txt
$ cat output.txt
This is stdout
my_script.sh: line 7: あああ: command not found
bash
以外のシェルだと動かないかもです。
>> ファイルに追記モードでリダイレクト
リダイレクトで >>
を使うと、ファイルを追記モードで開いてリダイレクトします。>
でのリダイレクトだと実行時にファイルをゼロ初期化したのちにリダイレクト内容で上書きします。ファイルが存在しなければ新規作成します。
# > でリダイレクト
$ echo first > output.txt
# >> でリダイレクト(追記)
$ echo second >> output.txt
# 結果確認
$ cat output.txt
first
second
< 標準入力にリダイレクト
<
で標準入力にリダイレクトが可能です。
標準入力にリダイレクトする例です。
# 適当にファイルを作成する
$ echo 'hello' > hello.txt
# 標準入力に hello.txt の内容をリダイレクト
$ cat - < hello.txt
hello
< hello.txt
でファイル内容を標準入力にリダイレクトしています。cat -
は標準入力の内容を出力します。リダイレクト結果を使って出力しているので実質 cat hello.txt
と同じ結果になるので無意味なコードですが、まあ使い方の参考です。
シェルコマンドには標準入力の内容を使って処理を実行するコマンドが多くあります。cat
や sort
, grep
などよく使うコマンドも標準入力を入力データとすることが可能です。そのようなときにファイルを入力データとして扱いたい場合に <
でリダイレクトします。
<(cmd) コマンドの実行結果を標準入力にリダイレクト
例えば shuf
でランダムな並べ替えで出力します。
$ shuf -i 1-5
2
5
1
4
3
この出力を標準入力にリダイレクトするには <(shuf -i 1-5)
とします。
例えば sort
で標準入力をソートできるのでリダイレクト内容をソートすると、昇順に並べ替えられます。
$ sort <(shuf -i 1-5)
1
2
3
4
5
diff
も標準入力にリダイレクトして比較できます。
$ diff <(echo hoge) <(echo foo)
1c1
< hoge
---
> foo
'<' と '>' を組み合わせる
標準入力へのリダイレクト内容を使った実行結果を別ファイルにリダイレクトすることも、'<' と '>' を組み合わせることで可能です。
例えばソート結果をリダイレクトしてファイルに書き出す例です。
# 適当な入力データ
$ echo -e '3\n4\n1\n2\n5' > input.txt
$ cat input.txt
3
4
1
2
5
# input.txt をソートした結果を output.txt に出力
$ sort < input.txt > output.txt
# ソート結果確認
$ cat output.txt
1
2
3
4
5
リダイレクトの記号の前後はスペースの挿入が可能です。ファイルディスクリプタを明示的に指定するなら以下のようになります。
# $ sort < input.txt > output.txt と同義
$ sort 0<input.txt 1>output.txt
ちゃんとソート結果が出力されることが確認できました。
パイプで標準入力で処理を連結
|
を使って複数の処理結果(標準出力/エラー)をパイプでつなぎ合わせながら実行できます。例えば、ランダムな文字列を5文字生成する処理をパイプを使って実行してみると以下のようになります。
$ head -c 10 /dev/urandom | base64 | cut -c 1-5
8x9Du
この中の |
がパイプです。どういう動作をしているかというと出力結果を次のコマンドの標準入力にリダイレクトするような感じの動作です。つまりこの例だと以下のように動作しています。
head -c 10 /dev/urandom
で10バイトのランダムバイト列を標準出力する|
で次の標準入力にバイト列(標準出力)を渡すbase64
でリダイレクトされた標準入力の内容をbase64エンコード|
で次の標準入力にエンコード結果(標準出力)を渡すcut -c 1-5
で標準入力の先頭5文字を取り出す
こんな感じで一連の処理の流れを標準入力/出力を介して連結するのがパイプ |
の役割です。リダイレクトとセットで使い方を覚えると便利なのでまとめておきます。これはリダイレクトと同じくよく使います。
特に grep
との組み合わせなどでよく使ったりします。ファイルの中から特定の文言を含む行を探したりすることができます。
$ cat input.txt | grep 'abc'
abc
abc
使い方は色々です。パイプはよく見かけるコードなので理解しておくとよいです。
以上。
コメントを書く