1. 概要

Linuxコマンドラインを使用する場合、あるコマンドの出力を別のコマンドにパイプすることがよくあります。

ただし、コマンドの出力を複数のコマンドに送信するという問題に直面する場合があります。 このチュートリアルでは、この問題を解決する方法について説明します。

2. 問題の紹介

まず、問題をより簡単に理解できるように、具体的な例を見てみましょう。

学生の名前と試験のスコアを保持する入力ファイルscores.txtがあるとします。

$ cat scores.txt 
Mark 98.3
Kent 99.7
Amanda 88.8
Eric 95
John 70
Timo 42
Jerry 93

さらに、3つのシェルスクリプトファイルを作成しました。

  • avg.sh –すべての学生の平均スコアを計算し、ファイルに保存します: avg.result
  • max.sh –学生名を含む最高スコアをファイルに保存します: max.result
  • min.sh –学生名を含む最低スコアをファイルに保存します: min.result

私たちの問題は、コマンド catscore.txtの出力を送信することです。 上記の3つのスクリプトにアクセスして、平均、最高、最低のスコアを取得します。

それでは、スクリプトを詳しく見てみましょう。 各スクリプトには、計算を実行して結果をファイルに保存するための短いawkコマンドが含まれています。

$ head *.sh
==> avg.sh <==
#!/bin/bash
if [ -p /dev/stdin ]; then
    awk '{ sum+=$2 } END{ printf "The average score: %.2f\n", sum/NR }' /dev/stdin > avg.result
else
    echo "Error Occured: No input was found on stdin!"
fi

==> max.sh <==
#!/bin/bash
if [ -p /dev/stdin ]; then
    awk 'max < $2{ max=$2; max_row=$0 } \
        END{ printf "The highest score: %s\n", max_row }' /dev/stdin > max.result
else
    echo "Error Occured: No input was found on stdin!"
fi

==> min.sh <==
#!/bin/bash
if [ -p /dev/stdin ]; then
    awk 'NR==1 || $2 < min{ min=$2; min_row=$0 } \
        END{ printf "The lowest score: %s\n", min_row }' /dev/stdin > min.result
else
    echo "Error Occured: No input was found on stdin!"
fi

上記の3つのスクリプトは非常に簡単です。 ただし、言及する価値のある点がいくつかあります。

  • すべてのスクリプトは、 stdin (/ dev / stdin)からの入力のみを読み取ります
  • 各スクリプトで[ -p / dev /stdin]チェックインする必要があります。 それ以外の場合、s tdin で入力せずにスクリプトを実行すると、何かが入力されるまでハングします。

このチュートリアルでは、次の3つの方法で問題を解決する方法について説明します。

3. teeコマンドとプロセス置換の使用

tee コマンドとプロセス置換を使用して、stdinをプロセスに直接フィードできます。

$ tee >(process1) >(process2) >(process3)....

したがって、私たちの問題は次のように解決できます。

$ cat scores.txt | tee >(./min.sh) >(./max.sh) | ./avg.sh
$ head *.result
==> avg.result <==
The average score: 82.30

==> max.result <==
The highest score: Kent 99.7

==> min.result <==
The lowest score: Timo 42

このソリューションは非常に簡単です。 ただし、すべてのシェルがプロセス置換機能をサポートしているわけではありません。シェルが Bash Zsh 、または Ksh93 の場合、プロセス置換を使用できます。 ]。

次に、この問題に対するよりポータブルなソリューションをいくつか見てみましょう。

4. teeコマンドと名前付きパイプの使用

名前付きパイプの使用は、すべての* nixシステムでサポートされているため、問題を解決するための移植可能な方法です。

名前付きパイプとは何か、その使用方法を理解するには、数分を費やす価値があります。

4.1. 一言で言えばパイプ

名前のないパイプについてはすでによく知っており、Linuxコマンドラインで頻繁に使用しています。

名前のないパイプは、command1|などの異なるプロセス間でデータを転送できるようにするための便利な手法です。 command2。 ただし、無名パイプはカーネルにのみ存在し、command2。以外のプロセスからアクセスすることはできません。

名前付きパイプは、名前なしパイプに似ています。 また、ファイルシステムに存在し、読み取りと書き込みのために複数のプロセスで開くことができます。

名前付きパイプの設定と使用は便利です。 名前付きパイプを使用する標準的な操作を見てみましょう。

  • mkfifo myPipe –「myPipe」という名前の名前付きパイプを作成します。 特別なファイルmyPipeが作成されます
  • command1> myPipe command1の出力を名前付きパイプmyPipeにリダイレクトします
  • command2 command2 からデータを読み取ります myPipe 入力として
  • rm myPipe –通常のファイルを削除するのと同じように、名前付きパイプを閉じます

4.2. 名前付きパイプを使用した小さな例

名前付きパイプとは何かを理解した後、例を通して名前付きパイプの使用方法を示しましょう。

まず、mkfifoコマンドを使用して名前付きパイプを作成しましょう。

$ mkfifo myPipe
$ ls -l myPipe 
prw-r--r-- 1 kent kent 0 Jul 12 16:38 myPipe

上記のlsコマンドの出力で、左端の列の p は、ファイルmyPipeが名前付きパイプであることを示しています。

その後、作成した名前付きパイプからデータを読み取るための短いawkコマンドを指示します。

$ awk '$0 = NR ":" $0' > withLineNumber.txt < myPipe &
[1] 467439

awk コマンドは、名前付きパイプからデータを読み取ります。 さらに、各行のプレフィックスとして行番号を追加し、その結果をwithLineNumber.txt。というファイルに保存します。

コマンドがで終了していることに気付くかもしれません キャラクター。 これは、現在のシェルで他のコマンドを入力したいためです。 &演算子を実行することができます awk バックグラウンドでコマンド。

次に、score.txtファイルを使用して名前付きパイプにフィードします。

$ cat scores.txt > myPipe

名前付きパイプにデータが書き込まれた後、バックグラウンドジョブが実行されます。

$ jobs
[1]+  Done            awk '$0 = NR ":" $0' > withLineNumber.txt < myPipe

次に、 withLineNumber.txt が作成され、期待されるデータが入力されているかどうかを確認しましょう。

$ cat withLineNumber.txt 
1:Mark 98.3
2:Kent 99.7
3:Amanda 88.8
4:Eric 95
5:John 70
6:Timo 42
7:Jerry 93

最後に、rmコマンドを使用して名前付きパイプを閉じる必要があります。

$ rm myPipe

4.3. teeと名前付きパイプを使用して問題を解決します

それでは、teeコマンドと名前付きパイプを使用して問題を解決する方法を見てみましょう。

$ mkfifo myPipe1 myPipe2
$ ./min.sh < myPipe1 &
[1] 482525
$ ./max.sh < myPipe2 &
[2] 482657
$ cat scores.txt | tee myPipe1 myPipe2 |./avg.sh 

さらに、結果ファイルが作成されたかどうかを確認しましょう。

$ head *.result
==> avg.result <==
The average score: 82.30

==> max.result <==
The highest score: Kent 99.7

==> min.result <==
The lowest score: Timo 42

すごい! 結果ファイルが生成されました。

最後に、rmコマンドを使用してパイプを閉じることを忘れないでください。

$ rm myPipe1 myPipe2

このソリューションでは、 tee コマンドは、score.txtファイルのコンテンツを両方の名前付きパイプにリダイレクトするのに役立ちます。 名前付きパイプがどのように機能するかを知っていれば、解決策を理解することは問題にはなりません。

5. teeコマンドとファイル記述子の使用

任意のPOSIXシェルで複数のファイル記述子を使用できるため、ファイル記述子を使用するソリューションも移植可能です。

まず、ファイル記述子を使用して問題を解決する方法を見てみましょう。

$ { { cat scores.txt| tee /dev/fd/3 /dev/fd/4 | ./avg.sh \
     } 3>&1 | ./min.sh  \
   } 4>&1 | ./max.sh    \

上記のコマンドを実行した後、結果ファイルをチェックして、期待される結果が含まれているかどうかを確認しましょう。

$ head *.result
==> avg.result <==
The average score: 82.30

==> max.result <==
The highest score: Kent 99.7

==> min.result <==
The lowest score: Timo 42

ここで、ソリューションがどのように機能するかを理解しましょう。

  • 中括弧{…}–中括弧を使用してコマンドをラップし、サブシェルではなく現在のシェルでそれらのコマンドを実行します
  • tee / dev / fd / 3 / dev / fd / 4 tee コマンドは、ファイルコンテンツを2つのファイル記述子(FD)3および4にリダイレクトします。
  • ティー…| ./avg.sh; –また、 tee コマンドは、ファイルの内容をavg.shスクリプトにパイプします。
  • {猫.. | tee ..} 3>&1 | ./min.sh; –FD3をFD1にリダイレクトします。これは stdout 。 次に、stdoutを入力としてmin.shにパイプします
  • {…}4>&1 | ./max.sh; – 同様に、FD4をにリダイレクトします stdout 次に、にパイプします max.sh. The max.sh スクリプトはこれを入力として受け取り、最高のスコアを見つけます

6. 結論

この記事では、コマンドの出力を複数のコマンドに送信する問題を解決しました。 そして、例を通して3つの異なるソリューションについて説明しました。

3つの方法はすべて、中央の tee コマンドを使用して、前のコマンドの出力をリダイレクトします。

まず、teeとプロセス置換を使用する方法について説明しました。 シェルがこの機能をサポートしている場合、これは簡単な解決策です。

それ以外の場合、より移植性の高いソリューションを探している場合は、名前付きパイプまたはファイル記述子でteeを使用することを検討できます。