129

これはおそらく多くの FAQ にある - 代わりに:

cat file | command

(これは猫の無用な使用と呼ばれます)、正しい方法は次のとおりです。

command < file

2 番目の「正しい」方法では、OS が余分なプロセスを生成する必要はありません。
それを知りながら、ダメ猫を使い続けた理由は2つ。

  1. より美的 - データが左から右に一様に移動する場合が好きです。catまた、別のもの ( gzcat、 、 ...)に置き換えechoたり、2 番目のファイルを追加したり、新しいフィルターを挿入したりする( pvmbuffer、 ...) 方が簡単grepです。

  2. 場合によってはもっと速いかもしれないと「感じた」。2 つのプロセスがあるため、1 つ目 ( cat) は読み取りを行い、2 つ目は何でも行います。また、それらは並行して実行できるため、実行が高速になる場合があります。

私の論理は正しいですか(2番目の理由で)?

4

9 に答える 9

101

新人が私の回答の 1 つとしてUUOCを私にピン留めしようとした今日まで、私はこの賞に気づいていませんでした。でしたcat file.txt | grep foo | cut ... | cut ...。私は彼に私の心の一部を与えました. そうして初めて、彼が私に与えたリンクにアクセスし、賞の起源とその実践について言及しました. さらに検索すると、この質問にたどり着きました。残念なことに、意識的に検討したにもかかわらず、どの回答にも私の理論的根拠が含まれていませんでした。

私は彼に弁護するつもりはありませんでした。結局のところ、私の若い頃grep foo file.txt | cut ... | cut ...は、頻繁に single s を実行するたびgrepに file 引数の配置を学習し、最初の引数がパターンで、後の引数がファイル名であることをすぐに知っているので、コマンドを次のように書いていたでしょう。

私が質問に答えたときにそれを使うのは意識的な選択でしたcat。部分的には「センスの良さ」(Linus Torvalds の言葉による) の理由によるものですが、主に機能上の説得力のある理由によるものです。

後者の理由の方が重要なので先に出します。ソリューションとしてパイプラインを提供する場合、パイプラインが再利用可能であることを期待します。パイプラインが別のパイプラインの最後に追加されるか、別のパイプラインに接合される可能性が非常に高くなります。その場合、grep に file 引数を指定すると再利用性が台無しになり、file 引数が存在する場合は、エラー メッセージを表示せずに黙って実行する可能性が非常に高くなります。私はe。grep foo xyz | grep bar xyz | wcと の両方を含む行数を期待しているときに、含まれる行数を示しxyzます。コマンドを使用する前に、引数をパイプライン内のコマンドに変更する必要があると、エラーが発生しやすくなります。それにサイレント失敗の可能性を加えると、特に陰湿な慣行になります。barfoobar

前者の理由も重要ではありません。なぜなら、多くの「センスの良さ」は、教育が必要な人が「そうではない」と言った瞬間に思いつかない、上記のサイレント失敗のようなものに対する直感的な潜在意識の論理的根拠に過ぎないからです。その猫は役に立たない」。

ただ、前者の「おいしさ」の理由も意識してみようと思います。その理由は、Unix の直交設計精神に関係しています。grepしませんcutlsませんgrep。したがって、少なくともgrep foo file1 file2 file3デザイン精神に反します。それを行う直交する方法は ですcat file1 file2 file3 | grep foogrep foo file1は単なる特別なケースでありgrep foo file1 file2 file3、同じように扱わないと、無駄な猫の報酬を回避しようとして、少なくとも脳のクロックサイクルを使い果たしています.

それgrep foo file1 file2 file3は連結であるという議論に私たちを導きます、そしてcat連結するのは適切ですcat file1 file2 file3が、なぜならそれはcat連結していないからです。その場合、Unix では、1 つのファイルの出力を読み取り、それを標準出力に出力する別のコマンドが必要になります (ファイルを改ページしたり、単に標準出力に出力したりするだけではありません)。したがって、複数のファイルを指定するとの設計でエラーが発生する可能性があるため、回避しながら、受賞を回避するために良心的に回避することを忘れないでください。cat file1 | grep foocatcat file1 file2dog file1cat file1dog file1 file2dog

願わくば、この時点で、ファイルを標準出力に吐き出すための別のコマンドを含めず、別の名前を付けるcatのではなく、連結のために名前を付けたことについて、Unix 設計者に同情してください。<edit>の誤ったコメントを削除しまし<た。実際に<は、パイプラインの先頭に配置できる標準出力にファイルを吐き出すための効率的なコピーなし機能であるため、Unix 設計者はこのために特別に何かを含めました。</edit>

次の質問は、それ以上の処理を行わずに、ファイルまたは複数のファイルを標準出力に出力するだけのコマンドを持つことがなぜ重要なのかということです。理由の 1 つは、標準入力で動作するすべての Unix コマンドが、少なくとも 1 つのコマンド ライン ファイル引数を解析し、存在する場合はそれを入力として使用する方法を認識しないようにするためです。2 番目の理由は、ユーザーが次のことを覚えなくて済むようにするためです。(a) ファイル名の引数の場所。(b) 上記のサイレント パイプライン バグを回避します。

grepこれにより、追加のロジックがある理由がわかりました。その理由は、(パイプラインとしてではなく)スタンドアロンベースで頻繁に使用されるコマンドをユーザーが流暢に操作できるようにすることです。これは、使いやすさを大幅に向上させるために、直交性をわずかに妥協したものです。すべてのコマンドをこのように設計する必要はなく、頻繁に使用されないコマンドは、ファイル引数の余分なロジックを完全に回避する必要があります (余分なロジックは不要な脆弱性 (バグの可能性) につながることに注意してください)。例外は、の場合のようにファイル引数を許可することですgrep。(ちなみに、lsファイル引数を受け入れるだけでなく、ほとんど必要とする理由はまったく異なることに注意してください)

最後に、ファイル引数が指定されたときに標準入力も使用できる場合、grep(必ずしもそうではありませんが)などの例外的なコマンドがエラーを生成する場合は、もっとうまくできたはずです。ls

于 2013-05-18T00:04:46.610 に答える
66

いいえ!

まず第一に、リダイレクトがコマンドのどこで発生するかは問題ではありません。したがって、コマンドの左側にリダイレクトしたい場合は、それで問題ありません。

< somefile command

と同じです

command < somefile

次に、パイプを使用するとn + 1 個のプロセスとサブシェルが発生します。それは最も明らかに遅いです。場合によってはnがゼロになることがあります (たとえば、シェルのビルトインにリダイレクトする場合) cat

一般論として、パイプを使用していることに気付いたときはいつでも、それを排除できるかどうかを確認するために 30 秒かかる価値があります。(しかし、おそらく 30 秒以上かかる価値はありません。) パイプとプロセスが不必要に頻繁に使用される例を次に示します。

for word in $(cat somefile); … # for word in $(<somefile); … (or better yet, while read < somefile)

grep something | awk stuff; # awk '/something/ stuff' (similar for sed)

echo something | command; # command <<< something (although echo would be necessary for pure POSIX)

自由に編集して例を追加してください。

于 2012-07-29T15:49:35.447 に答える
27

UUoC バージョンでcatは、ファイルをメモリに読み取ってからパイプに書き出す必要があり、コマンドはパイプからデータを読み取る必要があるため、カーネルはファイル全体を3回コピーする必要がありますが、リダイレクトされた場合は、カーネルはファイルを 1 回だけコピーする必要があります。何かを 3 回行うよりも 1 回行う方が速い。

使用:

cat "$@" | command

はまったく異なる の使用であり、必ずしも役に立たないというわけではありませんcat。コマンドが、0 個以上のファイル名引数を受け入れて順番に処理する標準フィルターである場合は、やはり役に立ちません。コマンドを考えてみましょうtr。これは、ファイル名の引数を無視または拒否する純粋なフィルターです。複数のファイルをフィードするにはcat、示されているように使用する必要があります。(もちろん、 の設計がtrあまり良くないという別の議論があります。標準のフィルタとして設計できなかった本当の理由はありません。)これは、コマンドですべての入力をフィルタとして処理する場合にも有効です。コマンドが複数の個別のファイルを受け入れる場合でも、複数の個別のファイルとしてではなく単一のファイル: たとえば、wcそのようなコマンドです。

無条件にcat single-fileだめなケースです。

于 2012-07-29T16:27:23.927 に答える
18

追加の問題は、パイプがサブシェルをサイレントにマスクできることです。この例では、に置き換えますcatecho、同じ問題が存在します。

echo "foo" | while read line; do
    x=$line
done

echo "$x"

が含まれていると思われるかもしれませんがx、含まれfooていません。設定したのは、ループxを実行するために生成されたサブシェルでした。パイプラインを開始したシェルでは、関係のない値が設定されているか、まったく設定されていません。whilex

bash4では、パイプラインの最後のコマンドがパイプラインを開始するコマンドと同じシェルで実行されるようにいくつかのシェルオプションを構成できますが、これを試すことができます

echo "foo" | while read line; do
    x=$line
done | awk '...'

そして、x再びwhile'sサブシェルに対してローカルです。

于 2012-07-29T15:56:27.157 に答える
-3

(従来の方法で)パイプを使用する方が少し速いと思います。私のボックスでは、straceコマンドを使用して何が起こっているかを確認しました。

パイプなし:

toc@UnixServer:~$ strace wc -l < wrong_output.c
execve("/usr/bin/wc", ["wc", "-l"], [/* 18 vars */]) = 0
brk(0)                                  = 0x8b50000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000
mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000
mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb779f000, 8192, PROT_READ)   = 0
mprotect(0x804f000, 4096, PROT_READ)    = 0
mprotect(0xb77ce000, 4096, PROT_READ)   = 0
munmap(0xb77a5000, 29107)               = 0
brk(0)                                  = 0x8b50000
brk(0x8b71000)                          = 0x8b71000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000
close(3)                                = 0
open("/usr/share/locale/locale.alias", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2570
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb77ac000, 4096)                = 0
open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0
mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000
close(3)                                = 0
open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0
mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000
close(3)                                = 0
read(0, "#include<stdio.h>\n\nint main(int "..., 16384) = 180
read(0, "", 16384)                      = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000
write(1, "13\n", 313
)                     = 3
close(0)                                = 0
close(1)                                = 0
munmap(0xb7260000, 4096)                = 0
close(2)                                = 0
exit_group(0)                           = ?

そしてパイプで:

toc@UnixServer:~$ strace cat wrong_output.c | wc -l
execve("/bin/cat", ["cat", "wrong_output.c"], [/* 18 vars */]) = 0
brk(0)                                  = 0xa017000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000
mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000
mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb773d000, 8192, PROT_READ)   = 0
mprotect(0x8051000, 4096, PROT_READ)    = 0
mprotect(0xb776c000, 4096, PROT_READ)   = 0
munmap(0xb7743000, 29107)               = 0
brk(0)                                  = 0xa017000
brk(0xa038000)                          = 0xa038000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000
close(3)                                = 0
fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0
read(3, "#include<stdio.h>\n\nint main(int "..., 32768) = 180
write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180
read(3, "", 32768)                      = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
13

より多くの長いコマンドを使用していくつかのテストを実行し、より良いベンチマークをstrace行うことができます。time

于 2012-07-29T18:38:15.257 に答える