cat x >> xがループするのはなぜですか?


17

次のbashコマンドは無限ループに入ります。

$ echo hi > x
$ cat x >> x

stdoutへの書き込みを開始catしたx後から読み続けていると推測できます。ただし、混乱を招くのは、私自身のcatのテスト実装が異なる動作を示すことです。

// mycat.c
#include <stdio.h>

int main(int argc, char **argv) {
  FILE *f = fopen(argv[1], "rb");
  char buf[4096];
  int num_read;
  while ((num_read = fread(buf, 1, 4096, f))) {
    fwrite(buf, 1, num_read, stdout);
    fflush(stdout);
  }

  return 0;
}

私が実行した場合:

$ make mycat
$ echo hi > x
$ ./mycat x >> x

ループしませ。の動作catと、stdout以前にフラッシュしているという事実がfread再び呼び出されることを考えると、このCコードはサイクルで読み取りと書き込みを続けると予想されます。

これら2つの動作はどのように一貫していますか?cat上記のコードがループしないのにループする理由を説明するメカニズムは何ですか?


ループします。strace / trussの下で実行してみましたか?どのシステムを使用していますか?
ステファンシャゼル14

BSD catにはこの動作があり、GNU catはこのようなことをしようとするとエラーを報告するようです。この答えは同じことを説明しています。GNUcatを持っているので、テストしたときにエラーが発生したので、BSD catを使用していると思います。
ラメシュ14

私はダーウィンを使用しています。cat x >> xエラーを引き起こすアイデアが好きです。ただし、このコマンドは、演習としてKernighanとPikeのUnix本で提案されています。
タイラー14

3
catほとんどの場合、stdioの代わりにシステムコールを使用します。stdioでは、プログラムがEOFnessをキャッシュしている可能性があります。4096バイトを超えるファイルで開始すると、無限ループが発生しますか?
マークプロトニック14

@MarkPlotnick、はい!Cコードは、ファイルが4kを超えるとループします。おかげで、たぶんそれがまさにその違いです。
タイラー14

回答:


12

私が持っている古いRHELシステムで/bin/catループしませcat x >> xcat「cat:x:入力ファイルは出力ファイルです」というエラーメッセージが表示されます。これを行うことで私はだますことができ/bin/catますcat < x >> x。上記のコードを試すと、説明した「ループ」が発生します。システムコールベースの「猫」も作成しました。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int
main(int ac, char **av)
{
        char buf[4906];
        int fd, cc;
        fd = open(av[1], O_RDONLY);
        while ((cc = read(fd, buf, sizeof(buf))) > 0)
                if (cc > 0) write(1, buf, cc);
        close(fd);
        return 0;
}

これもループします。ここでのバッファリングは(stdioベースの「mycat」とは異なり)カーネルで行われることだけです。

私は何が起こっていることは、そのファイルディスクリプタ3(の結果だと思うopen(av[1])0に提出記述子1(標準出力)のファイルにオフセットしている)、「>>」を行うために起動したシェルが発生するため、3のオフセットたlseek()上でcat子プロセスに渡す前にファイル記述子。

read()stdioバッファーまたはプレーンへの任意の種類のa を行うと、char buf[]ファイル記述子3 write()の位置が進みます。aを行うと、ファイル記述子1の位置が進みます。これら2つのオフセットは異なる数値です。「>>」のため、ファイル記述子1には常にファイル記述子3のオフセット以上のオフセットがあります。したがって、何らかの内部バッファリングを行わない限り、「猫のような」プログラムはループします。それは、多分、おそらく可能だというのstdioの実装FILE *(シンボルのタイプであるstdoutf独自のバッファが含まれるコード内で)。fread()実際にシステムコールread()を行って、内部バッファfoを埋めfます。これにより、の内部の何も変更されない場合がありstdoutます。呼び出しfwrite()stdout内の何も変更しない場合がありfます。そのため、stdioベースの「猫」はループしない可能性があります。またはそれかもしれません。多くの見苦しい、見苦しいlibcコードを読まずに言うのは難しい。

私はstraceRHELでやったcat-それは単にシステムコールの連続をread()行いwrite()ます。しかし、a catはこのように動作する必要はありません。mmap()入力ファイルが可能になりますwrite(1, mapped_address, input_file_size)。カーネルがすべての作業を行います。またはsendfile()、Linuxシステムで入力ファイル記述子と出力ファイル記述子の間でシステムコールを実行できます。古いSunOS 4.xシステムはメモリマッピングのトリックを行うと噂されていましたが、sendfileベースのcatを実行したことがあるかどうかはわかりません。いずれの場合においても、「ループ」の両方として、起こらないであろうwrite()sendfile()長さと転送パラメータを必要とします。


ありがとう。ダーウィンでは、freadMark Plotnickが示唆したように、コールがEOFフラグをキャッシュしたように見えます。証拠:[1]ダーウィン猫は、恐怖ではなく、読書を使用しています。[2] Darwinのfreadは__srefillを呼び出しfp->_flags |= __SEOF;、場合によっては設定します。[1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/...
タイラー

1
これはすごいです-昨日、私が最初に賛成票を投じました。それ可能性があることを言及する価値があることだけのためにPOSIXに定義されたスイッチがcatあるcat -u- uはのためにバッファリングされていません
mikeserv 14

実際には、>>と)(オープン呼び出すことで実装されるべきであるO_APPEND生じないフラグ、すべてのファイルの現在の末尾に(アトミック)書き込みへの書き込み動作をどんなファイルディスクリプタの位置が読み取りに先立っていました。foo >> logfile & bar >> logfileたとえば、この動作は正しく動作するために必要です。最後の書き込みが終了した後の位置がまだファイルの終わりであると想定する余裕はありません。
ヘニングマクホルム

1

最新のcat実装(sunos-4.0 1988)は、mmap()を使用してファイル全体をマップし、このスペースに対して1x write()を呼び出します。このような実装は、仮想メモリがファイル全体をマップできる限りループしません。

他の実装では、ファイルがI / Oバッファより大きいかどうかによって異なります。


多くのcat実装は、出力をバッファリングしません(-u暗黙的に)。それらは常にループします。
ステファンシャゼラス

Solaris 11(SunOS-5.11)は、小さなファイルにmmap()を使用していないようです(32769バイト以上のサイズのファイルにのみ使用するようです)。
ステファンシャゼラス

通常、正しい-uがデフォルトです。実装はファイルサイズ全体を読み取り、そのbufで1回だけ書き込みを行うことができるため、これはループを意味しません。
ずるい

Solaris catは、ファイルサイズが> max mapsizeである場合、または初期fileoffsetが!= 0の場合にのみループします
。– schily

Solaris 11で観察したこと。初期オフセットが!= 0の場合、またはファイルサイズが0〜32768の場合、read()ループを実行します。その上で、一度に8MiBのファイルの大きな領域をmmaps()し、 PiBファイル(スパースファイルでテスト済み)でもread()ループに戻るようです。
ステファンシャゼラス

0

Bash pitfallsに書かれているように、同じパイプラインでファイルから読み取り、書き込むことはできません。

パイプラインの動作に応じて、ファイルは上書きされる場合があります(0バイト、またはオペレーティングシステムのパイプラインバッファーのサイズに等しいバイト数まで)、または利用可能なディスク領域がいっぱいになるか、または到達するまで大きくなる場合がありますオペレーティングシステムのファイルサイズの制限、またはクォータなど。

解決策は、テキストエディターまたは一時変数を使用することです。


-1

両方の間に何らかの競合状態がある xます。cat(たとえばcoreutils 8.23)の一部の実装では、次のことが禁止されています。

$ cat x >> x
cat: x: input file is output file

これが検出されない場合、動作は明らかに実装(バッファサイズなど)に依存します。

コード内で、ファイルの終わりインジケータが設定されている場合に次がエラーを返す場合に備えて、のclearerr(f);後にを追加することができます。fflushfread


優れたOSは、同じ読み取り/書き込みコマンドを実行する単一のスレッドを持つ単一のプロセスに対して決定的な動作をするようです。いずれにせよ、行動は私にとって決定的であり、私は主に矛盾について尋ねています。
タイラー14

@Tyler IMHO、この場合の明確な仕様なしでは、上記のコマンドは意味がありません、そして決定論は本当に重要ではありません(ここのようなエラーは例外で、これは最良の動作です)。これはCのi = i++;未定義の動作に少し似ているため、矛盾があります。
vinc17 14

1
いいえ、競合状態はありません。動作は明確に定義されています。ただし、ファイルの相対的なサイズとで使用されるバッファに応じて、実装で定義されcatます。
ジル「SO-悪であるのをやめる」14

@Gilles動作が明確に定義されている/実装定義されているとどこでわかりますか?参考にできますか?POSIX cat仕様は、「-uオプションが指定されていない場合にcatユーティリティが出力をバッファリングするかどうかは実装定義です」と述べています。ただし、バッファを使用する場合、実装はその使用方法を定義する必要はありません。ランダムな時間にバッファがフラッシュされるなど、非決定的かもしれません。
vinc17 14

@ vinc17以前のコメントに「実際に」を挿入してください。はい、それは理論的に可能であり、POSIX準拠ですが、誰もそれを行いません。
ジル「SO-悪であるのをやめる」14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.