Edited at

シェルスクリプトでパイプを判断する

More than 1 year has passed since last update.

Unix の基本的考え方のひとつに「フィルタとして振る舞う」というものがあります。標準入力から入力をして何らかの処理を行い、標準出力に出力を行うように設計されているコマンドを フィルタ と呼びます。フィルタ・コマンドそれ自身は単純な機能しか持っていませんが、これを | (バーティカルバー)で結びつけることでフィルタを部品のように扱い、大きな機能をもったコマンドとして機能させることが出来ます。

pipe.png

(例)

$ history | awk '{print $2}' | sort | uniq -c | sort -nr | head

162 ls
110 mkdir
68 mv
23 rm
11 touch

この例ではパイプごとに説明すると、

ヒストリを表示し|第2フィールドのみを切り出し|ソートして|重複するものをカウントしながらまとめて|数字の大きい順にソートして|先頭を取り出す

ということを行っています。このような芸当ができるのは、Unix のコマンドがフィルタとして振る舞うように設計されているからです。


自作コマンドでもフィルタしたい

フィルタとしてのコマンドを作成する場合、そのコマンドにおいてパイプによる入力を捕捉しなくてはなりません。以下が解答例です。


is_pipe.func

function is_pipe() {

if [ -p /dev/stdin ]; then
#if [ -p /dev/fd/0 ]; then
#if [ -p /proc/self/fd/0 ]; then
#if [ -t 0 ]; then
# echo a | is_pipe
return 0
elif [ -p /dev/stdout ]; then
# is_pipe | cat
return 0
else
# is_pipe (Only!)
return 1
fi
}


解説



  • test コマンドのオプションの意味

オプション
意味

-p
名前付きパイプであれば真

-t
端末にてオープンされていれば真


  • 標準入出力の捕捉

標準入力の捕捉
標準出力の捕捉

[ -p /dev/stdin ]
[ -p /dev/stdout ]

[ -p /dev/fd/0 ]
[ -p /dev/fd/1 ]

[ -p /proc/self/fd/0 ]
[ -p /proc/self/fd/1 ]

[ -t 0 ]
[ -t 1 ]


使い勝手良く


in_pipe.sh

if [ -p /dev/stdin ]; then

echo "pipe!"
cat -
exit 0
else
exit 1
fi

パイプを検知したら pipe! と出力し、標準入力をそのまま標準出力に流すスクリプトです。以下実行例。

$ echo a | ./in_pipe.sh

pipe!
a

次は、パイプ直前に実行された場合のみ pipe! と出力するスクリプトです。


out_pipe.sh

if [ -p /dev/stdout ]; then

echo "pipe!"
exit 0
else
exit 1
fi

以下実行例です。

$ ./out_pipe | cat

pipe!

抽象的すぎてわかりづらいかもしれません。具体化しましょう。


具体例

.bak 拡張子を持つコピーファイルを作成する簡易バックアップコマンド bak を例に、このパイプ判断スクリプトの利点を説明します。


bak

for FILE do

[ ! -e $FILE ] && { echo 1>&2 "$FILE: no exist"; continue; }
if [ -p /dev/stdout ]; then
#if out_pipe; then の意味
# コピーファイルを作成
cp $FILE $FILE.bak
# 自作関数 abs_path でフルパスを標準出力へ流す
echo $(abs_path $_)
else
# copy to current dir
cp $FILE $FILE.bak
fi
done


パイプがなければ普通に .bak をもつコピーファイルを作成して終了(以下)。

$ bak myfile

$ ls
myfile myfile.bak

パイプがある場合は、.bak をもつコピーファイルを作成して、そのコピーファイルのフルパスを標準出力に流します。

実際にやってみる(以下)。

$ bak myfile | xargs mv -t Work/

$ ls
myfile Work/
$ ls Work/
myfile.bak

bak コマンドによる複製が行われてそのまま、mv によるコピーファイルの移動が行われていますね。

ちなみに、今回使用した mv コマンドの -t オプションは GNU のものです。BSD その他の mv では使用できません。


合わせて読みたい

自作コマンドの作り方を記事にしました。

絶対パス・相対パスを取得するを記事にしました。abs_path はこの記事でも使用した関数です。