リダイレクト

シェルが行入力を解釈して処理を行っていく途中で、リダイレクトを見つけて処理することはこれまでの回で簡単に触れている。このリダイレクトはシェルの代表的な機能と言えるもので、ファイルへの書き込みやファイルからのデータ読み込みなどを実現することができる。

シンタックス的な書き方をすると、リダイレクト演算子は次のような書き方をする。

[ファイルディスクリプタ番号] リダイレクト演算子 ファイル

リダイレクトの代表的な使用例を表にまとめると次のようになる。

リダイレクト演算子例 内容
> ファイル 標準出力をファイルへリダイレクト
>| ファイル 標準出力をファイルへリダイレクト(オプション-Cをオーバーライド)
>> ファイル 標準出力をファイルへ追記
< ファイル ファイルから標準入力へリダイレクト
<> ファイル 標準入力をファイルへリダイレクト/ファイルを標準入力へリダイレクト
<&n ファイルディスクリプタnを標準入力へ複製
<&- 標準入力をクローズ
>&n 標準出力をファイルディスクリプタnへ複製
>&- 標準出力をクローズ
n> ファイル ファイルディスクリプタnをファイルへリダイレクト
n>| ファイル ファイルディスクリプタnをファイルへリダイレクト(オプション-Cをオーバーライド)
n>> ファイル ファイルディスクリプタnをファイルへ追記
n< ファイル ファイルからファイルディスクリプタnへリダイレクト
n<> ファイル ファイルディスクリプタnをファイルへリダイレクト/ファイルを標準入力へリダイレクト
n1<&n2 ファイルディスクリプタn2をファイルディスクリプタおn1へ複製
n<&- ファイルディスクリプタnをクローズ
n1>&n2 ファイルディスクリプタn1をファイルディスクリプタn2へ複製
n>&- ファイルディスクリプタnをクローズ

リダイレクトにはもう1つ、「ヒアドキュメント」という使い方がある。これは、シェルスクリプトなどでスクリプト内にテキストなどを書き、それを標準出力に渡すといった使い方をするための機能だ。指定したデリミタまでがデータが扱いの対象になる。

[ファイルディスクリプタ番号]<< デリミタ
ヒアドキュメント
...
デリミタ

リダイレクトはdup(2)やdup2(2)といったシステムコールの機能を理解しているとわかりやすい。カーネルが提供している機能を直接利用するのがこのリダイレクトと呼ばれる機能だからだ。逆に言うと、カーネルの提供している機能がよくわかってないと、この機能やリダイレクトの説明を読んでもちんぷんかんぷんかもしれない。

リダイレクトについては、説明を読むよりも実際の動作を通じて実感として理解するほうが早いと思う。次にそうしたサンプルを見ていこう。

リダイレクトの使用例

まず次のコマンドの実行例を見てみよう。dateコマンドを実行して日時を出力させている。

$ date
2019年 6月25日 火曜日 17時11分01秒 JST
$

リダイレクトの最も代表的な使い方は、こうしたコマンドの出力をファイルに書き込むことだ。次のようにリダイレクトを利用すると、dateコマンドの出力を指定したファイルに書き込むことができる。

$ date > a.txt
$ ls -lh a.txt
-rw-r--r--  1 daichi  staff    48B  6月 25 17:11 a.txt
$ cat a.txt
2019年 6月25日 火曜日 17時11分05秒 JST
$

次のように、もう一度同じコマンドを実行してみよう。すると、先程書き込まれた内容は消え、新しいほうのデータのみが書き込まれていることを確認できる。

$ date > a.txt
$ cat a.txt
2019年 6月25日 火曜日 17時11分49秒 JST
$

「>」によるデータの書き込みは新規作成と同じだ。すでにその名前のファイルがあった場合、ファイルの先頭から新しいデータが上書きされる。いったん中身を削除してから書き込む動作に似ている。

これに対して「>>」は追記になる。「>>」で書き込みを行うと、次のように先に書き込まれているデータの最後に新しくデータが追加される。

$ date >> a.txt
$ cat a.txt
2019年 6月25日 火曜日 17時11分49秒 JST
2019年 6月25日 火曜日 17時12分09秒 JST
$ date >> a.txt
$ cat a.txt
2019年 6月25日 火曜日 17時11分49秒 JST
2019年 6月25日 火曜日 17時12分09秒 JST
2019年 6月25日 火曜日 17時12分12秒 JST
$

今度は逆方向のリダイレクトも見てみよう。「<」でファイルを標準入力として利用できるようになる。例えば、trコマンドは引数としてファイルパスを取らない。こうしたコマンドに対しては<リダイレクトでファイルの中身を渡してあげるか、パイプラインで接続してファイルの中身を渡してあげることができる。

$ cat a.txt
2019年 6月25日 火曜日 17時11分49秒 JST
2019年 6月25日 火曜日 17時12分09秒 JST
2019年 6月25日 火曜日 17時12分12秒 JST
$ tr "[A-Z]" "[a-z]" < a.txt
2019年 6月25日 火曜日 17時11分49秒 jst
2019年 6月25日 火曜日 17時12分09秒 jst
2019年 6月25日 火曜日 17時12分12秒 jst
$ cat a.txt | tr "[A-Z]" "[a-z]"
2019年 6月25日 火曜日 17時11分49秒 jst
2019年 6月25日 火曜日 17時12分09秒 jst
2019年 6月25日 火曜日 17時12分12秒 jst
$

リダイレクトでよく使われる機能にエラーメッセージなどを削除するというものがある。例えば、次のように/dev/nullに書き込むといった使い方をする。

$ echo エラーメッセージ > /dev/null
$

/dev/nullは、書き込んだものがそのまま消えるという特殊なデバイスファイルが、データを消したりエラーメッセージを消すといった用途で使われる。例えば、次のように「1>&2」と指定すると、標準出力を標準エラー出力へ変更することができる。

$ echo エラーメッセージ > /dev/null 1>&2
エラーメッセージ
$

この場合、「> /dev/null」は標準出力をリダイレクトするものなので、標準エラー出力に出力されるメッセージは/dev/nullへリダイレクトされることなく表示される。

この状態で次のように「>」を「2>」に変更すると、標準エラー出力は/dev/nullに書き込まれて消えることになる。

$ echo エラーメッセージ 2> /dev/null 1>&2
$

この状態で「1>&2」の指定を消すと、「2>」は標準エラー出力をリダイレクトするものなので、標準出力へ書き込まれているメッセージはそのまま表示されるようになる。

$ echo エラーメッセージ 2> /dev/null
エラーメッセージ
$

リダイレクトがよく使われる方法はこんなところだ。もっと複雑なこともできるが、あんまり使われることはないように思う。

続いてヒアドキュメントの使い方を見てみよう。次の例では「EOF」という文字列をデリミタとして使っている。

$ cat<<EOF
> 0 1 2 3 4 5 6 7 8 9
> a b c d e f g h i j
> EOF
0 1 2 3 4 5 6 7 8 9
a b c d e f g h i j
$

なお、デリミタからデリミタまでに表示されている行頭の「> 」というのはシェルが表示しているプロンプトで、ユーザーが入力したものではない。この使い方でcatコマンドにこのテキストが流れ込んでいき、そのまま出力されていく。

ヒアドキュメントを使いつつほかのコマンドを接続することも可能となっており、次のような使い方ができる。

$ cat<<EOF | awk "{print NR,\$1,\$5}"
0 1 2 3 4 5 6 7 8 9
a b c d e f g h i j
EOF
1 0 4
2 a e
$

こんな感じで、リダイレクトは使ってみると実感としてわかりやすい機能だ。説明を読むだけだと意味がわからないことも多いが、試してみればすぐ理解できるだろう。リダイレクトはシェルの最も基本的で強力な機能なので、今回紹介したような操作は考えなくてもできるようにしておきたい。

参考資料