入力と出力

リダイレクションとパイプ

コマンドの実行結果は通常、標準出力であるディスプレイに出力される。この実行結果はリダイレクション(>>>)やパイプ(|)を使用することにより、ディスプレイではなくテキストファイルやコマンドに対して出力するように切り替えることができる。

また、リダイレクションはコマンドの出力先をテキストファイルに切り替える以外にも、これとは逆にコマンドへの入力元をテキストファイルに切り替えることもできる。

なお、リダイレクションとパイプは次のように使い分ける。

リダイレクション

データを渡す対象がファイルである場合はリダイレクションを使用する。

パイプ

データを渡す対象がコマンドである場合はパイプを使用する。

リダイレクション [>, », <]

コマンドの出力を file へ上書きする

command >file

コマンドの出力を file へ追記する

command >>file

→ コマンドの実行結果の出力先を、ディスプレイからファイルへ切り替えたい場合は、リダイレクションを使用する。

コマンドに続けてリダイレクション記号(>>>)と出力先ファイルを指定することで、コマンドの実行結果をファイルへ書き込むことができる。

ファイルへの

  • 上書きでの出力には > (既存の内容は全て消去される)
  • 追記での出力には >> (既存の内容の最下行に追加される)

を使用する。

上書き (>) も、追記 (>>)もファイルが存在しなかった場合は、新規にファイルが作成された上で出力される。

>
  • 書き込み対象のファイルが既に存在する場合は、対象ファイルの内容が全てクリアされ、コマンドの実行結果が出力される。
  • 対象ファイルが存在しなかった場合は新規にファイルが作成される。
>>
  • 書き込み対象のファイルが既に存在する場合は、対象ファイルの内容に追記する形で、コマンドの実行結果が出力される。
  • 対象ファイルが存在しなかった場合は新規にファイルが作成される。

リダイレクションを使用したファイルへの出力例

以下、実際にリダイレクションを使用して、ファイルに出力してみる。

$ cat test.txt
hoge hoge
#↑出力先ファイル内容。

$ date >test.txt
#↑ date コマンドの実行結果をファイルへ上書きする。

$ cat test.txt
2007年  5月 27日 日曜日 01:58:32 JST
#↑コマンドの実行結果で「hoge hoge」が上書きされている。

$ date >>test.txt
#↑ date コマンドの実行結果をファイルへ追記する。

$ cat test.txt
2007年  5月 27日 日曜日 01:58:32 JST
2007年  5月 27日 日曜日 01:58:54 JST
#↑先に実行された date コマンドの実行結果に追記される形で出力されている。

リダイレクション記号 (>>>) の前後のスペースはあっても無くてもよいが、前方にのみスペースを入れるのが筆者の推奨 (容器にストローを差し込むイメージがあるため)。

command >file
command >>file

リダイレクションを使用したファイル内容のクリア

ファイルの中身を全てクリアし、0バイトのファイルにするにはリダイレクションを利用する。

>file
: >file
cat /dev/null >file

実際の実行結果は以下のとおり。

$ ls -l file*
-rw-rw-r-- 1 sunone sunone 122  5月 27 02:05 file1
-rw-rw-r-- 1 sunone sunone 122  5月 27 02:05 file2
-rw-rw-r-- 1 sunone sunone 122  5月 27 02:05 file3
#↑file1 ~ file3 はいずれも122バイト。

$ >file1
$ : >file2
$ cat /dev/null >file3
#↑各コマンドを file1 ~ file3 に対して実行する。

$ ls -l file*
-rw-rw-r-- 1 sunone sunone 0  5月 27 02:05 file1
-rw-rw-r-- 1 sunone sunone 0  5月 27 02:06 file2
-rw-rw-r-- 1 sunone sunone 0  5月 27 02:06 file3
#↑実行後には全て0バイトになっている。

いずれの方法でも、実行後に file は0バイトになっていることが分かる。

リダイレクションを使用したコマンドへの入力

リダイレクションはコマンドの出力先をファイルへ変更するのみならず、その逆も可能である。つまり、コマンドへの入力元をファイルに切り替えることもできる。

# ファイル内容をコマンドへ渡す
command <file

→ 入力先コマンドに対して < を使用して入力元ファイルを指定する。

コマンドに続けてリダイレクション記号 (<) と入力元ファイルを指定することで、ファイル内容に対してコマンドを実行することができる。

また、< はコマンドへのリダイレクションではなく、ファイルディスクリプタ 0番へのリダイレクションである。

$ cat test.txt
11111
22222
33333
44444
55555
#↑入力元となるファイルの内容。

$ wc -l <test.txt
5
#↑ファイルの行数を wc コマンドでカウントする (別にリダイレクションを使う必要は無いが)。

リダイレクションでファイルを入力元として使用する場合に、最もよく使用されるコマンドは while 文であろう (while 文は正確には while コマンドである)。以下にリダイレクションと while 文を使用した例 (redirection.sh) を示す。

#!/bin/bash

echo "while文にリダイレクション"

# readコマンドで標準入力から1行読み込む
while read line
do
  echo "$line"
done <$1

echo "カレントシェルにリダイレクション"

# exec コマンドを使用しカレントシェルの標準入力へリダイレクトする
exec <$1

# readコマンドで標準入力から1行読み込む
while read line
do
  echo "$line"
done

exit 0

このシェルスクリプト redirection.sh の実行結果は、以下のとおりとなる。

$ ./redirection.sh test.txt
while文にリダイレクション
11111
22222
33333
44444
55555
カレントシェルにリダイレクション
11111
22222
33333
44444
55555

パイプ [ | ]

command1 | command2
command1 | command2 | command3
command1 | command2 | … | commandN

→ コマンドの実行結果を、他のコマンドへ引き渡したい場合はパイプ (|) を使用する。

複数コマンドをパイプで結合すると、コマンドは左から順に実行され、各コマンドの実行結果はパイプで結合された隣のコマンドへと引き渡される。

実行結果を引き渡されたコマンドは、その引き渡された実行結果に対して処理を行う。

3つ以上のコマンドがパイプで結合されている場合も同様にして左から順に実行され、実行結果を隣のコマンドへと次々に引き渡していく。

なお、引き渡されるのは標準出力のみで、標準エラー出力は引き渡されない。

$ ls | wc -l
5
#↑ ls コマンドの実行結果を wc コマンドに引き渡して行数をカウント。

$ ls hogehoge | wc -l
ls: hogehoge: そのようなファイルやディレクトリはありません
0
#↑標準エラー出力は wc コマンドには引き渡されていない。

$ ls hogehoge 2>&1 | wc -l
1
#↑標準エラー出力も引き渡すにはパイプの前に2番を1番にリダイレクトする。

リダイレクションと同様にパイプ記号 (|) の前後のスペースはあっても無くてもよい。しかし、シェルスクリプトとして記述する場合には、可読性の面から前後にスペースを入れたほうがよいだろう。

# スペースがあって読みやすいので筆者推奨の書き方
command1 | command2 | command3
# スペースがなくて読みにくいのでやめましょう
command1|command2|command3

ヒアドキュメント [«]

command <<終了文字
hogehoge
fugafuga
foobar
終了文字

→ コマンドの標準入力に対して複数行にわたる任意の文字列を与えるにはヒアドキュメントを使用する。

ヒアドキュメントを使用すると、「終了文字」が出現するまでの文字列を、コマンドへの標準入力として与えることができる。

ヒアドキュメント中では ``$() によるコマンド置換や、変数も使用可能である。

ヒアドキュメント内でのコマンド置換や変数を完全に無効にしたい場合は、終了文字を '終了文字' といったようにシングルクォートで囲むか、もしくは \終了文字 といったようにバックスラッシュでエスケープする。

注意!

バックスラッシュは日本語環境だと「¥」、英語環境だと「\」と表示される。当サイト内ではフォントの関係で両方の表記が混在している可能性があるが、その場合は適宜読みかえてほしい。

$ cat <<_EOT_
> hoge hoge
> fuga fuga
> foo foo _EOT_
> _EOT_ bar bar
> _EOT_
hoge hoge
fuga fuga
foo foo _EOT_
_EOT_ bar bar
#↑「>」記号の部分がヒアドキュメントとしてキーボードから入力した部分。そのしたの部分は cat コマンドがヒアドキュメントからの入力を出力した部分。

$ cat <<_EOT_
`echo "hoge"`
$(echo "fuga")
> $PATH
> $PWD
> _EOT_
hoge
fuga
/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/sunone/bin
/home/sunone
#↑ヒアドキュメント中ではコマンド置換や変数も使用可能。

上記の実行例のように、終了文字は行中にあっても無視される。ヒアドキュメントを終了するには、終了文字を1行でかつ余計な文字を付けずに記述する必要がある

ちなみに、上記の実行例で終了文字として使用されている _EOT_ は「End Of Text」の略である。前後の _ は終了位置を強調するために付加している。終了文字は任意の文字列でかまわないが、筆者は_EOT___EOT__ を推奨する。

たまに終了文字を EOF としている人を見かけるが、これだと「End Of File」になるので間違いだと思う。

以下はヒアドキュメントを使用したシェルスクリプト (heredocument.sh) の例。

#!/bin/bash

# cat の出力結果を標準エラー出力へ
if [ $# -ne 1 ]; then
  cat <<_EOT_ 1>&2
引数を指定してください。

  Usage: $0 param

_EOT_
  exit 1
fi

#「$」を表示したいときは「\$」のようにエスケープする
cat <<_EOT_
  ヒアドキュメント中では変数も使用できます。
  \$1 は $1 です。

_EOT_

# 終了文字をエスケープするとヒアドキュメント中の変数は展開されない
cat <<'_EOT_'
   シングルクオートで終了文字を囲むと変数は無視されます。
  \$1 は $1 です。
  `echo "コマンド置換も無視されます。"`

_EOT_

cat <<\_EOT_
  バックスラッシュでも同様です。
  \$1 は $1 です。
  `echo "コマンド置換も無視されます。"`

_EOT_

# 「<<-」とすると、ヒアドキュメント中の先頭にあるタブは無視される(スペースは無視されない)
cat <<-_EOT_
  終了文字の前に「-」を指定すると、ヒアドキュメント中の先頭のタブは無視されます。
  ←タブ
  ←タブ

_EOT_

exit 0

このシェルスクリプト heredocument.sh の実行結果は、以下のとおりとなる。

$ ./heredocument.sh HEREDOC
  ヒアドキュメント中では変数も使用できます。
  $1 は HEREDOC です。

   シングルクオートで終了文字を囲むと変数は無視されます。
  \$1 は $1 です。
  `echo "コマンド置換も無視されます。"`

  バックスラッシュでも同様です。
  \$1 は $1 です。
  `echo "コマンド置換も無視されます。"`

終了文字の前に「-」をしてすると、ヒアドキュメント中の先頭のタブは無視されます。
←タブ
←タブ

複数行の文字列を出力する場合は、このヒアドキュメントを使用することを推奨する。echo や printf で複数行出力する場合は、'" で囲む必要があるため、出力する文字列にそれら自体が含まれているとエスケープする必要が出てくるが、ヒアドキュメントであればそれらのエスケープを意識する必要はない。

ヒアドキュメント使用方法まとめ

<<_EOT_
  • 通常のヒアドキュメント
  • ヒアドキュメント内で変数を使用可能 ($ を表示するには \$ のようにエスケープする)
  • コマンド置換が使用可能
<<'_EOT_' <<\_EOT_
  • 変数展開、コマンド置換が実行されないヒアドキュメント
  • ヒアドキュメント内で変数は使用できない ($ がそのまま出力される。)
  • コマンド置換が使用不可 (`` $() がそのまま出力される)
<<-_EOT_
  • 先頭のタブを無視する通常のヒアドキュメント
  • ヒアドキュメント内で変数を使用可能 ($ を表示するには \$ のようにエスケープする)
  • コマンド置換が使用可能
  • ヒアドキュメント内の先頭のタブが無視される
<<-'_EOT_' <<-\_EOT_
  • 先頭のタブを無視し、さらに変数展開、コマンド置換が実行されないヒアドキュメント
  • ヒアドキュメント内で変数は使用できない (「$」がそのまま出力される。)
  • コマンド置換が使用不可 (`` $() がそのまま出力される)
  • ヒアドキュメント内の先頭のタブが無視される

プロセス置換 [<()]

プロセス置換とは、簡潔に説明すると「コマンドの出力結果をファイルとして扱う機能」である。例えば diff コマンドは、引数にファイル以外を指定することができないが、このプロセス置換を使用することでコマンドの実行結果を diff コマンドに直接渡すことができる。

diff <(command) <(commandA; commandB)

→ 実行結果をファイルとして使用したいコマンドを <() で指定する。

このプロセス置換は次の例のように、一時ファイルを作らずにファイルの置換結果を確認したい場合などに有効である。

$ cat text.txt
hoge
fuga
foo
bar
#↑置換対象のファイル内容。

$ diff text.txt <(sed -e 's/hoge/HOGE HOGE/' text.txt)
1c1
< hoge
---
> HOGE HOGE
#↑置換前と置換後の diff 結果。

プロセス置換に使用するコマンドは、単一のコマンドのみではなく、セミコロン区切りの複数コマンド、パイプやネストしたプロセス置換も指定可能である。

$ diff <(echo "aaa"; echo "bbb") <(cat <(echo "aaa") | cat <(echo "BBB"); echo "ccc")
1,2c1,2
< aaa
< bbb
---
> BBB
> ccc

標準入力を使用する

標準入力とは、通常はキーボードからの入力を意味する。この標準入力は、ファイルディスクリプタの0番で表され、この0番にリダイレクション (>) を使用することにより、標準入力をテキストファイルの内容に切り替えることも可能である。

キーボードからの入力を読み取る

read 変数名

→ 標準入力からデータを読み取るには read コマンドを使用する。

read コマンドは標準入力からデータを読み取り、引数に指定した変数に読み取ったデータを設定する。キーボードからもしくは、リダイレクションによりテキストファイルなどからデータを読み取る場合には read コマンドを使用する。

$ read DATA
Today is a fine day.
#↑キーボードから「Today is a fine day.」と入力する。

$ echo $DATA
Today is a fine day.
#↑read コマンドに指定した変数には、入力した値が設定されている。

read コマンドを実行すると、キーボードからの入力待ち状態になり、Enter キーが入力されるまでの値が指定した変数に設定される。

データを読み取る以外にも、入力待ち状態になることを利用して、シェルスクリプトの処理を一時的に止める、といった使い方もできる。

データの読み取りと、読み取り後に read コマンドで一時停止するシェルスクリプト (input_test.sh) を作成してみる。

#!/bin/bash

echo "キーボードから文字を入力してください..."
read DATA

echo ""
echo "入力されたデータ: $DATA"

echo ""
echo "<Enter>で終了します。"

# readコマンドをスクリプトの一時停止に利用する
read DUMMY

echo "終了します..."

exit 0

このシェルスクリプト input_test.sh の実行結果は、以下のとおりとなる。

$ ./input_test.sh
キーボードから文字を入力してください...
My name is SUNONE.

入力されたデータ: My name is SUNONE.

<Enter>で終了します。
※ここでEnterキーを入力
終了します...
$

1回目の read コマンド実行時にキーボードから「My name is SUNONE.」を入力した。その入力値が「入力されたデータ: 」の後ろに正しく表示されているのが分かる。

2回目の read コマンドは入力された値はまったく使用せず、コマンドの入力待ちによる一時停止状態のみを利用している。つまり、Enter キーが入力されるまでスクリプトは一時停止状態となっている。

ファイルからの入力を読み取る

read 変数名 0<ファイル名
# 「0」は省略可能
read 変数名 <ファイル名

→ read コマンドにリダイレクションを使用することで、テキストファイルからもデータを入力することができる。

リダイレクションを使用すると、入力元や出力先を切り替えることが可能になる。この場合は、ファイルの出力をファイルディスクリプタ 0番に、つまり標準入力に切り替えることにより、ファイルからのデータ入力を実現している。なお、切り替え先として指定している「0」は省略可能である。

$ cat input.txt
Today is a fine day.
#↑入力に使用するファイル。

$ read DATA <input.txt
#↑ファイルを標準入力にリダイレクトする。

$ echo $DATA
Today is a fine day.
#↑キーボードからの入力ではなく、ファイルからの入力でも値が設定されている。

テキストファイルを標準入力にリダイレクトすることで、キーボードからの入力なしでも read コマンドが実行される。このリダイレクトと while 文と組み合わせることで、ファイル内容の解析といった処理に応用できる。

リダイレクトと while 文を使用した、テキストファイル内各行の文字数をカウントするシェルスクリプト (input_from_text.sh) を作成してみる。

#!/bin/bash

echo "$1の文字数をカウント..."

# 行数カウンタを初期化
line_no=1

# read コマンドで読み取れなくなるまでループ
while read LINE
do
  count=`echo "$LINE" | wc -c`
  echo "$line_no行目: $count文字 : $LINE"

  line_no=`expr $line_no + 1`
done <$1

exit 0

このシェルスクリプト input_from_text.sh の実行結果は、以下のとおりとなる。

$ cat input.txt
a
aa

bbb
bbbbbbbbbbbbbbbb

c                        c
ccccccccc cc ccccccccc
c         cccccccc              ccccc
   ccc     cccccccccccc c      c c    cccccc
#↑入力に使用するテキストファイル。

$ ./input_from_text.sh input.txt
input.txtの文字数をカウント...
1行目: 2文字 : a
2行目: 3文字 : aa
3行目: 1文字 :
4行目: 4文字 : bbb
5行目: 17文字 : bbbbbbbbbbbbbbbb
6行目: 1文字 :
7行目: 27文字 : c                        c
8行目: 23文字 : ccccccccc cc ccccccccc
9行目: 38文字 : c         cccccccc              ccccc
10行目: 42文字 : ccc     cccccccccccc c      c c    cccccc

※ 行頭のスペースは無視される。これを回避するには環境変数 IFS を変更する必要がある。

read コマンドに while 文とリダイレクトを組み合わせることにより、テキストファイルの各行に対して wc コマンド (文字数カウント) を実行することが可能となっている。なお、文字数には改行も含まれている。

上記のような処理を応用することにより、ログファイルを解析するといったようなシェルスクリプトも作成可能であるが、while 文にテキストファイルをリダイレクトするシェルスクリプトは、実行時間が非常に長くなる傾向がある

したがって10行程度のテキストファイルならば問題ないが、膨大な行数のテキストファイルを処理対象とする場合は、awk スクリプトもしくは Perl スクリプトなどを使用した方がよい。

実際に同様の処理を awk スクリプトを使用して行うシェルスクリプトを作成し、上記の非 awk スクリプト版と実行時間を比較してみる。

比較に使用する awk スクリプト版は次のとおり。

#!/bin/bash

echo "$1の文字数をカウント..."

awk '{
  count = length();
  printf("%d行目: %d文字 : %s\n", NR, count, $0);
}' $1

exit 0

非 awk 版 (input_from_text.sh) と awk 版 (input_from_text_awk.sh) の実行速度比較は以下のとおり。

$ time ./input_from_text.sh input.txt
input.txtの文字数をカウント...
1行目: 2文字 : a
2行目: 3文字 : aa
3行目: 1文字 :
4行目: 4文字 : bbb
5行目: 17文字 : bbbbbbbbbbbbbbbb
6行目: 1文字 :
7行目: 27文字 : c                        c
8行目: 23文字 : ccccccccc cc ccccccccc
9行目: 38文字 : c         cccccccc              ccccc
10行目: 42文字 : ccc     cccccccccccc c      c c    cccccc

real    0m1.125s
user    0m1.106s
sys     0m0.588s

$ time ./input_from_text_awk.sh input.txt
input.txtの文字数をカウント...
1行目: 1文字 : a
2行目: 2文字 : aa
3行目: 0文字 :
4行目: 3文字 : bbb
5行目: 16文字 : bbbbbbbbbbbbbbbb
6行目: 0文字 :
7行目: 26文字 : c                        c
8行目: 22文字 : ccccccccc cc ccccccccc
9行目: 37文字 : c         cccccccc              ccccc
10行目: 44文字 :    ccc     cccccccccccc c      c c    cccccc

real    0m0.149s
user    0m0.153s
sys     0m0.046s

※awk 版は改行を1文字とカウントしないため、非 awk 版よりも文字数が1文字少なく表示されている。また、非 awk 版は行頭のスペースも無視される。

awk 版の方が非 awk 版よりも 1秒ほど早いことが確認できる。

10行程度のテキストファイルが対象なので、それほど大きな違いは出ないが、対象となるテキストファイルが 1000行以上となるとかなりの差になってくる。

次は実際に 1000行のテキストファイルを使用した場合の実行速度比較。

$ wc -l input.txt
1000 input.txt
※↑1000行のテキストファイルを使用する。

$ time ./input_from_text.sh input.txt >/dev/null

real 2m5.945s
user 1m50.517s
sys  0m52.983s

$ time ./input_from_text_awk.sh input.txt >/dev/null

real 0m0.142s
user 0m0.122s
sys  0m0.077s

awk 版は一瞬で終了しているが、非 awk 版は終了まで 2分もかかっている。

リダイレクトは便利な機能ではあるが、while 文と組み合わせて使用する場合には、実行スピードに注意する必要がある。

標準出力を使用する

標準出力とは、通常はディスプレイを意味する。この標準出力はファイルディスクリプタの 1番で表される。

ディスプレイにメッセージを出力する - echo コマンド

echo メッセージ
# メッセージ出力後に改行しない
echo -n メッセージ

→ ディスプレイにメッセージを出力するには echo コマンドを使用する。

echo コマンドのパラメータに表示したいメッセージを指定して実行すると、標準出力 (通常はディスプレイ) に指定したメッセージが表示される。

通常はメッセージ出力後に自動的に改行されるが、-n オプションを指定するとメッセージ出力後に改行されない。

$ echo "Hello world."
Hello world.
$
#↑引数に指定したメッセージと改行が出力される。

$ echo -n "Hello world."
Hello world.$
#↑ -n オプションをしてすると改行されないので、メッセージの直後にプロンプトが表示されている。

整形してメッセージを出力する - printf コマンド

printf "%d %nd %s %ns" 数値1 数値2 文字列1 文字列2

→ メッセージを整形して表示するには printf コマンドを使用する。

シェルスクリプトでも C言語でお馴染みの printf が使用可能である。printf コマンドは C言語の printf 関数と同じように使用することができる。

この printf コマンドを使用することにより、echo コマンドでは不可能な、メッセージを整形して表示する機能を実現できる。

処理結果を表形式で出力したい場合などは、桁数を指定して出力できる printf コマンドを使用するとよい。

printf コマンドでメッセージを整形して出力するシェルスクリプト (printf.sh) を作成してみる。

#!/bin/bash

# 桁数を指定しないで、そのまま出力する。
printf "%d %d %d\n" 10 200 3000
printf "%d %d %d\n" 4000 500 60
printf "%d %d %d\n" 700 8000 9

# 文字数を指定しないで、そのまま出力する。
printf "%s %s %s\n" aa bbb cccc
printf "%s %s %s\n" dddd eee ff
printf "%s %s %s\n" ggg hhhh i

echo ""

# 桁数を指定し、整形して出力する。
printf "%5d %5d %5d\n" 10 200 3000
printf "%5d %5d %5d\n" 4000 500 60
printf "%5d %5d %5d\n" 700 8000 9

# 文字数を指定し、整形して出力する。
printf "%5s %5s %5s\n" aa bbb cccc
printf "%5s %5s %5s\n" dddd eee ff
printf "%5s %5s %5s\n" ggg hhhh i

echo ""

# 桁数・文字数を指定し、左詰で整形して出力する。
printf "%-5d %-5d %-5d\n" 10 200 3000
printf "%-5s %-5s %-5s\n" aa bbb cccc

echo ""

# 桁数分の0埋で出力する。
printf "%05d %05d %05d\n" 700 8000 9

exit 0

このシェルスクリプト printf.sh の実行結果は、以下のとおりとなる。

$ ./printf.sh
10 200 3000
4000 500 60
700 8000 9
aa bbb cccc
dddd eee ff
ggg hhhh i

   10   200  3000
 4000   500    60
  700  8000     9
   aa   bbb  cccc
 dddd   eee    ff
  ggg  hhhh     i

10    200   3000
aa    bbb   cccc

00700 08000 00009

桁数を指定して実行すると、右揃えできれいに出力される。データを表形式で出力したい場合などは、このように printf コマンドのフォーマットに桁数を指定して実行する。

また、デフォルトで改行される echo コマンドとは異なり、printf コマンドはフォーマットに \n を指定しないと改行されないので注意が必要だ。

printf コマンドでは単純な数値や文字列だけではなく、コマンドの実行結果や変数も指定可能だ。

printf "%d %nd %s %ns" `command`
printf "%d %nd %s %ns" $変数
printf "%d %nd %s %ns" `command` $変数

→ printf コマンドには単純な文字列だけではなく、コマンドや変数も指定可能。

コマンドを指定する場合は、バッククォートで囲んだ状態で指定する。バッククォートを使用することで、指定したコマンドの実行結果がそのまま printf コマンドのフォーマット部分へ渡される。

以下はコマンドや変数を指定した場合の実行結果。

$ printf "%10s %10s %10s %10s %10s %10s\n" `date`
   2007年       5月      27日  日曜日   04:16:58        JST
$ printf "%-10s %-10s %-10s %-10s %-10s %-10s\n" `date`
2007年    5月       27日      日曜日  04:17:09   JST
$ DATE=`date`
$ printf "%10s %10s %10s %10s %10s %10s\n" $DATE
   2007年       5月      27日  日曜日   04:17:16        JST

コマンドや変数を指定した場合の詳細は以下のとおりとなる。まず、

printf "%5d %5d %5d\n" `date '+%Y %m %d'`

と指定した場合は、バッククォートの部分が展開されて、

printf "%5d %5d %5d\n" 2007 05 27

という状態で、printf コマンドが実行される。

変数を指定した場合も同様に変数が展開された状態で printf コマンドが実行される。例えば、

DATE="2007 05 27"
printf "%5d %5d %5d\n" $DATE

と指定した場合は、変数が展開され、

printf "%5d %5d %5d\n" 2007 05 27

という状態で、printf コマンドが実行される。

標準エラー出力を使用する

標準エラー出力とは、標準出力と同様に通常はディスプレイを意味する。だが、標準出力とは異なり、ファイルディスクリプタは 2番で表される。

これによって、通常のメッセージとエラーメッセージを区別することが可能になっている。つまり、通常のメッセージのみを表示したい、またはエラーメッセージのみを表示したい、といった処理が実現できる。

ディスプレイにエラーメッセージを出力する

通常、コマンドが失敗した場合に表示されるエラーメッセージは、標準エラー出力に送られている。

シェルスクリプトにおいても、リダイレクションを使用することにより、標準エラー出力にエラーメッセージを出力することが可能である。

シェルスクリプトを作成する場合は、

  • 通常メッセージは標準出力へ
  • エラーメッセージは標準エラー出力へ

というように、メッセージの出力先を振り分けることが望ましい。

echo エラーメッセージ 1>&2
printf エラーメッセージ 1>&2
任意のコマンド 1>&2

→ エラーメッセージを出力するには、標準出力を標準エラー出力にリダイレクトする。

echo コマンドなどにより出力されるメッセージを、標準エラー出力にリダイレクトすることで、エラーメッセージとして出力することができる。

1>&2 はファイルディスクリプタ1番(標準出力) を 2番(標準エラー出力) に丸め込んで出力するという意味になる。結果的には、標準出力が標準エラー出力として出力される、ということになる。

通常のメッセージとエラーメッセージを出力するシェルスクリプト (stderr.sh) を作成してみる。

#!/bin/bash

# 標準出力に出力する。
echo "これは通常メッセージです。"

# 標準エラー出力に出力する。
echo "これはエラーメッセージです。" 1>&2

exit 0

このシェルスクリプト stderr.sh の実行結果は、以下のとおりとなる。

$ ./stderr.sh
これは通常メッセージです。
これはエラーメッセージです。

$ ./stderr.sh 1>/dev/null
これはエラーメッセージです。
#↑標準エラー出力のみを表示。

$ ./stderr.sh 2>/dev/null
これは通常メッセージです。
#↑標準出力のみを表示。

NULL デバイス (/dev/null) へリダイレクトするとメッセージは破棄される。NULL デバイスはゴミ箱のような物と考えるとよい。

標準出力(1番) を NULL デバイスへリダイレクトすると、エラーメッセージのみが表示されている。

逆に標準エラー出力(2番) を NULL デバイスへリダイレクトすると、通常メッセージのみが表示されている。

このことから、メッセージが標準出力と標準エラー出力に振り分けられていることが確認できる。

複数コマンドのリダイレクトとパイプ

複数コマンドの出力をまとめてリダイレクトする

複数コマンドの出力をまとめて一つのファイルに、または for 文や while 文の出力をまとめて一つのファイルにリダイレクトしたいときに有効なテクニックを紹介する。

こういった場合の処理でよく見られる悪い例として、次のようなものがある。

リダイレクトの悪い例

echo "1. hogehoge" >>file
echo "2. fugafuga" >>file
echo "3. foofoo"   >>file
echo "4. barbar"   >>file

文法的には全く問題ないのだが、リダイレクトが連続していてスクリプトがすっきりせず、リダイレクト対象が変わった場合の修正が面倒になる (一括置換すればいいことは確かだが)。

“{}” のグループをリダイレクト

スマートに複数コマンドの出力をリダイレクトするには {} によるコマンドのグルーピング機能を使用する。

{
  echo "1. hogehoge"
  echo "2. fugafuga"
  echo "3. foofoo"
  echo "4. barbar"
} >>file

ヒアドキュメントをリダイレクト

cat コマンドとヒアドキュメントを使用してもよい。

cat <<__END_OF_MESSAGE__ >>file
1. hogehoge
2. fugafuga
3. foofoo
4. barbar
__END_OF_MESSAGE__

for 文のブロックをリダイレクト

グルーピング機能と同様に、for 文の do done ブロックの出力をまとめてリダイレクトすることも可能だ。

for i in `seq 1 10`
do
  echo "number $i"
done >>file

上記をコマンドラインから実行してみる。

$ for i in `seq 1 10`
> do
>   echo "number $i"
> done >file
$
#↑何も表示されない。

$ cat file
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9
number 10
#↑ブロック内の全ての出力はリダイレクト先のファイルに出力されている。

制御文のブロックをリダイレクト

もちろん for 文以外の制御文も同様にリダイレクト可能である。

while [ $i -lt 10 ]
do
  echo "number $i"
  i=`expr $i + 1`
done >>file
if [ -f "$str" ]; then
  ls "fugafuga"
else
  echo "FUGAFUGA"
fi >>file
case "$str" in
  "hoge" ) ls "hogehoge" ;;
  "fuga" ) cat "fugafuga" ;;
  * ) echo "NG..." ;;
esac >>file

関数全体をリダイレクト

一切の出力を行わない関数を作りたい場合は次のようにする。

func()
{
  [ $#- ne 3 ] && return 1

  echo "NO MESSAGE..."
  echo "NO ERROR MESSAGE..." 1>&2

  grep -v "$1" $2 >$3
} >/dev/null 2>&1

複数コマンドの出力をまとめてパイプに流す

複数コマンドの出力をまとめてパイプに流し込みたいときは、{} によるコマンドのグルーピング機能を利用する。

{ echo "hogehoge"; echo "fugafuga"; } | wc -l

{} で複数コマンドをグループ化することにより出力を一つにまとめる。

コマンドの数が多い場合は、次のように記述すると可読性が高くなる。

{
  date
  echo "hoge"
  echo "HOGE"
  echo "fuga"
  echo "FUGA"
} | tee -a file