web-dev-qa-db-ja.com

「リダイレクト」と「パイプ」の違いは何ですか?

この質問は少し馬鹿げているように聞こえるかもしれませんが、リダイレクトとパイプの違いを実際に見ることはできません。

リダイレクトは、stdout/stdin/stderrのリダイレクトに使用されます。 ls > log.txt

パイプは、コマンドの出力を別のコマンドへの入力として与えるために使用されます。 ls | grep file.txt

しかし、同じことに対して2つの演算子があるのはなぜですか?

なぜls > grepと書くだけで出力を渡すのではなく、これも一種のリダイレクトではありませんか?私は何が欠けていますか?

197
John Threepwood

パイプは、出力を別のプログラムまたはユーティリティに渡すために使用されます。

リダイレクトは、出力をファイルまたはストリームに渡すために使用されます。

例:thing1 > thing2 vs thing1 | thing2

thing1 > thing2

  1. シェルはthing1という名前のプログラムを実行します
  2. thing1が出力するすべてのものは、thing2というファイルに配置されます。 (注-thing2が存在する場合、上書きされます)

プログラムthing1からの出力をthing2と呼ばれるプログラムに渡したい場合、以下を実行できます。

thing1 > temp_file && thing2 < temp_file

どっちが

  1. thing1という名前のプログラムを実行します
  2. 出力をtemp_fileという名前のファイルに保存します
  3. thing2という名前のプログラムを実行し、キーボードの人がtemp_fileの内容を入力として入力したように見せかけます。

しかし、それは不格好なので、彼らはそれを行う簡単な方法としてパイプを作成しました。 thing1 | thing2thing1 > temp_file && thing2 < temp_fileと同じことをします

コメントで質問する詳細を提供するために編集:

>が「プログラムへのパス」と「ファイルへの書き込み」の両方を試みた場合、両方向で問題が発生する可能性があります。

最初の例:ファイルに書き込もうとしています。上書きしたい名前のファイルが既に存在します。ただし、ファイルは実行可能です。おそらく、入力を渡してこのファイルを実行しようとします。出力を新しいファイル名に書き込んでから、ファイルの名前を変更する必要があります。

2番目の例: Florian Dieschが指摘したように、同じ名前の別のコマンドがシステムのどこかにある場合(実行パスにある場合)。現在のフォルダにその名前のファイルを作成するつもりだった場合は、行き詰まってしまいます。

番目:コマンドを誤って入力しても、コマンドが存在しないことを警告することはありません。現在、ls | gerp log.txtと入力すると、bash: gerp: command not foundと表示されます。 >が両方を意味する場合、単純に新しいファイルを作成します(log.txtをどうするかわからないことを警告します)。

218
David Oneill

foo > barの意味がbarという名前のコマンドが存在するかどうかに依存する場合、リダイレクトを使用するのがはるかに難しくなり、エラーが発生しやすくなります。宛先ファイルのような名前のコマンドがあるかどうか。

22
Florian Diesch

UnixおよびLinuxシステム管理ハンドブックから:

リダイレクション

シェルは、シンボル<、>、および>>をコマンド入力または出力をfileに、またはそこから再ルーティングする命令として解釈します。

パイプ

1つのcommandのSTDOUTをanotherのSTDINに接続するには、|を使用します。一般にパイプとして知られるシンボル。

だから私の解釈は次のとおりです。もしそれが命令するなら、パイプを使いましょう。ファイルへまたはファイルから出力する場合は、リダイレクトを使用します。

12
Mr Whatever

2つの演算子には重大な違いがあります。

  1. ls > log.txt->このコマンドは、出力をlog.txtファイルに送信します。

  2. ls | grep file.txt->このコマンドは、パイプ(|)を使用してlsの出力をgrepコマンドに送信し、grepコマンドは、提供された入力でfile.txtを検索します前のコマンド。

最初のシナリオを使用して同じタスクを実行する必要がある場合、次のようになります。

ls > log.txt; grep 'file.txt' log.txt

したがって、パイプ(|を使用)は出力を他のコマンドに送信するために使用され、リダイレクト(>を使用)は出力をファイルにリダイレクトするために使用されます。

12
Ankit

注:回答は、このサイト上のピアによる調査と回答の読み取りを通じて蓄積されたこれらのメカニズムの最新の自分自身の理解を反映しており、 nix.stackexchange.com であり、時間の経過とともに更新されますオン。質問をするか、コメントの改善を提案することをheしないでください。また、straceコマンドを使用して、シェルでsyscallがどのように機能するかを確認することをお勧めします。また、内部またはsyscallの概念に怖がらないでください。Shellがどのように物事を行うのかを理解するためにそれらを知ったり使用したりする必要はありませんが、それらは間違いなく理解に役立ちます。

TL; DR

  • |パイプはディスク上のエントリに関連付けられていません したがって、iノードがありません ディスクファイルシステムの数(ただし、 pipefs カーネル空間に仮想ファイルシステムがあります)、しかし、多くの場合、リダイレクトにはファイルが含まれます。ファイルにはディスクエントリがあり、対応するiノードがあります。
  • パイプはlseek() 'ableではないため、コマンドは一部のデータを読み取って巻き戻すことはできませんが、>または<を使用してリダイレクトする場合、通常はlseek()ableオブジェクトのファイルなので、コマンドは好きなようにナビゲートできます。
  • リダイレクションはファイル記述子の操作であり、多くの場合があります。パイプには2つのファイル記述子しかありません-1つは左コマンド用、もう1つは右コマンド用です
  • 標準ストリームとパイプのリダイレクトは両方ともバッファリングされます。
  • パイプはほとんど常に分岐を伴うため、プロセスのペアが関係します。リダイレクト-常にではありませんが、どちらの場合も、結果のファイル記述子はサブプロセスに継承されます。
  • パイプは常にファイル記述子(ペア)、リダイレクトを接続します-パス名またはファイル記述子を使用します。
  • パイプはプロセス間通信の方法ですが、リダイレクトは開いているファイルまたはファイルのようなオブジェクトに対する操作にすぎません
  • 両方とも、フードの下にdup2() syscallsを使用して、実際のデータフローが発生するファイル記述子のコピーを提供します。
  • リダイレクトは、exec組み込みコマンド( this および this を参照)を使用して「グローバルに」適用できます。したがって、exec > output.txtを実行すると、すべてのコマンドがoutput.txtに書き込まれます。 |パイプは現在のコマンドにのみ適用されます(これは、単純なコマンド、またはseq 5 | (head -n1; head -n2)のようなサブシェルまたは複合コマンドを意味します。
  • ファイルのリダイレクトが行われると、echo "TEST" > fileecho "TEST" >> fileのようなものは両方とも、そのファイルでopen() syscallを使用し( 参照 )、ファイル記述子を取得してdup2()に渡します。パイプ|は、pipe()およびdup2() syscallのみを使用します。

  • 実行されるコマンドに関しては、パイプとリダイレクトはファイル記述子にすぎません-盲目的に書き込むか、内部で操作するファイルのようなオブジェクト(予期しない動作を引き起こす可能性があります; apt、たとえばリダイレクトがあることがわかっている場合は、stdout にも書き込みません)。

前書き

これら2つのメカニズムがどのように異なるかを理解するには、それらの本質的な特性、2つの背後にある歴史、およびCプログラミング言語のルーツを理解する必要があります。実際、ファイル記述子が何であるか、およびdup2()およびpipe()システムコールがどのように機能するかを知ることは、lseek()と同様に不可欠です。シェルは、これらのメカニズムをユーザーに抽象化する方法を意味しますが、抽象化よりも深く掘り下げると、シェルの動作の本質を理解するのに役立ちます。

リダイレクションとパイプの起源

Dennis Ritcheの記事 Prophetic Petroglyphs によると、パイプは 1964内部メモ by Malcolm Douglas McIlroy によって作成されていました- Multicsオペレーティングシステム 。見積もり:

私の最も強い懸念を一言で言うと:

  1. ガーデンホースのようなプログラムを接続するいくつかの方法が必要です。別の方法でデータをマッサージする必要が生じたときに、別のセグメントにネジを締めます。これは、IOの方法でもあります。

明らかなことは、当時はプログラムがディスクに書き込むことができたが、出力が大きい場合は効率が悪いことでした。 nix Pipeline videoのBrian Kernighanの説明を引用するには:

まず、1つの大きな大規模なプログラムを作成する必要はありません-すでに仕事の一部を行っている既存の小さなプログラムを持っています...もう1つは、処理しているデータの量が次の場合に収まらない可能性があることですファイルに保存しました...幸運だったら、これらのディスクにディスクがあった時代に戻ったのを思い出してください。 。

したがって、概念的な違いは明らかです。パイプは、プログラムが互いに対話するメカニズムです。リダイレクト-基本レベルでファイルに書き込む方法です。どちらの場合も、Shellはこれら2つのことを簡単にしますが、ボンネットの下では、非常に多くのことが行われています。

さらに深く:システムコールとシェルの内部動作

ファイル記述子 の概念から始めます。ファイル記述子は、基本的に開いているファイル(ディスク上のファイル、メモリ内のファイル、または匿名ファイル)を表し、整数で表されます。 2つの 標準データストリーム (stdin、stdout、stderr)は、それぞれファイル記述子0、1、および2です。彼らはどこから来たのか ?さて、シェルコマンドでは、ファイル記述子は親-シェルから継承されます。そして、一般的にすべてのプロセスに当てはまります-子プロセスは親のファイル記述子を継承します。 daemons の場合、継承されたすべてのファイル記述子を閉じたり、他の場所にリダイレクトしたりするのが一般的です。

リダイレクトに戻ります。それは本当に何ですか?これは、コマンドのファイル記述子を準備するようシェルに指示し(コマンドの実行前にシェルによってリダイレクトが行われるため)、ユーザーが提案した場所をポイントするメカニズムです。出力リダイレクトの 標準定義

[n]>Word

[n]にはファイル記述子番号があります。 echo "Something" > /dev/nullを実行すると、そこで1が暗示され、echo 2> /dev/nullが暗示されます。

内部では、これはdup2()システムコールを介してファイル記述子を複製することによって行われます。 df > /dev/nullを見てみましょう。シェルはdfが実行される子プロセスを作成しますが、その前にファイル記述子#3として/dev/nullを開き、dup2(3,1)が発行され、ファイル記述子3のコピーが作成され、コピーは1になります。 2つのファイルfile1.txtfile2.txtを使用し、cp file1.txt file2.txtを実行すると2つの同じファイルが作成されますが、それらを個別に操作できますか?ここでも同じことが起こっています。多くの場合、実行前にbashdup(1,10)を実行して、後で復元するためにstdout(およびそのコピーはfd#10)であるコピーファイル記述子#1を作成します。重要なのは、 組み込みコマンド (シェル自体の一部であり、/binまたは他の場所にファイルがない)または 非対話型シェルの単純なコマンド =、シェルは子プロセスを作成しません。

そして、[n]>&[m][n]&<[m]のようなものがあります。これはファイル記述子を複製するもので、これはdup2()と同じメカニズムがシェル構文にあり、ユーザーが便利に使用できるようになっただけです。

リダイレクトに関して注意すべき重要なことの1つは、それらの順序が固定されていないことですが、シェルがユーザーの希望を解釈する方法にとって重要です。以下を比較してください。

# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/  3>&2  2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd

# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/    2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd

シェルスクリプトでのこれらの実用的な用途は多岐にわたります。

その他多数。

pipe()およびdup2()を使用した配管

それでは、パイプはどのように作成されますか? pipe() syscall を介して、タイプpipefd(整数)の2つの項目のintと呼ばれる配列(別名リスト)を入力として受け取ります。これらの2つの整数はファイル記述子です。 pipefd[0]がパイプの読み取り側になり、pipefd[1]が書き込み側になります。したがって、df | grep 'foo'では、greppipefd[0]のコピーを取得し、dfpipefd[1]のコピーを取得します。しかし、どのように?もちろん、dup2() syscallの魔法で。この例のdfの場合、pipefd[1]が#4であるため、シェルは子を作成し、dup2(4,1)を実行し(cpの例を覚えていますか?)、execve()を実行して実際にdfを実行します。当然、dfはファイル記述子#1を継承しますが、それがもはや端末を指していないことに気付かないでしょうが、実際にはfd#4(実際にはパイプの書き込み側)です。当然、ファイル記述子の数が異なる場合を除き、grep 'foo'でも同じことが起こります。

さて、興味深い質問:fd#1だけでなく、fd#2もリダイレクトするパイプを作成できますか?はい、実際、bashでは|&がそれを行います。 POSIX標準では、そのためにdf 2>&1 | grep 'foo'構文をサポートするシェルコマンド言語が必要ですが、bash|&をサポートしています。

重要なのは、パイプが常にファイル記述子を処理することです。 FIFOまたは 名前付きパイプ が存在します。ディスク上にファイル名があり、ファイルとして使用できますが、パイプのように動作します。しかし、|タイプのパイプは、匿名パイプと呼ばれるものです。ファイル名はありません。実際には、2つのオブジェクトが接続されているだけです。ファイルを処理していないという事実も重要な意味を持っています。 パイプはlseek() 'ableではありません。 メモリまたはディスク上のファイルは静的です-プログラムはlseek() syscallを使用してジャンプできますバイト120に戻り、バイト10に戻り、最後まで転送します。パイプは静的ではありません-連続しているため、lseek()を使用してパイプから取得したデータを巻き戻すことはできません。これにより、一部のプログラムがファイルまたはパイプから読み取りを行っているかどうかを認識できるため、効率的なパフォーマンスのために必要な調整を行うことができます。つまり、progは、cat file.txt | progまたはprog < input.txtを実行しているかどうかを検出できます。その実例は tail です。

パイプの他の2つの非常に興味深い特性は、バッファがあることです。これは Linuxでは4096バイト であり、実際には Linuxソースコードで定義されているファイルシステム !それらはデータを渡すための単なるオブジェクトではなく、データ構造そのものです!実際、パイプとFIFOの両方を管理するpipefsファイルシステムが存在するため、それぞれのファイルシステムに パイプにiノードがあります 番号:

# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/  | cat  
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'

Linuxでは、パイプはリダイレクトと同様に単方向です。 Unixライクな実装では、双方向パイプがあります。シェルスクリプトの魔法で、 Linuxの双方向パイプ も作成できます。

参照:

3

2つの間に大きな構文上の違いがあります。

  1. リダイレクトはプログラムへの引数です
  2. パイプは2つのコマンドを分離します

リダイレクトはcat [<infile] [>outfile]のように考えることができます。これは、順序が重要ではないことを意味します。cat <infile >outfilecat >outfile <infileと同じです。リダイレクトを他の引数と混在させることもできます。cat >outfile <infile -bcat <infile -b >outfileはどちらも完全に問題ありません。また、複数の入力または出力をつなぎ合わせることもできます(入力は順番に読み取られ、すべての出力は各出力ファイルに書き込まれます):cat >outfile1 >outfile2 <infile1 <infile2。リダイレクトのターゲットまたはソースは、ファイル名またはストリームの名前(少なくともbashでは&1など)のいずれかです。

ただし、パイプは1つのコマンドを別のコマンドから完全に分離します。それらを引数と混在させることはできません。

[command1] | [command2]

パイプは、command1から標準出力に書き込まれたすべてを取得し、command2の標準入力に送信します。

パイピングとリダイレクトを組み合わせることもできます。例えば:

cat <infile >outfile | cat <infile2 >outfile2

最初のcatはinfileから行を読み取り、同時に各行をoutfileに書き込み、2番目のcatに送信します。

2番目のcatでは、標準入力は最初にパイプ(infileの内容)から読み取り、次にinfile2から読み取り、各行をoutfile2に書き込みます。これを実行すると、outfileはinfileのコピーになり、outfile2にはinfileの後にinfile2が含まれます。

最後に、 "here string"リダイレクト(bashファミリのみ)とバックティックを使用して、実際にあなたの例に本当に似たようなことをします:

grep blah <<<`ls`

と同じ結果が得られます

ls | grep blah

しかし、リダイレクトバージョンは最初にlsのすべての出力を(メモリ内の)バッファーに読み込んでから、そのバッファーを一度に1行ずつgrepにフィードしますが、パイプバージョンではlsから各行が出現するたびに、その行をgrepに渡します。

3
user319857

他の答えに追加するために、微妙な意味の違いもあります-例えば。パイプはリダイレクトよりもすぐに閉じます:

seq 5 | (head -n1; head -n1)                # just 1
seq 5 > tmp5; (head -n1; head -n1) < tmp5   # 1 and 2
seq 5 | (read LINE; echo $LINE; head -n1)   # 1 and 2

最初の例では、headの最初の呼び出しが終了すると、パイプが閉じられ、seqが終了するため、2番目のheadに使用できる入力はありません。

2番目の例では、headは最初の行を消費しますが、headがそれを閉じると、stdinpipeになり、次の呼び出しで使用するためにファイルは開いたままになります。

3番目の例は、readを使用してパイプが閉じないようにする場合、サブプロセス内で引き続き使用できることを示しています。

そのため、「ストリーム」はデータをシャントするもの(stdinなど)であり、どちらの場合も同じですが、パイプは2つのプロセスからのストリームを接続します。リダイレクトはプロセスとファイルの間のストリームを接続します。類似点と相違点の両方の原因を確認できます。

追伸あなたが私と同じようにそれらの例に興味を持っている、および/または驚いているなら、あなたはプロセスをどのように解決するかを見るためにtrapをさらに使用してDigを得ることができます、例えば:

(trap 'echo seq EXITed >&2' EXIT; seq 5) | (trap 'echo all done' EXIT; (trap 'echo first head exited' EXIT; head -n1)
echo '.'
(trap 'echo second head exited' EXIT; head -n1))

1が出力される前に、場合によっては最初のプロセスが閉じることがあります。

また、exec <&-を使用して、リダイレクトからのストリームを閉じて、パイプの動作を近似することも(エラーはありますが)興味深いことがわかりました。

seq 5 > tmp5
(trap 'echo all done' EXIT
(trap 'echo first head exited' EXIT; head -n1)
echo '.'
exec <&-
(trap 'echo second head exited' EXIT; head -n1)) < tmp5`
2
Julian de Bhal

今日、Cでこれに問題があります。基本的に、Pipeには、stdinに送信された場合でも、リダイレクトに対する異なるセマンティクスもあります。本当に違いがあると思うので、パイプはstdin以外の場所に行くべきだと思うので、stdinを呼び出してstdpipe(任意の差分を作成する)をさまざまな方法で処理できます。

このことを考慮。あるプログラムの出力を別のプログラムにパイプすると、st_sizeがファイルがあることを示しているにもかかわらず、ls -lha /proc/{PID}/fdとしてfstatがゼロを返すようです。ファイルをリダイレクトする場合、これは当てはまりません(少なくともdebian wheezystretchおよびjessie Vanillaおよびubuntu 14.0416.04 Vanillaではそうではありません。

cat /proc/{PID}/fd/0リダイレクトを使用すると、好きなだけ何度でも読むことができます。パイプを使用してこれを行うと、タスクを連続して2回実行すると、同じ出力が得られないことがわかります。

1
MrMesees