Bash Basics | Basicsトップページ | トップページ

標準入力(stdin)、標準出力(stdout)、標準エラー出力(stderr)

コマンドの実行を行う場合、何らかの情報を入力して、何らかの処理を実行し、その結果を出力するという基本的な流れが考えられます。また、実行時にエラーが発生した場合にそのエラーを示すメッセージを表示するということも考えられます。
このような入出力の標準的な動作を抽象化して、画面、キーボード、ファイルに対する入出力を同じように扱うことができるようにしたものが標準入出力です。標準入出力には、標準入力(stdin)、標準出力(stdout)、標準エラー出力(stderr)の3つがあり、以下のように使用されます。

  ファイルディスクリプタ デフォルトの入出力装置 用途など
標準入力 0 キーボード 対象のデータやユーザーからの応答受け付けなどの入力
標準出力 1 端末画面 実行結果や処理済みデータ、およびメッセージなどの出力
標準エラー出力 2 端末画面 確認や警告メッセージ、エラー情報などの出力

例えば、lsコマンドを実行するとディレクトリ内の一覧を画面に表示しますが、この画面に表示された一覧が標準出力に出力された内容となります。
同じようにreadコマンドは標準入力からデータを読み込んでフィールドごとに分割します。
もし実行時に権限がないなどのエラーが発生するとそのエラーの内容を示すメッセージが表示されますが、そのメッセージは標準エラー出力に出力されたものです。
bashでは内部的にファイルディスクリプタ 255を保持しています。これは標準入力、標準出力、標準エラー出力がブロックされた場合に代替えとして端末に入出力するために使用されます。ファイルディスクリプタ 255は内部用のため、使用すべきではありません。


ファイルディスクリプタ

ファイルディスクリプタとはコマンドやプログラムなどOS上で動作するソフトウェアがOSの管理しているファイルにアクセスする際、OS内でアクセスを識別するための識別子です。
上述したとおり、ファイルハンドル0から2までは標準入出力としてOSがプロセスを生成した時点で自動的に割り当てられます。このため、プロセスが新しくファイルをオープンすると3から順番にファイルディスクリプタが割り当てられます。


リダイレクション

標準入出力はリダイレクションを使ってストリームの入出力先を変更することができます。
具体的には、以下の方法で指定することができます。

表中の[n]は任意のファイルディスクリプタを指定するか省略することが可能です。また、特定のファイルディスクリプタを指定するのではなく、{変数}の形式でファイルディスクリプタを示す変数を指定することもできます。この場合、リダイレクション演算子が<&-と>&-以外では、10より大きな割当て可能なファイルディスクリプタがシェルによって決定され、変数に代入されます。リダイレクション演算子が<&-と>&-では、その変数ファイルディスクリプタがクローズされます。

リダイレクション演算子はコマンドに記述することができ、単純なコマンドでは途中に記述することができます。
リダイレクション演算子に続く単語は、ブレース展開チルダ展開パラメータ展開コマンド置換算術式展開単語の分割パス名展開クォートの削除が行われます。展開の結果、複数の単語になる場合がエラーとなります。

形式説明
[n]<word入力のリダイレクションを行います。
wordを展開した結果のファイルを読み込み専用としてオープンし、ファイルディスクリプタ nで読み込めるようになります。
nが省略された場合、0と見做され標準入力として読み込めるようになります。
[n]>word出力のリダイレクションを行います。
wordを展開した結果のファイルを書き込み専用としてオープンし、ファイルディスクリプタ nで書き込めるようになります。
nが省略された場合、1と見做され標準出力として書き込めるようになります。
ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容はクリアされて新しく書き込まれます。
noclobberが有効化されている場合、wordを展開したファイル名を持つ通常ファイルが既に存在する場合、リダイレクションは失敗します。

実行例:
$ echo 'Hello World!' > memo.txt
$ cat memo.txt 
Hello World!
$ date -x
date: 無効なオプション -- 'x'
Try 'date --help' for more information.
$ date -x 2> memo.txt 
$ cat memo.txt 
date: 無効なオプション -- 'x'
Try 'date --help' for more information.
[n]>|word出力のリダイレクションを行います。
wordを展開した結果のファイルを書き込み専用としてオープンし、ファイルディスクリプタ nで書き込めるようになります。
nが省略された場合、1と見做され標準出力として書き込めるようになります。
ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容はクリアされて新しく書き込まれます。
noclobberが有効化されている場合でも、既に存在するファイルをクリアして新しく書き込みます。
[n]>>word出力のリダイレクトを行います。
wordを展開した結果のファイルを書き込み専用としてオープンし、ファイルディスクリプタ nで書き込めるようになります。
nが省略された場合、1と見做され標準出力として書き込めるようになります。
ファイルが存在しない場合、新規に作成されますが、ファイルが既に存在している場合、既存の内容に追記して書き込まれます。
[n]<&wordwordを展開した結果が整数の場合、ファイルディスクリプタと見做してwordの展開結果のファイルディスクリプタをnに複製します。
nが省略された場合、0と見做され標準入力として読み込めるようになります。
wordの展開結果の整数が読み込み可能なファイルディスクリプタでなければリダイレクションは失敗します。
wordを展開した結果が整数値でない場合、[n]<wordの形式と同様に入力のリダイレクションを行います。
[n]>&digitファイルディスクリプタ digitをnに複製します。
nが省略された場合、1と見做されファイルディスクリプタ digitが標準出力に複製されます。
digitが書き込み可能なファイルディスクリプタでなければリダイレクションは失敗します。

例えば、1>&2は標準エラー出力標準出力に複製され、どちらの出力も標準エラー出力となります。
逆に、2>&1は標準出力標準エラー出力に複製され、どちらの出力も標準出力となります。

下記のスクリプトredirect.shを作成して確認します。
#!/usr/bin/bash

echo "これは標準出力" > /dev/stdout;
echo "これは標準エラー出力" > /dev/stderr;

実行例:
$ ./redirect.sh > file
これは標準エラー出力
$ ./redirect.sh 2> file
これは標準出力
$ { ./redirect.sh 1>&2; } > file
これは標準出力
これは標準エラー出力
$ { ./redirect.sh 2>&1; } > file
  1. 標準出力のみリダイレクションしているため、標準エラー出力のメッセージはリダイレクションされずに画面に出力されています。
  2. 標準エラー出力のみリダイレクションしているため、標準出力のメッセージはリダイレクションされずに画面に出力されています。
  3. {}の内側で標準エラー出力標準出力に複製しています。
    {}の外側でそれをさらに標準出力のみファイルにリダイレクションしています。
    内側のリダイレクションの結果、すべてのメッセージは標準エラー出力へ出力されているため、画面にメッセージが出力され、ファイルには何も書き込まれません。
  4. {}の内側で標準出力標準エラー出力に複製しています。
    {}の外側でそれをさらに標準出力のみファイルにリダイレクションしています。
    内側のリダイレクションの結果、すべてのメッセージは標準出力へ出力されているため、画面にメッセージは出力されず、ファイルに書き込まれます。
&>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文字目以降に書き込みが行われます。

実行例:
$ echo "Hello World" > data
$ echo "Hello Wondeful World" >> data
$ cat data
Hello World
Hello Wondeful World
$ exec 3<> data
$ read line <&3
$ echo -n "Welcome to the" >&3
$ exec 3>&-
$ cat data
Hello World
Welcome to the World
  1. リダイレクションを使ってファイル dataに"Hello World"と1行書き込んでいます。
  2. ファイル dataに"Hello Wonderful World"と追記しています。
  3. catコマンドで内容を確認しています。
  4. execでリダイレクションのみ記述することで現在のシェルの実行環境でdataをファイルディスクリプタ 3としてオープンしています。
  5. readファイルディスクリプタ 3から1行読み込んでいます。
  6. ファイルディスクリプタ 3に"Welcom to the"と書き込んでいます。
    2行目の行頭の位置から上書きされます。
  7. ファイルディスクリプタ 3をクローズしています。
  8. catコマンドで内容を確認しています。
    2行目が上書きされていることが確認できます。
[n]<&digit-ファイルディスクリプタ digitをnに変更します。digitのファイルディスクリプタはクローズされます。
nが省略された場合、0と見做されdigitを標準入力に変更します。つまり、digitとして開かれていたファイルが標準入力として使用されます。
digitが読み込み可能なファイルディスクリプタでなければ、ファイルディスクリプタ nから読み込もうとした時にエラーが発生します。

下記のスクリプトredirect.shを作成して確認します。
#!/usr/bin/bash

echo "Hello World" > data
exec 3< data
ls -l /dev/fd/
exec <&3-
ls -l /dev/fd/
cat

実行例:
$ ./redirect.sh 
合計 0
lrwx------ 1 user1 user1 64  4月 10 16:43 0 -> /dev/pts/7
lrwx------ 1 user1 user1 64  4月 10 16:43 1 -> /dev/pts/7
lrwx------ 1 user1 user1 64  4月 10 16:43 2 -> /dev/pts/7
lr-x------ 1 user1 user1 64  4月 10 16:43 3 -> /home/user1/data
lr-x------ 1 user1 user1 64  4月 10 16:43 4 -> /proc/27634/fd
合計 0
lr-x------ 1 user1 user1 64  4月 10 16:43 0 -> /home/user1/data
lrwx------ 1 user1 user1 64  4月 10 16:43 1 -> /dev/pts/7
lrwx------ 1 user1 user1 64  4月 10 16:43 2 -> /dev/pts/7
lr-x------ 1 user1 user1 64  4月 10 16:43 3 -> /proc/27635/fd
Hello World
ファイルディスクリプタ 3を標準入力に変更しているため、/dev/fd/0がファイル dataを示しています。この時のファイルの権限に読み込み権があることが確認できます。
catコマンドを実行するとdataから読み込んでいます。
[n]>&digit-ファイルディスクリプタ digitをnに変更します。digitのファイルディスクリプタはクローズされます。
nが省略された場合、1と見做されdigitを標準出力に変更します。つまり、digitとして開かれていたファイルが標準出力として使用されます。digitが書き込み可能なファイルディスクリプタでなければ、ファイルディスクリプタ nへ書き込もうとした時にエラーが発生します。

下記のスクリプトredirect.shを作成して確認します。
#!/usr/bin/bash

echo "Hello World" > data
exec 3>> data
ls -l /dev/fd/
exec >&3-
ls -l /dev/fd/
echo "Welcome to the World"

実行例:
$ ./redirect4.sh 
合計 0
lrwx------ 1 user1 user1 64  4月 10 17:39 0 -> /dev/pts/7
lrwx------ 1 user1 user1 64  4月 10 17:39 1 -> /dev/pts/7
lrwx------ 1 user1 user1 64  4月 10 17:39 2 -> /dev/pts/7
l-wx------ 1 user1 user1 64  4月 10 17:39 3 -> /home/user1/data
lr-x------ 1 user1 user1 64  4月 10 17:39 4 -> /proc/29594/fd
$ cat data
Hello World
合計 0
lrwx------ 1 user1 user1 64  4月 10 17:39 0 -> /dev/pts/7
l-wx------ 1 user1 user1 64  4月 10 17:39 1 -> /home/user1/data
lrwx------ 1 user1 user1 64  4月 10 17:39 2 -> /dev/pts/7
lr-x------ 1 user1 user1 64  4月 10 17:39 3 -> /proc/29595/fd
Welcome to the World
ファイルディスクリプタ 3を標準出力に変更しているため、/dev/fd/1がファイル dataを示しています。この時のファイルの権限に書き込み権があることが確認できます。
lsコマンドもechoコマンドもdataへ書き込んでいます。

bashは以下のファイル名がリダイレクトで使用されると特別に扱います。
ファイル特別な処理
/dev/fd/ファイルディスクリプタ有効なファイルディスクリプタが指定された場合、指定されたファイルディスクリプタが複製されます。
/dev/stdinファイルディスクリプタ 0が複製されます。

$ if [ -p /dev/stdin ]; then echo "From pipe"; fi
$ echo Hello | if [ -p /dev/stdin ]; then echo "From pipe"; cat < /dev/stdin; fi
From pipe
Hello
パイプラインが接続されていない状態では[ -p /dev/stdin ]は真にはなりません。
/dev/stdinがパイプラインに接続されている時、/dev/stdinから読み込みのリダイレクションを行うとパイプライン標準入力として読み込むことができます。
この例ではHelloの文字列がパイプラインを通して/dev/stdinから読み込まれてcatコマンドの入力となっています。
"From pipe"のメッセージはパイプラインを経由せず直接標準出力へ出力されています。
cat < /dev/stdinとすれば、パイプラインを経由せずに直接標準入力からリダイレクションされます。
/dev/stdoutファイルディスクリプタ 1が複製されます。

$ if [ -p /dev/stdout ]; then echo "To pipe"; fi
$ if [ -p /dev/stdout ]; then echo "To pipe"; echo Hello > /dev/stdout; fi | cat
To pipe
Hello
パイプラインが接続されていない状態では[ -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を作成して確認します。
#!/usr/bin/bash

exec {fd}<> /dev/tcp/www.example.com/80

MESSAGE='HEAD /index.html HTTP/1.1\r\n';
MESSAGE+='Connection: close\r\n';
MESSAGE+='Host: www.example.com\r\n\r\n';
echo -e $MESSAGE >& ${fd};
cat 0<& ${fd};

実行例:
$ ./httpsend.sh 
HTTP/1.1 200 OK
Date: Sun, 10 Apr 2022 03:05:56 GMT
Server: Apache
Cache-Control: max-age=1800
Expires: Sun, 10 Apr 2022 03:35:56 GMT
Connection: close
Content-Type: text/html; charset=iso-8859-1

/dev/udp/ホスト名/ポート番号ホスト名とポート番号が有効であれば、対応するソケットに対してUDP接続を試みます。

組み込みコマンドexecは新なプロセスを起動せずに、現在のシェルでコマンドを実行するために使用しますが、リダイレクションだけを記述することで、それ以降の標準入出力を全てリダイレクションすることができます。

実行例:
$ ls /proc/$$/fd
0  1  2  255
$ exec 10>& 1
$ /bin/ls /proc/$$/fd
0  1  10  2  255
$ exec {fd}> data
$ /bin/ls /proc/$$/fd
0  1  10  11  2  255
$ exec 1>& ${fd}
$ echo "こんにちは"
$ echo "今日は晴れです" >& 10
今日は晴れです
$ echo "今日は雨です" >& 11
$ exec 11>&-
$ exec 1>& 10
$ exec 10>&-
$ cat data
こんにちは
今日は雨です
$ ls /proc/$$/fd
0  1  2  255
  1. シェルの標準入出力をリダイレクションする前に元に戻せるように準備します。
    現在オープンされているファイルディスクリプタを確認します。
    /procファイルシステムが有効なLinuxでは、/proc/$$/fdに現在オープン中のファイルディスクリプタが仮想ファイルとして存在しています。
    この例では0、1、2、255のファイルディスクリプタがオープンされています。
  2. 標準出力ファイルディスクリプタが1)をファイルディスクリプタ10として(後に元に戻すために)複製します。
  3. 現在オープン中のファイルディスクリプタとして10が増えていることが確認できます。
  4. exec {fd}dataとすることでdataというファイルをオープンしてそのファイルディスクリプタ変数fdに設定します。
  5. 現在オープン中のファイルディスクリプタとして11が増えていることが確認できます。
    11はファイル dataのファイルディスクリプタです。
  6. ファイルディスクリプタ ${fd}を標準出力ファイルディスクリプタが1)に複製します。
  7. echoコマンドで"こんにちは"と出力しても画面には表示されません。
    リダイレクションしたファイルディスクリプタ ${fd}に書き込まれます。
  8. echoコマンドで"今日は晴れです"という文字列をファイルディスクリプタ 10に出力すると、上記2.の手順で標準出力ファイルディスクリプタ 10に複製しているため、文字列が画面に出力されます。
  9. 同様にechoコマンドで"今日は雨です"という文字列をファイルディスクリプタ 11に出力すると、画面には出力されず、上記4.の手順でリダイレクションしたdataに書き込まれます。
  10. exec 11>&-とすることで、ファイルディスクリプタ 11をクローズします。
  11. ファイルディスクリプタ 10を標準出力ファイルディスクリプタ 1)に複製することで標準出力を元に戻します。
  12. exec 10>&-とすることで、ファイルディスクリプタ 10をクローズします。
  13. dataの内容を確認します。
  14. 現在オープン中のファイルディスクリプタも元に戻っていることが確認できます。
$ /bin/ls /proc/$$/fd
0  1  2  255
$ exec {fd}< data
$ ls /proc/$$/fd
0  1  10  2  255
$ cat 0<& ${fd}
こんにちは
今日は雨です
$ cat 0<& 10
$ exec 10< data
$ cat 0<& 10
こんにちは
今日は雨です
$ exec 10<&-
  1. 現在オープンされているファイルディスクリプタを確認します。
  2. exec {fd}< dataとすることでdataというファイルをオープンしてそのファイルディスクリプタ変数fdに設定します。
  3. 現在オープン中のファイルディスクリプタとして10が増えていることが確認できます。
    10はファイル dataのファイルディスクリプタです。
  4. catコマンド標準入力の代わりにファイルディスクリプタ fdから読み込んで、文字列が画面に出力されます。
  5. もう一度実行するとどうでしょうか?
    何も出力されません。これは、先程の入力でファイルディスクリプタからの入力を最後まで読み込んだため、読み込むべきデータがない(EOD: End of Data)の状態になっているからです。
  6. 改めて、exec 10< dataとすることでdataというファイルをファイルディスクリプタ 10としてオープンしなおします。
  7. ファイルが再オープンされたため、ファイルの先頭からデータを読み込むため、catコマンドファイルディスクリプタ 10から読み込んで、文字列が画面に出力されます。
  8. exec 10<&-とすることで、ファイルディスクリプタ 10をクローズします。

コラム:
exec {変数名}< ファイルexec {変数名}> ファイルを実施した場合、OSでは何が行われているのでしょうか?
下記はリダイレクションに関連する部分のシステムコール(OSの処理)を抜粋したものになります。
$ exec {fd}< data
$ cat 0<& ${fd}
こんにちは
今日は雨です
$ exec {fd}<&-
open("data", O_RDONLY)            = 3
fcntl(3, F_DUPFD, 10)             = 10
fcntl(10, F_GETFD)                = 0
fcntl(10, F_DUPFD, 10)            = 11
fcntl(10, F_GETFD)                = 0
fcntl(11, F_SETFD, FD_CLOEXEC)    = 0
close(3)                          = 0
close(11)                         = 0
##### ここから子プロセスでの処理(外部コマンドcat) #####
dup2(10, 0)                       = 0
fcntl(10, F_GETFD)                = 0
read(0, "\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257\n\344\273
\212\346\227\245\343\201\257\351\233\250\343\201\247\343"..., 65536) = 35
write(1, "\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257\n\344\27
3\212\346\227\245\343\201\257\351\233\250\343\201\247\343"..., 35) = 35
read(0, "", 65536)                = 0
##### 子プロセスでの処理(外部コマンドcat)はここまで #####
fcntl(10, F_GETFD)                = 0
fcntl(10, F_DUPFD, 10)            = 11
fcntl(10, F_GETFD)                = 0
fcntl(11, F_SETFD, FD_CLOEXEC)    = 0
close(10)                         = 0
close(11)                         = 0
exec {変数名}< ファイルの場合、OS内部では上記のようにファイルを読み込み専用でオープンしていることがわかります。
また、catコマンド外部コマンドであるため、実行時は子プロセスが生成されます。
catコマンドを処理する子プロセスでは標準入力ファイルディスクリプタ 0にファイルディスクリプタ 10を複製し、標準入力ファイルディスクリプタ 0に複製されたファイルの内容を読み込んでいます。
$ exec {fd}> data
$ echo こんにちは 1>&${fd}
$ echo 今日は雨です 1>&${fd}
$ exec {fd}>&-
open("data", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(3, F_DUPFD, 10)             = 10
fcntl(10, F_GETFD)                = 0
fcntl(10, F_DUPFD, 10)            = 11
fcntl(10, F_GETFD)                = 0
fcntl(11, F_SETFD, FD_CLOEXEC)    = 0
close(3)                          = 0
close(11)                         = 0
fcntl(1, F_GETFD)                 = 0
fcntl(1, F_DUPFD, 11)             = 11
fcntl(1, F_GETFD)                 = 0
fcntl(11, F_SETFD, FD_CLOEXEC)    = 0
dup2(10, 1)                       = 1
fcntl(10, F_GETFD)                = 0
fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(1, "\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257\n", 16) 
= 16
dup2(11, 1)                       = 1
fcntl(11, F_GETFD)                = 0x1 (flags FD_CLOEXEC)
close(11)                         = 0
fcntl(1, F_GETFD)                 = 0
fcntl(1, F_DUPFD, 11)             = 11
fcntl(1, F_GETFD)                 = 0
fcntl(11, F_SETFD, FD_CLOEXEC)    = 0
dup2(10, 1)                       = 1
fcntl(10, F_GETFD)                = 0
write(1, "\344\273\212\346\227\245\343\201\257\351\233\250\343\201\247\343\201\
231\n", 19) = 19
dup2(11, 1)                       = 1
fcntl(11, F_GETFD)                = 0x1 (flags FD_CLOEXEC)
close(11)                         = 0
fcntl(10, F_GETFD)                = 0
fcntl(10, F_DUPFD, 10)            = 11
fcntl(10, F_GETFD)                = 0
fcntl(11, F_SETFD, FD_CLOEXEC)    = 0
close(10)                         = 0
close(11)                         = 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

端末で単にwcと入力してみてください。
wcコマンド標準入力であるキーボードからの入力を受け付けます。文字列を入力してEnterキーを入力したら、Ctrl+Dを入力してみてください。入力した行数、単語数、バイト数をカウントできましたか?