複数のコマンドに標準出力を送信するにはどうすればよいですか?


186

入力をフィルタリングまたは処理し、それを出力として渡すstdoutコマンドがいくつかありますが、通常はそうなると思いますが、一部のコマンドはそれを受け取り、stdinそれで何でもし、何も出力しません。

私はOS Xで最もよく知っているので、気になる二人はすぐにありますされているpbcopypbpaste-システムクリップボードにアクセスする手段です。

とにかく、stdoutを使用して出力を吐き出し、両方stdoutとファイルに移動する場合は、teeコマンドを使用できることを知っています。そして、私は少し知っxargsていますが、私はそれが私が探しているものだとは思わない。

stdout2つ(またはそれ以上)のコマンドに分割する方法を知りたい。例えば:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

おそらくそれよりも良い例がありますが、私はそれを中継しないコマンドにstdoutを送信し、stdout「ミュート」されないようにする方法を知っていることに本当に興味がcatあります- grepその一部をクリップボードにコピーします-特定のコマンドはそれほど重要ではありません。

また、私はこれをファイルに送信する方法を尋ねていませんstdout-これは「重複」の質問かもしれません(申し訳ありません) -そして、それらの質問に対する答えはのようteeでした。

最後に、「pbcopyをパイプチェーンの最後のものにしないのはなぜですか?」私の応答は1)それを使用して、コンソールに出力を表示したい場合はどうなりますか?2)stdout入力を処理した後に出力しない2つのコマンドを使用する場合はどうなりますか?

ああ、もう1つ-私teeは名前付きパイプ(mkfifo)を使用できることを理解していますが、事前にセットアップせずに、これをインラインで簡潔に行う方法を望んでいました:)


回答:


240

teeこれには、置換を使用および処理できます。

cat file.txt | tee >(pbcopy) | grep errors

これにより、cat file.txttoのすべての出力が送信され、コンソールpbcopyでのみ結果が取得さgrepれます。

teeパーツに複数のプロセスを配置できます。

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors

21
には関係ありませんpbcopyが、一般的に言及する価値があります。プロセス置換出力は、元の入力の後の次のパイプセグメントで見られます。例えば:seq 3 | tee >(cat -n) | cat -ecat -n数字の入力ラインは、cat -eとの改行をマークし$、あなたはそれが表示されますcat -e元の入力(第一)と(当時)の出力からの両方に適用されますcat -n)。複数のプロセス置換からの出力は、非決定的な順序で到着します。
mklement0 14

49
>(のみ動作しbashます。たとえば、shそれを使用して試してみると機能しません。この通知をすることが重要です。
AAlvz 14

10
@AAlvz:良い点:プロセス置換はPOSIXの機能ではありませんdashshUbuntuのように動作しますが、サポートしていません。また、Bash自体も、有効になっているshとき、またはset -o posix有効になっているときに機能を無効にします。ただし、プロセス置換をサポートしているのはBashだけではありません。kshまた、zshそれらもサポートしています(他の人についてはわかりません)。
mklement0

2
@ mklement0は真実ではないようです。zsh(Ubuntu 14.04)では、次の行が出力されます:1 1 2 2 3 3 1 $ 2 $ 3 $これは悲しい機能です。
アクタウ

2
@Aktau:確かに、私のサンプルコマンドのみで説明したように動作bashし、ksh- zsh明らかに(恐らく間違いなく、それはだ、パイプラインを介して出力処理の置換からの出力を送信しないことが好ましいけれども-それは次のパイプライン・セグメントに送られるものを汚染しないので、まだ印刷されます)。では、すべての出力順序は予測可能ではありません、方法で、まれにしかまたは大型で表面化-言及したシェルは、しかし、それは一般的に、プロセスの置換から、通常の標準出力と出力が混合された単一のパイプラインを持っていることは良い考えではありません出力データセット。
mklement0

124

複数のファイル名をに指定できtee、さらに標準出力を1つのコマンドにパイプで送ることができます。出力を複数のコマンドにディスパッチするには、複数のパイプを作成し、各パイプをの1つの出力として指定する必要がありますtee。これを行うにはいくつかの方法があります。

プロセス置換

シェルがksh93、bash、またはzshの場合、プロセス置換を使用できます。これは、ファイル名を期待するコマンドにパイプを渡す方法です。シェルはパイプを作成/dev/fd/3し、コマンドのようなファイル名を渡します。番号は、パイプが接続されているファイル記述子です。一部のUNIXバリアントはサポートしていません/dev/fd。これらでは、代わりに名前付きパイプが使用されます(以下を参照)。

tee >(command1) >(command2) | command3

ファイル記述子

POSIXシェルでは、複数のファイル記述子を明示的に使用できます。これには、をサポートするUNIXバリアントが必要です。これは、/dev/fdの出力の1つを除くすべてがtee名前で指定される必要があるためです。

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

名前付きパイプ

最も基本的で移植可能な方法は、名前付きパイプを使用することです。欠点は、書き込み可能なディレクトリを見つけ、パイプを作成し、その後クリーンアップする必要があることです。

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2

10
bashや特定のkshに依存したくない人のために2つの代替バージョンを提供してくれてありがとう。
trr

tee "$tmp_dir/f1" "$tmp_dir/f2" | command3確かにcommand3 | tee "$tmp_dir/f1" "$tmp_dir/f2"、あなたはcommand3パイプの標準出力をしたいteeのですか?私はあなたのバージョンでは、下でテストdashし、tee期待される結果を生成したブロックは無期限の入力を待っているが、順序を切り替えます。
エイドリアンギュンター

1
@AdrianGünter号全ての3つの例は、標準入力からデータを読み取り、それぞれにそれを送るcommandcommand2そしてcommand3
ジル

@Gilles、私は意図を誤って解釈し、スニペットを誤って使用しようとしました。説明をありがとう!
エイドリアンギュンター

使用するシェルを制御できない場合でも、bashを明示的に使用できる場合は、できます<command> | bash -c 'tee >(command1) >(command2) | command3'。それは私の場合に役立ちました。
gc5

16

プロセス置換で遊んでください。

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepmycommand_execプロセス固有の入力と同じ出力を持つ2つのバイナリです。


16

使用している場合はzshMULTIOS機能の力を活用できます。つまり、teeコマンドを完全に取り除きます。

uname >file1 >file2

の出力unameを2つの異なるファイルに書き込みます:file1およびfile2に相当するものuname | tee file1 >file2

同様に標準入力のリダイレクト

wc -l <file1 <file2

は同等ですcat file1 file2 | wc -l(これはと同じではないことに注意してください。wc -l file1 file2後で、各ファイルの行数を個別にカウントします)。

もちろんMULTIOS、プロセス置換を使用して、ファイルではなく他のプロセスに出力をリダイレクトするために使用することもできます。例:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')

3
知っておくといい。MULTIOSはデフォルトでオンになっているオプションです(でオフにできますunsetopt MULTIOS)。
mklement0

6

コマンドによって生成されるかなり小さな出力の場合、出力を一時ファイルにリダイレクトし、それらの一時ファイルをループ内のコマンドに送信できます。これは、実行されたコマンドの順序が重要な場合に役立ちます。

たとえば、次のスクリプトはそれを実行できます。

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

/bin/shas dashshellを使用したUbuntu 16.04でのテスト実行:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash

5

コマンドSTDOUTを変数にキャプチャし、何度でも再利用します。

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

キャプチャする必要がある場合は、次のようにコマンドの最後でSTDERR使用2>&1します。

commandoutput="$(command-to-run 2>&1)"

3
変数はどこに保存されますか?あなたが大きなファイルまたはその種のものを扱っていた場合、これは多くのメモリを消費しませんか?変数のサイズに制限はありますか?
cwd

1
$ commandoutputが巨大な場合、パイプとプロセス置換を使用する方が良いでしょう。
ニキルマレー

4
明らかに、この解決策は、出力のサイズがメモリに簡単に収まることがわかっていて、次のコマンドを実行する前に出力全体をバッファリングしても問題ない場合にのみ可能です。パイプは、任意の長さのデータを許可し、生成時にリアルタイムでレシーバーにストリーミングすることにより、これら2つの問題を解決します。
trr

2
これは、出力が小さく、出力がバイナリではなくテキストであることがわかっている場合に適したソリューションです。(シェル変数は、多くの場合、バイナリセーフではありません)
Rucent88

1
これをバイナリデータで動作させることはできません。nullバイトやその他の文字以外のデータを解釈しようとするエコーがあると思います。
ロルフ


0

これは、を含むすべてのシェルと互換性のある、手早く汚れた部分的な解決策busyboxです。

それが解決するより狭い問題は、完全なものstdoutを1つのコンソールに出力し、一時ファイルや名前付きパイプなしで別のコンソールでフィルタリングすることです。

  • 同じホストへの別のセッションを開始します。TTY名を確認するには、と入力しttyます。仮定しましょう/dev/pty/2
  • 最初のセッションで、実行します the_program | tee /dev/pty/2 | grep ImportantLog:

1つの完全なログとフィルター処理されたログを取得します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.