警告

本資料は参考情報として提供されています。内容についてのご質問には必ずしもお答えできない可能性がございます。

3. ファイルの読み書きとコマンドの繋ぎ方

ファイルの入出力とコマンドの連携方法について記述します。

3.1. 標準入力と標準出力

UNIXやLinuxのプログラム(プロセス)は、データを入出力するためのチャンネル(ストリーム)を標準で3つ備えています。それぞれ、標準入力標準出力標準エラー出力と呼ばれています。

../../_images/stdio.svg../../_images/stdio1.svg
標準入力(stdin)

データの受け入れ元。例えばキーボードやファイル。

標準出力(stdout)

データの出力先。例えばディスプレイやファイル。

標準エラー出力(stderr)

標準出力と同じだが、エラーメッセージの表示に使用される。例えば、標準出力がファイルに向いていても、標準エラー出力がディスプレイに接続されていれば、ユーザーはエラーに気づくことができる。

Linuxでは、これら標準入出力の接続先を操作することで、ファイルへの入出力やプログラム間の連携が容易にできるようになっています。

3.2. リダイレクト

リダイレクト早見表

> file

標準出力をファイルに出力

>> file

標準出力をファイルに追記

2> file

標準エラー出力をファイルに出力

< file

標準入力にファイルを入力

リダイレクトは標準入出力を特定のファイルに向ける仕組みです。ここではcatコマンドを例にリダイレクトの動作を確認します。

3.2.1. リダイレクトしない場合

catコマンドを引数なしで実行して、Hello, world!と入力します。

1$ catEnter
2Hello, world!Enter
3Hello, world!Ctrl+D
4$

catコマンドは引数が指定されなかった場合、標準入力のデータをそのまま標準出力に流します。ここでは、ユーザーが入力した文字列(2行目)がそのまま出力(3行目)されています。

ヒント

Ctrl+Dは標準入力の終わりをプログラムに伝えるための操作です。

3.2.2. 標準出力先をリダイレクトする

次に、catコマンドに>を加えて標準出力をファイルに指定します。

1$ cat > hello.txtEnter
2Hello, world!Enter
3Ctrl+D
4$ cat hello.txt
5Hello, world!

>の後にはファイル名を指定します。これにより、標準出力先がディスプレイからファイルに変更されます。そのため、catコマンドはディスプレイに入力を出力しません(3行目)。hello.txtを確認すると、標準入力へ入力したデータが書き込まれています。

危険

標準出力先のファイルが既に存在する場合、上書きされます。

3.2.3. 標準入力をリダイレクトする

最後に、標準入力のリダイレクトです。

1$ cat < hello.txt
2Hello, world!
3$

ファイルから入力が行われたため、キーボードからの入力は求められません。

注釈

catコマンドの場合は、cat < hello.txtcat hello.txtは同じ動作をします。

標準入力と出力を同時に行うこともできます。

1$ cat < hello.txt > hello2.txt
2$ cat hello2.txt
3Hello, world!

警告

標準入力と出力に同じファイルを指定することはできません。標準出力に指定したファイルは、コマンド が実行される前にシェルが空のファイルに置き換えてしまうためです。

3.2.4. 追記と標準エラー出力のリダイレクト

>の代わりに>>を使うことで、ファイルへの追記を行うことができます。

1$ cat < hello.txt >> hello2.txt
2$ cat hello2.txt
3Hello, world!
4Hello, world!

2>は標準エラー出力をリダイレクトします。

$ cat nosuchfile.txt
cat: nosuchfile.txt: No such file or directory
$ cat nosuchfile.txt > copy.txt  # エラーメッセージは標準エラー出力に出力されているで、標準出力をリダイレクトしてもディスプレイに表示される。
cat: nosuchfile.txt: No such file or directory
$ cat nosuchfile.txt 2> errmsg.txt
$ cat errmsg.txt
cat: nosuchfile.txt: No such file or directory

ヒント

スクリプトやプログラムを自作する際も、標準出力と標準エラー出力を使い分けることによって、解析結果とエラーメッセージが混ざることを防いだり、エラーに気づきやすいプログラムを作成できます。

2>&1と書くことにより、標準エラー出力を標準出力と同じ出力先にリダイレクトさせることができます。

$ cat nosuchfile.txt >output.txt 2>&1
$ cat output.txt
cat: nosuchfile.txt: No such file or directory
$ cat nosuchfile.txt 2>&1 >output.txt  # `2>&1`を先に書くと、標準エラー出力を標準出力に向けてから、標準出力をファイルに向けたことになる。
cat: nosuchfile.txt: No such file or directory

bashでは>output.txt 2>&1をまとめて&>output.txtもしくは>&output.txtと書けます。

注釈

< input.txt> output.txtは、0< input.txt1> output.txtの省略記法になっています。01はファイルディスクリプタを指定しており、0, 1, 2はそれぞれ標準入力、標準出力、標準エラー出力が割り当てられています。

2>&1はファイルディスクリプタ1番を複製して2番に割り当てています。

3.3. パイプ

ターミナルでの処理では、あるデータに対して複数のコマンドを連続で適用したいことが多々あります。例えば、あるタブ区切りのファイルから

  1. chr1を含む行を取り出し、

  2. 4列目のみを抜き出し、

  3. アルファベット順にソートして、

  4. 先頭の10行のみを取り出す

コマンドは次のように書けます(各コマンドの詳細は次章をご覧ください)。

1$ grep chr1 list.tab > list_chr1.tab
2$ cut -f 4 list_chr1.tab > list_chr1_4.tab
3$ sort list_chr1_4.tab > list_chr1_4_sorted.tab
4$ head list_chr1_4_sorted.tab
../../_images/without_pipe.svg ../../_images/without_pipe1.svg

このような書き方でも目的の処理は行えますが、1コマンドを実行する度に中間ファイルを出力する必要があり煩雑です。このような処理を効率的にこなすためにフィルタとパイプという仕組みが用意されています。

リダイレクトの例で用いたcatコマンドは1つ以上のファイルの内容を続けて出力するコマンドですが、ファイルが指定されなかった場合は標準入力から入力を受け取るようになっています。このように、標準入力から入力を受け取り、結果を標準出力へ出力するコマンドやプログラムをフィルタと呼びます。特に、次章で紹介するテキスト処理を行うコマンドの多くがフィルタとして機能します。

フィルタの最大のメリットはコマンド間の連携が容易になることです。すなわち、あるコマンドの標準出力を、次のコマンドの標準入力とつなぐことによって、中間ファイルを介さずに同じ処理ができます。これを実現するのがパイプで、シェル上では|記号を使って表記します。先程の例と同じ処理は、パイプを使うと次のように書くことができます。

$ grep chr1 list.tab | cut -f 4 | sort | head
../../_images/with_pipe.svg../../_images/with_pipe1.svg

CLI環境でよく使われるコマンドの1つ1つは機能的には単純なことが多いですが、パイプで複数のコマンドを連携させることにより、複雑な処理を容易に実現できるように工夫されています。また、スクリプトやプログラムを作成する場合も、フィルタとして動作するよう書くことにより、既存のコマンドとの連携が可能になります。例えば、自作のプログラムの中でデータをソートするより、sortコマンドでソートしたデータを入力として受け付けた方が楽かもしれません。

危険

パイプを用いたスクリプトをジョブスケジューラで実行する場合は、同時に実行されるコマンドの数に注意が必要です。

例えば、sort --parallel 1 file.txt | headのようなコマンドでは、sortコマンドが処理を終えるまで結果が出力されないため、2つのコマンドが同時に実行される時間はほとんどありませんが、grep x file.txt | sort --parallel 1のような場合、grepコマンドは結果を逐次出力するため、grepコマンドとsortコマンドが同時に働くことになります。このようなコマンドを実行するスクリプトが、CPUコア数1を指定されたジョブで実行される場合、計算ノードが過負荷になるおそれがあります。

パイプ等を使うことにより、複数のプロセスが同時に実行されるコマンドを含むスクリプトは、ジョブ投入前にテストを実施することをおすすめします。

ヒント

フィルタとして動作するコマンドの多くは、ファイル名が指定されなかった場合は標準入力から入力を受け取るよう動作しますが、ファイル名に-(ハイフン)を与えることで明示的に指定することができます。

$ cat 1.txt
1
$ echo 2 | cat 1.txt  # ファイルが指定されているので標準入力は無視される
1
$ echo 2 | cat 1.txt -  # `-`を指定することで標準入力をファイルのように参照する
1
2
$ echo 2 | cat - 1.txt
2
1

-を標準入力として扱うかはプログラムの実装によりますが、多くのコマンドがこの慣例に倣っています。ファイル名を省略した時に標準入力を受け取らないコマンドも、ファイル名として-を指定すると期待した動作をする場合があります。

ヒント

フィルタとして動くコマンドでは、command < filecat file | commandがほぼ同じ意味になります。例えばcommand < in.txt > out.txtのように複数のリダイレクトがあると読みにくくなる場合がありますが、cat in.txt | command > out.txtの場合は実際の処理に合った順でファイルとコマンドを記述できます。

3.4. tee

teeはリダイレクトやパイプと組み合わせて使われるコマンドです。teeコマンドは、標準入力から受け取ったデータを、指定されたファイルと標準出力に出力するコマンドです。

tee [Files...]

例えば、コマンドの結果をファイルに書き込むのと同時に、画面にも出力して内容を確認したい場合によく用いられます。

$ grep chr1 list.tab | tee list_chr1.tab  # `tee`が`list_chr1.tab`に標準入力を書き込む。`tee`の標準出力はディスプレイのままなので、grepコマンドの結果はディスプレイにも出力される。
$ grep chr1 list.tab | tee out1.tab > out2.tab  # 結果を複数のファイルに出力する
$ grep chr1 list.tab | tee out1.tab out2.tab    # 同上