はうすてんぼぶ

コードかいてて疑問に思ったことや、興味あることをつらつらと暇なときに書く場所、ここはそんな場所

標準ストリーム

今回はUNIXの標準ストリームに関して。

C++でHTTPサーバ作る際に、半強制的に入力とパイプを触らざるを得なかったので、少し知ってる、ってレベル。

C++でいう、

std::cout << "Hello Out-World." << endl; //出力
std:string str = NULL;
std::cin >> str; //入力

は、この標準ストリームと関係あるんだよね。

なんか普通に使っている気もするけど、すごく曖昧な感じなので、実際にコマンド打ちながら勉強してみますよ。

標準入出力

ざっくばらんな概要

早速、echoを使って例を交える。

% echo 'Hello World'
Hello World

この2行目のHello Worldが表示されるまでの一連の流れは、
標準入力(stdin)にキーボードで入力されたHello Worldが投げられて、
標準出力(stdout)で、そのHello Worldをディスプレイへ出力する、となる。

もう一個、エラー出力用の標準エラー出力(stderr)というのがあって、
この3つが標準ストリームと呼ばれる。

標準入力はキーボード、標準出力と標準エラー出力はディスプレイが対応付けられているので、
上のechoに関しても、特に出力先を指定しなかったので勝手にディスプレイに表示された、ってことになる。

このstdin、stdout、stderrはなんだかよくわからん存在なんだけれども、
その3つはファイルディスクリプタで、プロセス起動時に勝手に割り当てられる。

ファイルディスクリプタ

ファイルディスクリプタとは、ファイルの識別子
この識別子ってのが全然ピンとこなくてイマイチ理解できてない感はある。

ファイルを開いた時に勝手に割り当てられる番地で、
この番地に対してデータを投げることを入力、
その番地から返って来るデータが出力、だと俺は思っている。

「データが返って来る」って言うと勝手に戻ってくるみたいだけれども、
こちら側で、ファイルディスクリプタを使って向こうにデータを投げるのと同じで、
向こうのプロセス(stdoutだとOS?)も、こっちの入出力用のファイルディスクリプタを使って結果を投げているので、
第三者から見ると2つのプロセスが入力をしあっているように見えるのだろうか。

ユーザの視点からだと、

10: [キーボード] ---(入力)---> 0: stdin
20: [画面] <---(出力)--- 1: stdout

だが、OSから見ると、

0: stdin <---(出力)--- 10: [キーボード]
1: stdout ---(入力)---> 20: [画面]

こういうこと?よくわからぬ。

リダイレクト

上で、stdinとstdoutはそれぞれキーボードとディスプレイに対応付けられている、と書いた。
この標準入出力先を切り替えることを、リダイレクトするという。

たとえば出力先をファイルにすると、以下のようにlsコマンドの結果をファイルに入れることができる。

% ls > ls_result.txt
% cat ls_result.text
ダウンロード
テンプレート
〜 省略 〜

入力先をファイルに置き換えることもできる。

% echo 'Hello Bob' > file1.txt
% tr "Bob" "Tom" < file1.txt > file2.txt
% echo file2.txt
Hello Tom

tr [set1] [set2]を普通に使うと、

% tr "Bob" "Tom"
Hello Bob (キーボードからの入力)
Hello Tom

こんな感じになるが、「<」と「>」を使うことで、簡単にfile1.txt中の文字列を置き換えた結果をfile2.txtに入れることができる。

リダイレクト記号

>や<はリダイレクト記号といって、これを使って、入出力先を切り替えたりする。
そんなに数が多くないので、参考書を元に表でまとめておく。

記号 説明
標準入力をファイルにする
> 標準出力をファイルにする
>> 標準出力をファイルに追加する
>& エラー情報もファイルに出力する
>>& エラー情報をファイルに追加する
2> エラー情報だけファイルに出力する

>と<はさっき使ったので、>&と>>&の例を取り上げる。

% cat err.txt
エラー出力を書き込むためのファイルです
% tr hoge >& err.txt
% cat err.txt
tr: `hoge' の後にオペランドがありません
% tr fuga >>& err.txt
tr: `hoge' の後にオペランドがありません
tr: `fuga' の後にオペランドがありません

上の例では最初の、

% tr hoge >& err.txt

でerr.txtに出力したため、ファイルにもともと書いてあった内容は消えてエラー内容だけが書きこまれた。
次の、

% tr fuga >>& err.txt

でerr.txtに追加出力したので、一度目のエラー内容の後に、新しいエラー内容が書きこまれているのが分かる。

- : 標準入力を指定する

「-」は標準入力を明示的に指定する際に使われる。省略すると標準入力になるが、場合に寄っては明示しないと困るときもある。
stdinの代名詞見たいな感じに使う。

以下のように、ファイルの先頭に追記したい際は、catの第一引数に標準入力を指す「-」を入れればおっけー。

% cat - err.txt > file3.txt
# エラー出力結果
% cat file3.txt
# エラー出力結果
tr: `hoge' の後にオペランドがありません
tr: `fuga' の後にオペランドがありません

これはcatコマンドの機能ではなく、シェルの機能である、という点に注意。

set -o noclobber: リダイレクトを抑止する

リダイレクトは便利だけれども、出力先が既存ファイルだと一気に危険性が増す。

% echo ';)' > 卒論.txt

ってうっかりやっただけで卒論を無力化できるのだから何それ恐い。

こういう恐ろしいことを防ぐためには、
set -o noclobberを使って前もってリダイレクト書き込み禁止にしておくと良いみたい。
再度、リダイレクト書き込みを許可する場合には、set +o noclobberを実行するだけ。

% set -o noclobber
% echo ';)' > 卒論.txt
zsh: ファイルが存在します: 卒論.txt
% set +o noclobber
% echo ';)' > 卒論.txt
% cat 卒論.txt
;)

これ.zshrcに書いておいたほうが安全なのでは!?

パイプ

パイプ(|)を使うと、コマンドライン上だと「コマンドの出力を、別のコマンドの入力に流す」ことができる。
それまで、一度ファイルに出力していたようなものも、パイプを利用すると簡単にかけるので便利。

% ls | wc -l
33

これは、lsで出力した結果の行数をwcで出力しているってだけ。

もしパイプを使わないと。

% ls > tmp.txt
% wc -l < tmp.txt
% rm tmp.txt

のように、一時的にファイルを作らないとできない。

つまりパイプを使うと、それまで出力先がディスプレイかファイルに限定されていた文字列を、別のコマンドの入力として流すことができる、ってことになる。

リダイレクトは、文字列がダバダバ流れるパイプがそれまで画面に繋がっていたものを切り替えて、別のファイルに繋げるもの。

% echo "Hello World" ------ X [画面] Hello World
                                       '---- 0 [ファイル] file.txt

パイプは、文字列がダバダバ流れるパイプを別のコマンドに流すのだからイメージとしては、

% echo "Hello World" ---
% cat                            ---'

みたいな感じになる。

おわりに

標準ストリームに関してはだいたいこんな感じ。

リダイレクトはコマンドの出力をファイル書きだすときに使うし、パイプはコマンド間で連携をするときに使うし、
これはもっと早く勉強しておくべきだった。

あと、grepとパイプはえらく相性が良いというか、やたらと何かとセットで出てくるわけがわかった。
grepで見つけたファイルの中身をさらに検索しにいったり正規表現で置換する、とかはパイプを使えば一行で済む!!良い!

どこか間違っているとこがあったらご指摘お願いします。

参考資料

Amazon.co.jp: 改訂 新Linux/UNIX入門: 林 晴比古: 本, Chapter19 リダイレクト操作(p258-p-265), Chapter 20 パイプとフィルタ(p268-p270).
標準入力、標準出力、標準エラー出力、パイプとは ?, http://www.creatology.jp/unix/outin.html.
UNIXの標準入出力とリダイレクション, http://www.rsch.tuis.ac.jp/~ohmi/literacy/stdio.html.
標準入出力使用のすすめ, http://www-or.amp.i.kyoto-u.ac.jp/algo-eng/db/stdinout.html.
パイプによるプロセス間通信 [Linux] - Web/DB プログラミング徹底解説, http://keicode.com/note/lin07.php.