Help us understand the problem. What is going on with this article?

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

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 はこの記事でも使用した関数です。

mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした