はじめに
これまでの連載では、Linuxの標準入力や標準出力を繋ぎかえる「パイプ」や「リダイレクト」を何度も利用してきましたが、これらの仕組みについてはしっかり体系立てて解説をしていませんでした。そのため、普段からコマンドは利用しているものの、このあたりを
- なんとなく「|」
- とりあえず「>」
- 雰囲気で「2>&1」
といった使い方をしている方も多いのではないでしょうか。
そこで今回は、改めてLinuxの「標準入力」「標準出力」の基本を整理します。「リダイレクト」や「パイプ」の仕組みをしっかりと理解していきましょう。
「標準入力」「標準出力」とは
Linuxのコマンドは「キーボードから入力を受け取り」「ディスプレイに結果を表示」します。しかし、これは厳密には正しくありません。Linuxの入出力は「標準入力(standard input)」と「標準出力(standard output)」という、抽象的な仕組みとして扱われています。コマンドはキーボードやディスプレイといったデバイスを直接扱っているのではなく、標準入力からデータを受け取り、標準出力へデータを出力するという動作をしています。単にデフォルトの標準入力がキーボードに、標準出力がディスプレイに繋がっているというだけなのです。
なぜこのような面倒な仕組みになっているかと言うと、標準入出力を自由に切り替え可能にするためです。標準入出力という抽象化を一段挟むことで、実際のデータ入力元や出力先が変更されてもプログラムはその違いを意識する必要がなくなります。そして、実際に標準入出力の切り替えを行うのが今回解説する「リダイレクト」や「パイプ」です。
リダイレクト
リダイレクトとは「標準入出力の接続先を変更」する機能です。出力リダイレクトと入力リダイレクトに分けて説明します。
出力リダイレクトとは
最もよく使われるのが、出力リダイレクトです。単にリダイレクトと言った場合、だいたいは出力リダイレクトのことを指すと思って良いでしょう。「>」に続いて、出力したいファイルを指定して使います。
$ ls /usr/bin > ~/list.txtここでは「ls /usr/bin」コマンドを実行しています。これは「/usr/bin」ディレクトリにあるファイルの一覧を、標準出力(ディスプレイ)に出力するコマンドです。もうお馴染みですね。その後ろにある「>」が出力リダイレクトの指定。そして「~/list.txt」がリダイレクト先のファイル名となります。つまり、このコマンドを実行すると本来ディスプレイに出力されるはずのファイル一覧が、ホームディレクトリ内のlist.txtというファイルに書き込まれることになります。
「>」と「>>」の違い
また、出力リダイレクトには「>>」というバージョンも存在します。「>」がリダイレクト先のファイルを新規作成するのに対し「>>」は既存のファイルに追記(存在しなかった場合は「>」と同じく新規作成)します。出力先を常に初期化したいのか、あるいは追記したいのかによって使い分けましょう。以下のコマンドを実行すると「/var/log」以下にあるファイル一覧をlist.txtの末尾に追記します。
$ ls /var/log >> ~/list.txtよくある失敗例
よくある失敗例も紹介しましょう。以下は「example.log」というログファイルから「error」という文字を含む行を検索し、同じファイルに上書きしようとしています。つまりエラーが発生した行だけを抽出し、無関係な行を掃除した上でファイルを更新しようとしているわけですが、このコマンドは期待通りに動きません。このコマンドを実行するとexample.logの中身は空になってしまいます。
$ grep -i error example.log > example.logこれはリダイレクト先が初期化される順序によるものです。上記コマンドが実行されると
- シェルがリダイレクト先のexample.logを空の状態で新規作成する
- grepコマンドが実行される
という順番で処理が行われるため、grepコマンドの検索対象は空の新規ファイルになってしまいます。リダイレクトはコマンドではなく「コマンドを起動するシェルが行う」という点を意識すると良いでしょう。
入力リダイレクトとは
入力リダイレクトはその名の通り、標準入力をファイルに切り替えるリダイレクトです。「<」に続いてファイル名を指定すると、そのファイルの中身を標準出力に流し込んだことと同等になります。これが威力を発揮するのは「標準入力からしか入力を受け付けないコマンド」を使う場合です。具体例を挙げて解説しましょう。
grepコマンドは指定された文字列を検索するコマンドですが、標準入力に対しても、ファイルを指定しても、同じように実行できます。以下の2つのコマンドは、どちらもexample.txtにerrorという文字列で検索をかけられます。grepのようなコマンドはコマンド自体がファイルを直接開けるため、入力リダイレクトを使う意味はありません。
$ grep error example.txt
$ cat example.txt | grep error対して「tr」というコマンドがあります。これは標準入力から受け取った文字列に対し、文字の置換や削除を行うコマンドです。ポイントは「trはファイルを直接開くことができず、標準入力経由でしか入力を受け取れない」というところです。以下はexample.txt内のアルファベット小文字をすべて大文字に変換する例ですが、trはファイルを直接開けないため、catからパイプして渡す必要があります(パイプについては後述)。
$ cat example.txt | tr '[:lower:]' '[:upper:]'しかし入力リダイレクトを使えば、以下のようにファイルを標準入力に流し込めるのです。
$ tr '[:lower:]' '[:upper:]' < example.txtcatとパイプでも代用できるため、入力リダイレクトが絶対に必要かと言われればそうではありません。出力リダイレクトと比較しても利用頻度が低いのは事実ですが、入力リダイレクトを使いこなせばコマンドをよりスマートに記述できます。
シェル全体の出力をリダイレクトする
「exec」コマンドを使うと、そのシェル全体の出力先を変更できます。例えば、以下のコマンドを実行すると、これ以降にそのシェルで実行されるコマンドの標準出力はすべて「~/output.log」に書き込まれるようになります。
$ exec > ~/output.log一般的な対話的シェルで使う機能ではありませんが、シェルスクリプト内でログを取りたい場合などには便利です。例えば、Cronで定期実行するスクリプトなどでは、
- 出力をディスプレイには一切出したくない
- でもログは残したい
というケースがよくあります。このようなときは都度コマンドの末尾でリダイレクトを指定するより、スクリプトの先頭でexecしてしまう方が便利です。リダイレクトの指定が一箇所で済むため、出力先を変更するのも簡単になります。
なお、このリダイレクトは以下のコマンドで元に戻すこともできます*。
$ exec > /dev/tty*: この方法で戻せるのは制御端末を持つシェルに限ります。Cronから実行されたスクリプトは端末に関連づいていないため/dev/ttyは使えません。また通常は使う必要もありません。
実は2種類ある出力を区別する
実は、出力には標準出力とは別に「標準エラー出力」があります。デフォルトではどちらも同じようにディスプレイに(混ざって)表示されるため普段はあまり意識しませんが、これらは個別にリダイレクトできるのです。そのため「標準出力は表示したくないがエラー出力だけは記録したい」「それぞれを分けてファイルにリダイレクトしたい」といった制御ができます。
標準エラー出力は「2」で表します。以下の例では、あるコマンドの標準出力はディスプレイに出力し、エラーはerror.logに出力しています。
$ コマンド 2> error.log以下の例では、標準出力とエラー出力の双方をall.logに出力します。「1」は標準出力を表す数字で、「2>&1」は「2(標準エラー出力)を1(標準出力)と同じ(&)場所にリダイレクト(>)する」という意味になります。2つの出力をマージする際の決まり文句として、この記法をそのまま覚えておきましょう。
$ コマンド > all.log 2>&1ここで突然登場した「1」や「2」という数字は、「ファイルディスクリプタ」と呼ばれるものです。これはプロセスの読み書き対象となるファイルをOSが識別するための番号で、以下の3つは予約されています。
| ファイルディスクリプタ番号 | 意味 |
| 0 | 標準入力 |
| 1 | 標準出力 |
| 2 | 標準エラー出力 |
ファイルディスクリプタについては本筋と外れてしまうため、今回は詳しく解説しません。とりあえず「出力とエラー出力が分かれている」こと、「標準入出力には固定の番号が振られており番号でそれらを制御できる」ことを覚えておきましょう。
パイプ
あるコマンドの標準出力を後続のコマンドの標準出力に繋ぐのが「パイプ」です。説明が前後しましたが、これまでに何度も利用してきた機能ですね。パイプを使うと、標準入出力を経由して複数のコマンドを連続実行し、データを順次加工できます。パイプは「|」で2つのコマンドを繋いで記述します。以下のコマンドを実行してみましょう。
$ ls /usr/bin | grep ls
false
grub-menulst2cfg
inetutils-telnet
ls
lsattr
lsb_release
lsblk
lscpu lshw
(...略...)この例では、まずlsコマンドが「/usr/bin」以下にあるファイル一覧を標準出力に出力します。この出力がgrepコマンドの標準入力に繋がります。そしてgrepコマンドはlsコマンドの出力に対し「ls」という文字列でフィルタをかけています。結果として「/usr/bin」以下にあるファイル名に「ls」を含むコマンドのみがgrepの標準出力(ここではディスプレイ)に出力されます。つまりパイプを活用すると複数のコマンドをまとめて実行して一気にデータを加工できるというわけです。grepやsortのようなコマンドを繋いでデータを望みの形に加工するのは、コマンドラインの定番テクニックです。
Linuxのコマンドはシンプルなコマンドを組み合わせて複雑な処理を作ることから、よく「レゴブロックのようだ」と言われます。そしてその中心にあるのがパイプです。コマンドを使いこなすなら絶対にマスターしたい機能です。
リダイレクトにありがちな罠
サーバーの初期設定をする際、システム設定ファイルをコマンドで書き換えたいことはよくあります。以下は、/etc/example.confというファイルに「key=value」という文字列を書き込む例ですが、以下のようにこのコマンドは失敗します。
$ echo 'key=value' >> /etc/example.conf
bash: /etc/example.conf: Permission denied理由は単純に権限の問題です。/etcディレクトリ以下にはroot権限がないと書き込みが行えません。そしてroot権限を得るためにはsudoを使います。それでは、今度はsudoを付けてechoコマンドを実行してみましょう。
$ sudo echo 'key=value' >> /etc/example.conf
bash: /etc/example.conf: Permission denied同様に、こちらもエラーとなってしまいました。root権限でコマンドを実行しているのに「権限がない」と怒られてしまっています。なぜでしょうか。
これは、実は多くの人が経験する「あるある」です。上の例では、たしかにechoコマンドはroot権限で実行されていますが、root権限で実行されているのはあくまで「標準出力に文字列を出力する」だけなのです。先ほどもgrepの失敗例の部分で触れましたが、リダイレクトはコマンドではなくシェルが行っています。そのためいくらコマンドをroot権限で実行しようとも、その前段階のシェルが一般ユーザー権限で起動している以上、権限不足で/etc以下のファイルに書き込むことはできないのです。
この問題を解決するのが「tee」コマンドです。teeは標準入力を受け取り、ファイルに書き込むと同時に標準出力にも出力します。以下のように使います。
$ echo 'key=value' | sudo tee -a /etc/example.confファイルへの書き込みはteeコマンドが担当するわけですから、teeコマンドをsudo付きで実行してあげれば/etc以下のファイルでも書き換えられるのです。書き込みにroot権限が必要な場合は単なるリダイレクトではなく、こうした小技を挟む必要があります。覚えておきましょう。
おわりに
標準入力・標準出力は、Linuxの最も基本的な概念の1つです。しかし、同時に「なんとなくコピペで」「理解せずに」使えてしまう部分でもあります。今回改めて整理したことで「パイプは何をしているのか」「なぜリダイレクトが便利なのか」という部分が少しクリアになったのではないでしょうか。
Linuxのコマンドは、すべてこの仕組みの上に成り立っています。仕組みを理解すれば、複雑なコマンドの組み合わせも自然と理解できるようになってくるはずです。
