「catファイル| ./binary」および「./binary <file」?


102

バイナリ(変更できない)があり、次のことができます。

./binary < file

私もできる:

./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF

しかし

cat file | ./binary

エラーが発生します。パイプで動作しない理由がわかりません。3つの場合すべてで、ファイルのコンテンツはバイナリの標準入力に(さまざまな方法で)与えられます。

  1. bashはファイルを読み取り、バイナリの標準入力に渡します
  2. bashはstdinから(EOFまで)行を読み取り、それをバイナリの stdinに渡します
  3. catはファイルの行を読み取ってstdoutに書き込み、bashはそれらをバイナリの stdinにリダイレクトします

私が理解している限り、バイナリはこれら3つの違いに気付かないはずです。3番目のケースが機能しない理由を誰かが説明できますか?

ところで:バイナリによって与えられるエラーは次のとおりです。

20170116 / 125624.689-U3000011スクリプトファイル ''を読み取れませんでした、エラーコード '14'。

しかし、私の主な質問は、その3つのオプションを持つプログラムどのような違いがあるのということです。

ここではいくつかの詳細については、以下のとおりです。私は再びそれを試してみましたstraceの 多少の誤差は、実際にあったESPIPE(不正がシーク)からのlseek 続く(不良アドレス)EFAULTから読み込む右のエラーメッセージの前に。

私は(一時ファイルを使用せずに)Rubyスクリプトでコントロールしようとしたバイナリはの一部であるcallapiからAutomic(UC4)


25
クール、バイナリにUUOC検出器が組み込まれています。私はそれがほしい。
xhienne

4
OSは何ですか(したがって、errnoを意味する場合は14がわかります)。
ステファンシャゼラス

6
プログラムがこのように反応する可能性はありますが、それはそうでしたが、それは危険なバギーなものでした。stdinがttyの場合、stdinからの入力を予期するすべてのクレイジーでないプログラムは動作する必要があり、ttyとファイルの両方で動作できる場合、パイプもサポートしない理由はほとんどありません。おそらく、プログラムの作者は、一時的な出血があって、その何もかかわらisatty()についてfalseを返すが、シーク可能かmmappableファイルになります...
ヘニングMakholmさん

9
エラーコード14はEFAULTを表します。宣言したバッファが無効な場合に発生する読み取り。私はプログラムを追跡しますが、データを読み取るためのバッファサイズを取得するためにファイルの最後を探していると思われます、シークが機能しないという事実をうまく処理して、負のサイズを割り当てようとします(悪いmallocを処理しません) 。バッファを渡して、バッファに与えられた障害が無効であることを読み取ります。
マシューイフェ

3
@xhienneいいえ、それはそれをcat防止します。2つのファイルを結合するために使用することはできませんでした。
jpmc26

回答:


150

./binary < file

binaryの標準入力は、読み取り専用モードで開いているファイルです。bashファイルをまったく読み込まず、実行するプロセスのファイル記述子0(stdin)で読み込むためにファイルを開くことに注意してくださいbinary

に:

./binary << EOF
test
EOF

シェルに応じて、binaryさんのSTDINが含まれている削除された一時ファイル(AT&T kshの、zshのは、bash ...)のいずれかになりますtest\nシェルまたはパイプの読取終了(によってそこに置かとしてdashyash;およびシェルが書き込みをtest\n並列にパイプのもう一方の端で)。あなたの場合、を使用している場合はbash、一時ファイルになります。

に:

cat file | ./binary

シェルに応じて、binaryのstdinはパイプの読み取り端、または書き込み方向がシャットダウンされ(ksh93)、もう一方の端のcatコンテンツを書き込むソケットペアfileの一方の端になります。

stdinが通常ファイル(一時ファイルかどうか)である場合、それはシーク可能です。binary先頭または末尾に移動したり、巻き戻しなどを行うこともできます。また、mmapしたり、ioctl()sFIEMAP / FIBMAPのような操作を行ったりすることもできます(の<>代わりに使用すると<、穴を切り捨てたり、パンチしたりすることができます)。

一方、パイプとソケットのペアはプロセス間通信手段であり、データbinary以外にできることはあまりありませんread(ただしioctl()、通常のファイルではなく、パイプ固有の操作がいくつかあります) 。

時間のほとんどは、それがために不足している能力だseek(のようなアプリケーションは、パイプを扱うときに文句/失敗するが、それは通常のファイルではなく、ファイルの種類に有効な他のシステムコールのいずれかの可能性がありmmap()ftruncate()fallocate()) 。Linuxでは、/dev/stdinfd 0がパイプ上または通常のファイル上にあるときに開くときの動作にも大きな違いがあります。

シーク可能なファイルのみを処理できるコマンドは数多くありますが、その場合、通常は標準入力で開かれているファイルには対応していません。

$ unzip -l file.zip
Archive:  file.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       11  2016-12-21 14:43   file
---------                     -------
       11                     1 file
$ unzip -l <(cat file.zip)
     # more or less the same as cat file.zip | unzip -l /dev/stdin
Archive:  /proc/self/fd/11
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of /proc/self/fd/11 or
        /proc/self/fd/11.zip, and cannot find /proc/self/fd/11.ZIP, period.

unzipファイルの末尾に格納されているインデックスを読み取り、ファイル内でシークしてアーカイブメンバーを読み取る必要があります。しかし、ここで、ファイル(最初のケースでは、通常は、第二パイプは)へのパス引数として指定されているunzip、とunzip代わりにすでに親が開かれたFDを継承の(通常は0以外のFDに)それ自体を開きます。標準入力からzipファイルを読み取りません。stdinは、主にユーザーとの対話に使用されます。

binaryターミナルエミュレーターで実行されている対話型シェルのプロンプトでリダイレクトせずに実行すると、シェルbinaryのstdinが親シェルから継承され、シェル自体は親からターミナルエミュレーターを継承し、読み取り+書き込みモードで開いているptyデバイス(のようなもの/dev/pts/n)。

これらのデバイスもシークできません。したがって、binary端末から入力を取得するときに問題なく動作する場合、問題はシークに関するものではない可能性があります。

その14がerrno(システムコールの失敗によって設定されるエラーコード)である場合、ほとんどのシステムではEFAULTBad address)になります。read()書き込み可能でないメモリアドレスに読み込むように求めた場合、システムコールは、そのエラーで失敗します。これは、ポイントからデータを読み取るfdがパイプまたは通常のファイルのどちらであるかには関係なく、一般にバグを示します1

binaryおそらくその標準入力で開いているファイルのタイプを(withでfstat())判断し、通常のファイルでもttyデバイスでもない場合にバグに遭遇します。

アプリケーションの詳細を知らなくてもわかりにくい。以下の下でそれを実行するstrace(またはtruss/ tuscシステム上で同等のことは)私たちは、システムコールが、ここで失敗していることがあれば何であるか見るのを助けることができました。


1あなたの質問へのコメントでマシュー・イフェが想定しているシナリオは、ここでもっともらしい。彼を引用:

データを読み取るためのバッファサイズを取得するためにファイルの最後までシークし、シークが機能しないという事実を不適切に処理し、負のサイズを割り当てようとしています(不良なmallocを処理しない)。バッファを渡して、バッファに与えられた障害が無効であることを読み取ります。


14
非常に興味深い...これは、スタイルのリダイレクトされた標準入力./binary < fileがシーク可能であると聞いた最初の例です!
デビッドZ

2
@DavidZこれはopen編集されたファイルであり、編集されたファイルと同じように動作しますopen。たまたま親プロセスから継承されただけですが、それはそれほど珍しいことではありません。
ホッブズ

3
システムにstraceまたは同様のツールが含まれている場合、それを使用して、バイナリが失敗するシステム呼び出しを確認できます。
パブーク

2
「切り捨て、mmap、穴あけなども可能です。」- うーん、ダメ。ファイルは読み取り専用モードで開かれています。それを行うには、プログラムは書き込みモードで開く必要があります。しかし、書き込みモードで開くことはできません。直接実行するためのインターフェイスがなく、開いているファイルに対応する「the」ディレクトリエントリを見つけるためのインターフェイスもありません(そのようなデントリが2つある場合、またはゼロがある場合) 。ファイルを統計し、同じiノード番号を持つオブジェクトのファイルシステムをスキャンする必要があります。それは非常に遅いでしょう。
ケビン

1
@StéphaneChazelas:open("/proc/self/fd/0", O_RDWR)削除されたファイルでも動作します。愚かな私:P。 からリダイレクトされたstdinでa.outが実行される前にecho foo>foo; (sleep 0.5; ll -L /proc/self/fd/0; strace ./a.out; ll -L /proc/self/fd/0) < foo & sleep 0.1 && rm fooリンク解除foofooます。
ピーター・コーデス

46

以下は、入力にStéphaneChazelasの回答を使用lseek(2)した簡単なサンプルプログラムです。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int c;
    off_t off;
    off = lseek(0, 10, SEEK_SET);
    if (off == -1)
    {
        perror("Error");
        return -1;
    }
    c = getchar();
    printf("%c\n", c);
}

テスト:

$ make seek
cc     seek.c   -o seek
$ cat foo
abcdefghijklmnopqrstuwxyz
$ ./seek < foo
k
$ ./seek <<EOF
> abcdefghijklmnopqrstuvwxyz
> EOF
k
$ cat foo | ./seek
Error: Illegal seek

パイプはシーク可能ではなく、それはプログラムがパイプについて文句を言うかもしれない1つの場所です。


21

パイプとリダイレクトは、いわば別の動物です。here-docリダイレクト(<<)を使用するか、stdinをリダイレクトすると< 、テキストが空中に出てくることはありません-実際にはファイル記述子(または、必要に応じて一時ファイル)に入り、それがバイナリのstdinが指す場所になります。

具体的には、bash'sソースコードのredir.cファイル(バージョン4.3)からの抜粋です。

/* Create a temporary file holding the text of the here document pointed to
   by REDIRECTEE, and return a file descriptor open for reading to the temp
   file.  Return -1 on any error, and make sure errno is set appropriately. */
static int
here_document_to_fd (redirectee, ri)

したがって、リダイレクトは基本的にファイルとして扱うことができるため、バイナリはそれらをナビゲートしたりseek()、ファイルを簡単に移動してファイルの任意のバイトにジャンプしたりできます。

Pipesは、4096バイト以下の書き込みがアトミックであることが保証されている64 KiB(少なくともLinuxの場合)のバッファーであるため、シークできません。つまり、自由にナビゲートすることはできません。かつてtailPythonでコマンドを実装しました。リダイレクトされた場合、マイクロ秒で2900万行のテキストをcatシークできますが、パイプを介してedを実行した場合、実行できることは何もないため、すべてを順番に読み取る必要があります。

別の可能性は、バイナリがファイルを明確に開きたい場合があり、パイプからの入力を受け取りたくない場合です。通常fstat()、システムコールを介して行われ、入力がS_ISFIFOファイルの種類(パイプ/名前付きパイプを意味する)からのものかどうかを確認します。

あなたの特定のバイナリは、それが何であるかわからないため、おそらくシークを試みますが、パイプをシークできません。エラーコード14の正確な意味を調べるには、ドキュメントを参照することをお勧めします。

:ダッシュ(Debian Almquist Shell、/bin/shUbuntuのデフォルト)などの一部のシェルは、パイプを使用したhere-docリダイレクトを内部的に実装するため、シークできない場合があります。ポイントは同じままです-パイプはシーケンシャルであり、簡単にナビゲートすることができず、そうしようとするとエラーになります。


Stephaneの答えは、here-docsはパイプを使用して実装でき、一般的なシェルdashはそのように実装できると述べています。この回答では、bashで観察された動作について説明していますが、その動作は他のシェル全体では保証されていないようです。
ピーターコーデス

@PeterCordesそれは絶対にそうです、そして私はちょうどdash私のシステムでそれを確認しました。以前は気づいていませんでした。指摘していただきありがとうございます
セルギーKolodyazhnyy

別のコメント:fstat()パイプかどうかを確認するためにstdinで使用します。 statパス名を取ります。しかし、実際にlseekは、fdが既に開かれている後にシーク可能かどうかを判断する最も適切な方法は、おそらく試行することです。
ピーター・コーデス

5

主な違いはエラー処理です。

次の場合、エラーが報告されます

$ /bin/cat < z.txt
-bash: z.txt: No such file or directory
$ echo $?
1

次の場合、エラーは報告されません。

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0

bashでは、PIPESTATUSを引き続き使用できます。

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo ${PIPESTATUS[0]}
1

ただし、コマンドの実行直後にのみ使用可能です。

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
$ echo ${PIPESTATUS[0]}
0
# oops !

バイナリの代わりにシェル関数を使用する場合、別の違いがあります。ではbash、パイプラインの一部である関数はサブシェルで実行されlastpipeます(オプションが有効でbash非インタラクティブな場合、最後のパイプラインコンポーネントを除く)。したがって、変数の変更は親シェルに影響しません。

$ a=a
$ b=b
$ x(){ a=x;}
$ y(){ b=y;}

$ echo $a $b
a b

$ x | y
$ echo $a $b
a b

$ cat t.txt | y
$ echo $a $b
a b

$ x | cat
$ echo $a $b
a b

$ x < t.txt
$ y < t.txt
$ echo $a $b
x y

4
したがって、エラー処理>はシェルで行われますが、パイプではテキストを生成するコマンドで行われます。OK。しかし、この特定の質問では、OPは既存のファイルを使用しているため、これは問題ではなく、明らかに生成されるエラーはバイナリによるものです。
セルギーKolodyazhnyy

1
それはほとんどポイントの横にありますが、この答えは一般的なケースでこのQ&Aにいくらか関連があり、ほとんど正しいです。
ステファンシャゼラス

@Serg:シェルをコマンドラインとして使用する場合、これは重要ではありません。しかし、スクリプトでは、エラーの処理は非常に重要です。
-Vouze
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.