Bash Basics | Basicsトップページ | トップページ
標準入出力
UNIXやUnix系のOSにおいて、デバイスごとの複雑な入出力を単純化し扱いやすくするために考えられた抽象化された入出力の概念で、取り扱うデータを逐次的なバイト列のデータストリームとしてEnd Of File(EOF)まで処理するための仕組みです。
プロセスの入出力チャネルとして標準的に定められた(あらかじめ確立している)入出力経路を標準ストリームと言い、標準入力、標準出力、標準エラー出力の3つの入出力があります。
コマンドの実行を行う場合、何らかの情報を入力して、何らかの処理を実行し、その結果を出力するという基本的な流れが考えられます。また、実行時にエラーが発生した場合にそのエラーを示すメッセージを表示するということも考えられます。
このような入出力の標準的な動作を抽象化して、画面、キーボード、ファイルに対する入出力を同じように扱うことができるようにしたものが標準入出力です。標準入出力には、標準入力(stdin)、標準出力(stdout)、標準エラー出力(stderr)の3つがあり、以下のように使用されます。
ファイルディスクリプタ | デフォルトの入出力装置 | 用途など | |
---|---|---|---|
標準入力 | 0 | キーボード | 対象のデータやユーザーからの応答受け付けなどの入力 |
標準出力 | 1 | 端末画面 | 実行結果や処理済みデータ、およびメッセージなどの出力 |
標準エラー出力 | 2 | 端末画面 | 確認や警告メッセージ、エラー情報などの出力 |
ファイルディスクリプタとはコマンドやプログラムなどOS上で動作するソフトウェアがOSの管理しているファイルにアクセスする際、OS内でアクセスを識別するための識別子です。
上述したとおり、ファイルハンドル0から2までは標準入出力としてOSがプロセスを生成した時点で自動的に割り当てられます。このため、プロセスが新しくファイルをオープンすると3から順番にファイルディスクリプタが割り当てられます。
標準入出力はリダイレクションを使ってストリームの入出力先を変更することができます。
具体的には、以下の方法で指定することができます。
表中の[n]は任意のファイルディスクリプタを指定するか省略することが可能です。また、特定のファイルディスクリプタを指定するのではなく、{変数名}の形式でファイルディスクリプタを示す変数を指定することもできます。この場合、リダイレクション演算子が<&-と>&-以外では、10より大きな割当て可能なファイルディスクリプタがシェルによって決定され、変数に代入されます。リダイレクション演算子が<&-と>&-では、その変数のファイルディスクリプタがクローズされます。
リダイレクション演算子はコマンドの後に記述することができ、単純なコマンドでは前や途中に記述することができます。
リダイレクション演算子に続く単語は、ブレース展開、チルダ展開、パラメータ展開、コマンド置換、算術式展開、単語の分割、パス名展開、クォートの削除が行われます。展開の結果、複数の単語になる場合がエラーとなります。
形式 | 説明 | ||
---|---|---|---|
[n]<word | 入力のリダイレクションを行います。 wordを展開した結果のファイルを読み込み専用としてオープンし、ファイルディスクリプタ nで読み込めるようになります。 nが省略された場合、0と見做され標準入力として読み込めるようになります。 | ||
[n]>word | 出力のリダイレクションを行います。 wordを展開した結果のファイルを書き込み専用としてオープンし、ファイルディスクリプタ nで書き込めるようになります。 nが省略された場合、1と見做され標準出力として書き込めるようになります。 ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容はクリアされて新しく書き込まれます。 noclobberが有効化されている場合、wordを展開したファイル名を持つ通常ファイルが既に存在する場合、リダイレクションは失敗します。 実行例:
| ||
[n]>|word | 出力のリダイレクションを行います。 wordを展開した結果のファイルを書き込み専用としてオープンし、ファイルディスクリプタ nで書き込めるようになります。 nが省略された場合、1と見做され標準出力として書き込めるようになります。 ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容はクリアされて新しく書き込まれます。 noclobberが有効化されている場合でも、既に存在するファイルをクリアして新しく書き込みます。 | ||
[n]>>word | 出力のリダイレクトを行います。 wordを展開した結果のファイルを書き込み専用としてオープンし、ファイルディスクリプタ nで書き込めるようになります。 nが省略された場合、1と見做され標準出力として書き込めるようになります。 ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容に追記して書き込まれます。 | ||
[n]<&word | wordを展開した結果が整数の場合、ファイルディスクリプタと見做してwordの展開結果のファイルディスクリプタをnに複製します。 nが省略された場合、0と見做され標準入力として読み込めるようになります。 wordの展開結果の整数が読み込み可能なファイルディスクリプタでなければリダイレクションは失敗します。 wordを展開した結果が整数値でない場合、[n]<wordの形式と同様に入力のリダイレクションを行います。 | ||
[n]>&digit | ファイルディスクリプタ digitをnに複製します。 nが省略された場合、1と見做されファイルディスクリプタ digitが標準出力に複製されます。 digitが書き込み可能なファイルディスクリプタでなければリダイレクションは失敗します。 例えば、1>&2は標準エラー出力が標準出力に複製され、どちらの出力も標準エラー出力となります。 逆に、2>&1は標準出力が標準エラー出力に複製され、どちらの出力も標準出力となります。 下記のスクリプトredirect.shを作成して確認します。
実行例:
| ||
&>word または >&word | 標準出力と標準エラー出力の両方をwordを展開した結果のファイルにリダイレクションを行います。 ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容はクリアされて新しく書き込まれます。 noclobberが有効化されている場合、wordを展開したファイル名を持つ通常ファイルが既に存在する場合、リダイレクションは失敗します。 これは以下と同じ意味となります。 >word 2>&1 | ||
&>>word | 標準出力と標準エラー出力の両方をwordを展開した結果のファイルにリダイレクションを行います。 ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容に追記して書き込まれます。 これは以下と同じ意味となります。 >word 2>>&1 | ||
[n]<&- | ファイルディスクリプタ nをクローズします。 nが省略された場合、0と見做され標準入力をクローズします。 | ||
[n]>&- | ファイルディスクリプタ nをクローズします。 nが省略された場合、1と見做され標準出力をクローズします。 | ||
[n]<>word | 入出力のリダイレクションを行います。 wordを展開した結果のファイルを読み書きの両方でオープンし、ファイルディスクリプタ nで読み書きできるようになります。 nが省略された場合、0と見做され標準入力に対して読み書きができるようになります。 読み込みも書き込みをファイル内の位置を供用することに注意してください。例えば5文字読み込んだ後で書き込みを行うと6文字目以降に書き込みが行われます。 実行例:
| ||
[n]<&digit- | ファイルディスクリプタ digitをnに変更します。digitのファイルディスクリプタはクローズされます。 nが省略された場合、0と見做されdigitを標準入力に変更します。つまり、digitとして開かれていたファイルが標準入力として使用されます。 digitが読み込み可能なファイルディスクリプタでなければ、ファイルディスクリプタ nから読み込もうとした時にエラーが発生します。 下記のスクリプトredirect.shを作成して確認します。
実行例:
ファイルディスクリプタ 3を標準入力に変更しているため、/dev/fd/0がファイル dataを示しています。この時のファイルの権限に読み込み権があることが確認できます。catコマンドを実行するとdataから読み込んでいます。 | ||
[n]>&digit- | ファイルディスクリプタ digitをnに変更します。digitのファイルディスクリプタはクローズされます。 nが省略された場合、1と見做されdigitを標準出力に変更します。つまり、digitとして開かれていたファイルが標準出力として使用されます。digitが書き込み可能なファイルディスクリプタでなければ、ファイルディスクリプタ nへ書き込もうとした時にエラーが発生します。 下記のスクリプトredirect.shを作成して確認します。
実行例:
ファイルディスクリプタ 3を標準出力に変更しているため、/dev/fd/1がファイル dataを示しています。この時のファイルの権限に書き込み権があることが確認できます。lsコマンドもechoコマンドもdataへ書き込んでいます。 |
ファイル | 特別な処理 |
---|---|
/dev/fd/ファイルディスクリプタ | 有効なファイルディスクリプタが指定された場合、指定されたファイルディスクリプタが複製されます。 |
/dev/stdin | ファイルディスクリプタ 0が複製されます。 パイプラインが接続されていない状態では[ -p /dev/stdin ]は真にはなりません。/dev/stdinがパイプラインに接続されている時、/dev/stdinから読み込みのリダイレクションを行うとパイプラインを標準入力として読み込むことができます。 この例ではHelloの文字列がパイプラインを通して/dev/stdinから読み込まれてcatコマンドの入力となっています。 "From pipe"のメッセージはパイプラインを経由せず直接標準出力へ出力されています。 cat < /dev/stdinとすれば、パイプラインを経由せずに直接標準入力からリダイレクションされます。 |
/dev/stdout | ファイルディスクリプタ 1が複製されます。 パイプラインが接続されていない状態では[ -p /dev/stdout ]は真にはなりません。/dev/stdoutがパイプラインに接続されている時、/dev/stdoutへ書き込みのリダイレクションを行うとパイプラインを標準出力として書き込むことができます。 この例ではHelloの文字列がパイプラインを通して/dev/stdoutへ書き込まれてcatコマンドの入力となっています。 "To pipe"のメッセージはパイプラインを経由せず直接標準出力へ出力されています。 echo Hello > /dev/stdoutとすれば、パイプラインを経由せずに直接標準出力へリダイレクションされます。 |
/dev/stderr | ファイルディスクリプタ 2が複製されます。 |
/dev/tcp/ホスト名/ポート番号 | ホスト名とポート番号が有効であれば、対応するソケットに対してTCP接続を試みます。 下記のスクリプトhttpsend.shを作成して確認します。
実行例:
|
/dev/udp/ホスト名/ポート番号 | ホスト名とポート番号が有効であれば、対応するソケットに対してUDP接続を試みます。 |
|
|
|
|
exec {変数名}< ファイルやexec {変数名}> ファイルを実施した場合、OSでは何が行われているのでしょうか? 下記はリダイレクションに関連する部分のシステムコール(OSの処理)を抜粋したものになります。 | |
|
|
exec {変数名}< ファイルの場合、OS内部では上記のようにファイルを読み込み専用でオープンしていることがわかります。 また、catコマンドは外部コマンドであるため、実行時は子プロセスが生成されます。 catコマンドを処理する子プロセスでは標準入力のファイルディスクリプタ 0にファイルディスクリプタ 10を複製し、標準入力のファイルディスクリプタ 0に複製されたファイルの内容を読み込んでいます。 | |
|
|
exec {変数名}> ファイルの場合、OS内部では上記のようにファイルを書き込み専用(ファイルが存在しなければ新たに生成、存在していれば内容を消去)でオープンしていることがわかります。 また、echoコマンドは組み込みコマンドであるため、実行時はシェルと同一のプロセスで実行されます。 echoコマンドを実行する前に標準出力のファイルディスクリプタ 1をファイルディスクリプタ 11に複製しています。 その後、標準出力のファイルディスクリプタ 1にファイルディスクリプタ 10を複製しています。 echoコマンドは標準出力のファイルディスクリプタ 1に文字列を出力することでファイルにデータを出力(書き出す)しています。 echoコマンドの実行後でファイルディスクリプタ 11を標準出力のファイルディスクリプタ 1に複製することで標準出力を元に戻しています。 |
区切り文字を指定することで標準入力の終わりを明示的に指示することができる機能です。
複数行の文字列を簡単に扱ったり、スクリプト内で固定の文字列や変数などを簡単に扱うことができます。
$ tr a-z A-Z << EOT
> Hello World
> Blue
> Red
> Yellow
> EOT
HELLO WORLD
BLUE
RED
YELLOW
$ cat << EOT
> 1 + 1 = $((1+1))
> EOT
1 + 1 = 2
$ cat << EOT
> 現在のディレクトリは$PWDです
> EOT
現在のディレクトリは/home/userです
$ cat << "EOT"
> 1 + 1 = $((1+1))
> EOT
1 + 1 = $((1+1))
$ cat << EOT
> 現在のディレクトリは$PWDです
> EOT
現在のディレクトリは$PWDです
#!/usr/bin/bash
cat <<- EOT
Hello
World
EOT
$ sh test.sh
Hello
World
$ tr a-z A-Z <<< "Hello World"
HELLO WORLD
$ cat <<< "1 + 1 = $((1+1))"
1 + 1 = 2
$ cat <<< "現在のディレクトリは$PWDです"
現在のディレクトリは/home/userです
$ var="blue green red "
$ while read -d" " str; do echo $str; done <<< "$var"
blue
green
red
対話的に実行されたコマンドは標準入力として端末の入力つまり、キーボードを入力として処理を行います。
キーボードを標準入力とした場合、Ctrl+Dを入力することで標準入力の終わりをコマンドに指示することが可能です。
例えば、wcコマンドは標準入力から入力された文字列の行数、単語数、バイト数をカウントして標準入力に出力します。
$ wc << EOT
> Hello World!
> EOT
1 2 13