なんとなしにBashのリダイレクトとパイプについて調べてたので、後から確認しやすいようにまとめてみることにする。

1.ファイルへのリダイレクト

基本的な使い方。コマンドの実行結果をファイルにリダイレクトする事で、上書きや追記をさせることが出来る。
これについては、UNIX系OSに慣れていない人でもよく知っている事だろう。
「>」で上書き(ファイルの既存内容は削除される)、「>>」で追記書き込みが行われる。

コマンド > ファイル # 上書き
コマンド >> ファイル # 追記

で、このリダイレクトだが通常は標準出力しかリダイレクトされない。
この辺りを使い分ける場合は、以下のようにコマンドを実行する。
※追記書き込み時も同様のため割愛。

コマンド 1> ファイル # 標準出力のみをリダイレクト(デフォルト)
コマンド 2> ファイル # 標準エラー出力のみをリダイレクト
コマンド &> ファイル # 標準出力・標準エラー出力を共にリダイレクト

これを利用することで、以下のように標準エラー出力だけを/dev/nullにリダイレクトしてコンソールにもファイルにも出力させないようにしたり、それぞれを別のファイルに出力させたりすることができる。
ちょっとしたお遊びとしては、他のコンソールに任意の文字列を送りつけるといった事も可能だ。たとえば、以下のようにコマンドを実行することで「/dev/pts/1」を使っているコンソールに文字列を送ることができる。

echo "文字列" > /dev/pts/1

ちなみに、標準エラー出力と標準出力を入れ替える場合は、以下のようにコマンドを実行する事で行える。

コマンド 3>&1 1>&2 2>&3

2.ファイルの読込み

リダイレクトを使うことで、ファイルを一部のコマンドに読み込ませる事も出来る。

コマンド < ファイル # ファイルの内容を一部のコマンドに読み込ませる

以下、readコマンドを用いた例

[root@test-node ~]# cat /work/command
/etc
[root@test-node ~]# read -r line < /work/command ;echo $line
/etc

3.複数行の文字列をコマンドに渡す

これもいろんな所でみる使い方だ。
以下のようにコマンドを実行することで、複数行の文字列をコマンドにリダイレクトすることが出来る。

コマンド << EOF
......
EOF

よく使われるのは、catと組み合わせてファイルに書き込みをする方法だろう。
以下、例。

[root@test-node ~]# cat << EOF > /work/test_red.txt
> 1234
> 5678
> 90
> EOF
[root@test-node ~]# cat /work/test_red.txt
1234
5678
90

ここで記述されているEOFというのは処理終了の判定文字列となる。
ここではEOFと記述しているが、他の文字列でも構わない。

なお、Tabなどを入力したい場合は、以下のようにコマンドを実行する。

コマンド <<- EOF
......
EOF

4.コマンドに文字列をリダイレクトする

以下のように指定することで、コマンドに文字列をリダイレクトする事も出来る。

コマンド <<< "文字列"

例えば、catコマンドに文字列を送ることでechoと同じような処理を行わせる事ができる。
(日常で使うかわからないけど)

[root@test-node ~]# cat <<< "This Server is $(hostname)."
This Server is test-node.

5.execコマンドと組み合わせる

execコマンド(forkせずにコマンドを実行する)と組み合わせる事で、エラーのリダイレクト設定をそのセッション内では継続させるような事もできる。
(ただし、PS1などがコンソールに出力されない状態になる場合もあるので、シェルスクリプト内でしか使えないと思われる)

以下、簡単にまとめ。

セッション中は永続的に標準エラー出力を指定したファイルにリダイレクトする

exec 2>ファイル # セッション中は永続的に標準エラー出力を指定したファイルにリダイレクトする
[root@test-node ~]# cat /work/test_sh.sh
exec 2>/tmp/error_test
greew
grep 'aaa' /etc/hogeghogege
[root@test-node ~]# sh /work/test_sh.sh
[root@test-node ~]# cat /tmp/error_test
/work/test_sh.sh: 行 2: greew: コマンドが見つかりません
grep: /etc/hogeghogege: そのようなファイルやディレクトリはありません

ファイルを読込む

exec 3<ファイル
[root@test-node ~]# exec 3</etc/fstab
[root@test-node ~]# grep "" <&3

#
# /etc/fstab
# Created by anaconda on Sun Jan 3 08:46:04 2016
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/centos-root / xfs defaults 0 0
UUID=461b7464-714d-4a8b-9f63-19fe0482887f /boot xfs defaults 0 0
/dev/mapper/centos-home /home xfs defaults 0 0
/dev/mapper/centos-swap swap swap defaults 0 0
[root@test-node ~]# grep "" <&3
[root@test-node ~]#


## ファイルへ書き込む ```bash exec 3>ファイル
[root@test-node ~]# exec 3>/tmp/3teest
[root@test-node ~]# echo "aaaaaaaa" >&3
[root@test-node ~]# cat /tmp/3teest
aaaaaaaa

ファイルへ書き込み・読込みを行う

exec 3<>ファイル
[root@test-node ~]# exec 3<>/tmp/3test
[root@test-node ~]# echo "aaaaaaaa" >&3
[root@test-node ~]#
[root@test-node ~]# exec 3<>/tmp/3test
[root@test-node ~]# grep "" <&3
aaaaaaaa

指定ポートに対し通信する

exec 3<> /dev/tcp/アドレス/ポート番号 # tcpの場合
exec 3<> /dev/udp/アドレス/ポート番号 # udpの場合
[root@test-node ~]# exec 3<> /dev/tcp/127.0.0.1/22
[root@test-node ~]# echo $?
0
[root@test-node ~]# exec 3<> /dev/tcp/127.0.0.1/23
-bash: connect: 接続を拒否されました
-bash: /dev/tcp/127.0.0.1/23: 接続を拒否されました
[root@test-node ~]# echo $?
1

6.グルーピングしてまとめてリダイレクトする

前にもここで書いた事があるが、「()」もしくは「{}」でコマンドを囲むことでまとめてリダイレクトさせることが出来る。

(コマンド1;コマンド2;...) > ファイル # サブシェルあり
{ コマンド1;コマンド2;...; } > ファイル # サブシェルなし
[root@test-node ~]# (echo aaa;echo bbb) > /tmp/aaa123
[root@test-node ~]# cat /tmp/aaa123
aaa
bbb
[root@test-node ~]#
[root@test-node ~]# { echo aaa;echo bbb; } > /tmp/aaa123_2
[root@test-node ~]# cat /tmp/aaa123_2
aaa
bbb

7.コマンドに複数コマンドの実行結果を渡す

一つのコマンドの実行結果を渡すだけなら「|(パイプ)」でいいのだが、時には複数コマンドの実行結果を渡したい時だってある。
その場合は、シェル芸勉強会などではよく見かけるのだが、以下のようにリダイレクトさせればよい。
(一応、この機能には「Process Substitution」という名前が付いているらしい)

コマンド <(コマンド)

複数ある場合は、そのままスペース区切りで続けて記述すれば良いだけだ。

コマンド <(コマンド) <(コマンド)
[root@test-node ~]# cat /work/test_sort1
2 dddddd
1 aaaaaa
5 bbbbbb
6 gfgfgr
[root@test-node ~]# cat /work/test_sort2
6 gfgfgr
4 bbbbbb
1 aaaaaa
3 cfffff
[root@test-node ~]# sort /work/test_sort1
1 aaaaaa
2 dddddd
5 bbbbbb
6 gfgfgr
[root@test-node ~]# sort /work/test_sort2
1 aaaaaa
3 cfffff
4 bbbbbb
6 gfgfgr
[root@test-node ~]# diff <(sort /work/test_sort1) <(sort /work/test_sort2)
2,3c2,3
< 2 dddddd
< 5 bbbbbb --- > 3 cfffff
> 4 bbbbbb

8.コマンドの実行結果を他のコマンドに連携する(パイプ)

流石にこれは説明しなくてもいいだろう。
コマンドの実行結果をパイプで繋げる事で連携していく、UNIX系OSのシェルなら大体持っている機能だ。

コマンド | コマンド
[root@test-node ~]# grep "" /work/hostname_*
/work/hostname_aaa_000.txt:aaa_000
/work/hostname_aaa_001.txt:aaa_001
/work/hostname_aaa_002.txt:aaa_002
/work/hostname_aaa_003.txt:aaa_003
/work/hostname_aaa_004.txt:aaa_004
/work/hostname_aaa_005.txt:aaa_005
/work/hostname_bbb_000.txt:aaa_000
/work/hostname_bbb_001.txt:aaa_001
/work/hostname_bbb_002.txt:aaa_002
/work/hostname_bbb_003.txt:aaa_003
/work/hostname_bbb_004.txt:aaa_004
/work/hostname_bbb_005.txt:aaa_005
[root@test-node ~]# grep "" /work/hostname_* | grep aaa_000
/work/hostname_aaa_000.txt:aaa_000
/work/hostname_bbb_000.txt:aaa_000

なお、bash4.1以降の場合だと、以下のようにコマンドを実行することで標準エラー出力を渡す事も出来るようになったようだ。

コマンド |& コマンド
[root@test-node ~]# grep "" /work/hostneme_*
grep: /work/hostneme_*: そのようなファイルやディレクトリはありません
[root@test-node ~]#
[root@test-node ~]# grep "" /work/hostneme_* | sed 's/work/aaaa/g'
grep: /work/hostneme_*: そのようなファイルやディレクトリはありません
[root@test-node ~]#
[root@test-node ~]# grep "" /work/hostneme_* |& sed 's/work/aaaa/g'
grep: /aaaa/hostneme_*: そのようなファイルやディレクトリはありません

9.パイプでつなげた各コマンドの実行結果(exitcode)を確認する

パイプでつなげた場合、途中のコマンドでエラーが出てもecho $?ではパイプの最後にあるコマンドの結果しか表示されない。
各コマンドのexitcodeを取得する場合は、bash変数のPIPESTATUSから取得すると良いだろう。

echo ${PIPESTATUS[@]}
[root@test-node ~]# grep "" /euc |&  sed 's/euc/aaa/g'
grep: /aaa: そのようなファイルやディレクトリはありません
[root@test-node ~]# echo ${PIPESTATUS[@]}
2 0