猫の無駄使い?


101

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

cat file | command

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

command < file

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

  1. より美的-データが左から右にのみ均一に移動するのが好きです。そして、それは簡単に交換するためにcat何か他のものを(gzcatecho、...)、第二のファイルを追加したり、新しいフィルタを挿入します(pvmbuffergrep...)。

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

私のロジックは正しいですか(2番目の理由)?


22
catあるアイデンティティパイプが。入力のみを出力にストリーミングします。チェーンの2番目のプログラムは、あなたがに渡す同じ引数から入力を取ることができた場合はcat(あなたは何も引数を渡さない場合、または標準入力から)、その後、catある絶対に無駄にフォークされて、追加のプロセスにおける唯一の結果、追加のパイプがあること作成した。
フレデリックハミディ2012

11
@FrédéricHamidiは、猫に引数がない場合、または引数が-の場合、アイデンティティパイプです。ただし、ダッシュ以外のファイル名引数が複数ある場合は、IDパイプ以上のものになり、実際の目的を果たし始めます。
kojiro

3
以前は一般的だったpartmaps.orgへのリンクは、残念ながら死んでいます。コンテンツは現在porkmail.org/era/unix/award.htmlにあります
tripleee


2
右向きのデータフローを表示したい場合(理由1)<file command1 | command2、見た目については意見の相違がありますが、のように、コマンドの前にファイルリダイレクトを置くことでそれを行うことができます。
holdenweb

回答:


81

今日、新人が私の回答の1つとしてUUOCを私に固定しようとしたときまで、この賞に気づきませんでした。それはでしたcat file.txt | grep foo | cut ... | cut ...。私は彼に一片の心を与え、そうした後に初めて彼は私に賞の起源とそうすることの実践について言及してくれたリンクを訪れました。さらに検索したところ、この質問につながりました。意識的な配慮にもかかわらず、残念ながらやや残念ながら、私の回答には私の根拠は含まれていませんでした。

私は彼に応答する際に防御するつもりはありませんでした。結局のところ、私の若い頃grep foo file.txt | cut ... | cut ...は、頻繁にシングルgrepsを実行するたびにファイル引数の配置を学習し、最初のパターンがパターンであり、後のパターンがファイル名であることがすぐにわかるため、コマンドを記述していました。

使用することを意識した選択でした cat私が質問に答えたとき一部には「美味しさ」の理由(Linus Torvaldsの言葉で)が理由で、主に機能の説得力のある理由のためすることをました。

後者の理由の方が重要なので、最初に説明します。ソリューションとしてパイプラインを提供するとき、それは再利用可能であると期待しています。パイプラインが最後に追加されるか、別のパイプラインに接合される可能性は非常に高いです。その場合、grepにファイル引数を指定すると再利用性が台無しになり、ファイル引数が存在する場合はエラーメッセージなしで静かに再利用できます。I. e。grep foo xyz | grep bar xyz | wcは、との両方を含む行数を期待しているときに、何行をxyz含むかを示します。使用する前にパイプライン内のコマンドの引数を変更する必要があると、エラーが発生しやすくなります。それにサイレント障害の可能性を追加すると、それは特に油断のならない習慣になります。barfoobar

前者の理由も重要ではありません。なぜなら、多くの「良い味」は、教育の必要がある人が「しかし、そうではないその猫は役に立たない」。

ただし、先ほど述べた「いい味」の理由も意識してみます。その理由は、Unixの直交設計の精神に関係しています。grepしないしcutlsしないgrep。したがって、少なくともgrep foo file1 file2 file3デザインの精神に反します。それを行う直交する方法はcat file1 file2 file3 | grep fooです。現在、grep foo file1はの特別なケースにすぎgrep foo file1 file2 file3ません。同じように扱わないと、少なくとも、無駄な猫賞を回避しようとして脳のクロックサイクルを使い果たしていることになります。

それは私たちをgrep foo file1 file2 file3連結しているという議論につながり、cat連結しているので適切cat file1 file2 file3ですcatが、連結されていないcat file1 | grep fooので、catそしてすべてのUnixの精神に違反しています。そうだとすれば、Unixは1つのファイルの出力を読み取ってstdoutに吐き出すために別のコマンドを必要とするでしょう(ページネーションや単なる純粋な吐き出しではなく)。だから、あなたは言うcat file1 file2か、あなたが言ってdog file1、良心的にcat file1賞を得ることを避けるために避けることを覚えている一方でdog file1 file2、うまくいけば、dog複数のファイルが指定されている場合はの設計がエラーをスローするので避けてください。

うまくいけば、この時点で、ファイルをstdoutに吐き出す別のコマンドを含めず、cat他の名前を付けるのではなく連結の名前を付けることについて、Unixデザイナーに同情します。実際、の<edit>誤ったコメントが削除されました。これは、パイプラインの先頭に配置できるstdoutにファイルを吐き出すための効率的なコピー機能ではないため、Unixデザイナーはこのための特別なものを含めました<<</edit>

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

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

最後に、ファイルの引数が指定されているときに標準入力も使用できる場合、grep(必ずしもではないがls)などの例外的なコマンドがエラーを生成する場合は、より適切に実行できます。


52
grepが複数のファイル名で呼び出された場合は、見つかった行の前に、見つかったファイルの名前を付けます(その動作をオフにしない限り)。また、個々のファイルの行番号を報告することもできます。catフィードのみに使用するとgrep、ファイル名が失われ、行番号はファイルごとではなく、すべてのファイルにわたって連続します。したがって、grep処理catできない複数のファイル自体を処理する理由があります。シングルファイルとゼロファイルのケースは、の一般的なマルチファイル使用の単なる特殊ケースですgrep
ジョナサンレフラー2013年

38
で述べたように答えによって小次郎、とのパイプラインを開始するために完全に可能と合法です< file command1 ...。I / Oリダイレクト演算子の従来の位置はコマンド名とその引数の後にありますが、これは単なる規則であり、必須の配置ではありません。<ファイル名の前に持っています。したがって、>output<inputリダイレクトの間には、ほぼ完全な対称性があります<input command1 -opt 1 | command2 -o | command3 >output
ジョナサンレフラー2013年

15
UUoCストーン(私を含む)を投げる理由の1つは、主に教育することだと思います。時々人々はギガバイトの巨大なテキストファイルを処理します。その場合、パイプを最小化する(UUoC、シーケンシャルグレップを1つに、asoに折りたたむ)ことが重要であり、多くの場合、OPは小さな調整が実際には知らないという質問に基づいて安全に想定できます。パフォーマンスへの大きな影響。私はあなたの脳サイクルについての意見に完全に同意します。そのため、私は必要のないときでも猫を定期的に使用しています。しかし、それが必要でないことを知ることは重要です。
エイドリアンFrühwirth2013年

13
ご理解ください; それcatが役に立たないと言っても意味がありません。それはcat役に立たないということではありません。これは、特定の構成要素がを使用する必要がないことですcat。あなたは、それがあることに注意してください好きならUUoC(の無駄な使用cat)、およびない UoUC(無駄の使用をcat)。がcat適切なツールである場合が多くあります。使用するのに適切なツールである場合は、使用しても問題ありません(実際に、回答でケースに言及します)。
ジョナサンレフラー2013年

6
@randomstring聞いていますが、実際にはユースケースに依存すると思います。コマンドラインで使用する場合、データによってcatはパイプで1つ追加しても大したことないかもしれませんが、プログラミング環境として使用する場合は、これらのパフォーマンスが重要なものを実装する必要があります。特にbash、パフォーマンスの面で長方形のホイールのようなものを扱う場合(kshとにかく比較してください。ここでは最大10倍遅い-冗談ではありません)。あなたはない、より大きなスクリプトや巨大なループを扱うとき(だけでなく、その)あなたのフォークを最適化したいです。
AdrianFrühwirth2013年

58

いや!

まず、コマンドのどこでリダイレクトが発生するかは問題ではありません。したがって、コマンドの左側へのリダイレクトが必要な場合は、問題ありません。

< 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)

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


2
まあ、速度の増加はあまりありません。
ダッカロン

9
「<somefile」を「command」の前に置くと、技術的には左から右に移動しますが、構文上の境界がないため、あいまいな読み取りになり< cat grep dogます。入力とコマンドへの引数を受け取ります。
ネクロマンサー、2015年

2
STDINリダイレクトの方向を決定するために私が採用した経験則では、不明確さ/ 驚きの可能性の出現を最小限に抑えることは何でも行うことです。前に行くと断言するとネクロマンサーの問題が浮上しますが、後に行くと断言すると同じことができます。検討してください。Q. DOESはに適用する、またはにある、によってD」?A.に適用されますが、あいまいに見える場合があります。パッティングstdout=$(foo bar -exec baz <qux | ENV=VAR quux)<quxfoobaz-execfoofoo<qux fooこの場合、に方が一般的ではありませんが、より明確であり、末尾に似ていENV=VAR quuxます。
マーク

3
@necromancer、<"cat" grep dog読みやすいです。(私は通常プロ空白文字ですが、この特定のケースは非常に例外です)。
Charles Duffy

1
@kojiro「最も遅いです。」あなたはそれを数字でバックアップせずにそれを書くことはできません。私の番号はここにあります:oletange.blogspot.com/2013/10/useless-use-of-cat.html(そして、それは高いスループットがある場合にのみ遅くなることを示しています)あなたはどこにいますか?
Ole Tange

30

他の誰かに教えるとき、cat議論されている問題やタスクに適した出力を生成するコマンドまたは無愛想で複雑なコマンドのパイプラインの便利なプレースホルダーであるため、私は過度に独り占めのUUOCアワードのほとんどの例に同意しません。

これは、Stack Overflow、ServerFault、Unix&Linuxなどのサイト、または任意のSEサイトで特に当てはまります。

誰かが最適化について具体的に質問したり、最適化について追加情報を追加したい場合は、猫の使用がどのように非効率的であるかについて話してください。しかし、人々をあざけらないでください。なぜなら、彼らは、見た目はどうですか、クールな私ではなく、例で単純さと理解の容易さを目指すことを選んだからです!複雑。

要するに、猫はいつも猫ではないからです。

また、UUOCを授与することを楽しんでいるほとんどの人々は、人々を助けるまたは教えることよりも自分たちがどれほど「賢い」かを誇示することに関心があるので、それを行うので。実際には、彼らはおそらく、仲間を倒すための小さな棒を見つけた別の初心者であることを示しています。


更新

これは私が回答で投稿した別のUUOCです https://unix.stackexchange.com/a/301194/7696のとおりです。

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "$1" == "--delete-blank-lines" ] ; then
    filter='grep -v "^$"'
    shift
  fi

  # each arg is piped into sqlplus as a separate command
  printf "%s\n" "$@" | sqlplus -S sss/eee@sid | $filter
}

UUOC pedantsは、$filterデフォルトを空の文字列にしてifステートメントに次のようにさせることが簡単にできるので、それはUUOCであると言います。filter='| grep -v "^$"'が、IMO、中にパイプ文字を埋め込むないことで$filter、この「役に立たない」とはcat自己文書事実の極めて有用な目的を果たしますことは、$filterprintf行するだけで、別の引数ではありませんsqlplus、それはオプションのユーザ選択可能な出力フィルタです。

複数のオプションの出力フィルターが必要な場合は、オプションの処理を必要なだけ追加| whateverすることができます。パイプラインの$filter1つの追加catは、何も害したり、パフォーマンスを著しく低下させたりすることはありません。


11
余談ですが、==inside [ ]はPOSIXでは指定されておらず、すべての実装がそれを受け入れるわけではありません。標準化された演算子はただ=です。
Charles Duffy

26

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

使用:

cat "$@" | command

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

それはcat single-file無条件に無用である場合。


26

防衛猫の:

はい、

   < input process > output 

または

   process < input > output 

より効率的ですが、多くの呼び出しにはパフォーマンスの問題がないため、気にする必要はありません。

人間工学的な理由:

私たちは左から右に読むのに慣れているので、次のようなコマンド

    cat infile | process1 | process2 > outfile

理解するのは簡単です。

    process1 < infile | process2 > outfile

process1を飛び越えて、左から右に読む必要があります。これは次の方法で回復できます。

    < infile process1 | process2 > outfile

何となく、何もないところに左向きの矢印があるかのように見えます。より混乱し、派手な引用のように見えるのは:

    process1 > outfile < infile

多くの場合、スクリプトの生成は反復プロセスです。

    cat file 
    cat file | process1
    cat file | process1 | process2 
    cat file | process1 | process2 > outfile

あなたが進歩を段階的に見るところ、

    < file 

動作しません。簡単な方法はエラーが発生しにくく、人間工学に基づいたコマンドの連結はcatで簡単です。

別のトピックは、ほとんどの人が比較演算子として>と<にさらされていて、コンピューターを使用するずっと前に、そしてコンピューターをプログラマーとして使用しているときに、これらにさらされることが多いということです。

また、2つのオペランドを<と>で比較すると、可換ではなくなります。つまり、

(a > b) == (b < a)

入力リダイレクトに<を初めて使用したときのことを覚えていました。

a.sh < file 

と同じ意味になる可能性があります

file > a.sh

そして、どういうわけか私のa.shスクリプトを上書きします。多分これは多くの初心者にとって問題です。

まれな違い

wc -c journal.txt
15666 journal.txt
cat journal.txt | wc -c 
15666

後者は直接計算に使用できます。

factor $(cat journal.txt | wc -c)

もちろん、ここでもファイルパラメータの代わりに<を使用できます。

< journal.txt wc -c 
15666
wc -c < journal.txt
15666
    

しかし、誰が気にする-15k?

たまに問題にぶつかったら、きっと猫を呼び出す習慣を変えてしまうでしょう。

非常に大きい、または多くのファイルを使用する場合は、catを回避するのが適切です。ほとんどの質問に対して、猫の使用は直交しており、トピック外であり、問​​題ではありません。

1つおきのシェルトピックで猫の議論のこれらの役に立たない役に立たない使用を開始することは、迷惑で退屈なだけです。パフォーマンスの質問に取り組むときは、人生を手に入れて、あなたの名声を待ちましょう。


5
+11111 ..現在受け入れられている回答の作成者として、私はこの楽しい補足を強くお勧めします。特定の例は、私がしばしば抽象的で冗長な議論を解明し、著者の初期の恐怖からあなたが得る笑いfile > a.shは、これを読む時間の価値があるだけです:)共有してくれてありがとう!
ネクロマンサー2019

この呼び出しcat file | wc -cwcは、EOFまでバイト数をカウントしてstdinを読み取る必要があります。しかし、これではwc -c < file、それはstdinを統計し、それが通常のファイルであることを検出し、入力を読み取る代わりにst_sizeを出力します。大きなファイルの場合、パフォーマンスの違いがはっきりとわかります。
oguz ismail

18

追加の問題は、パイプが静かにサブシェルをマスクできることです。この例では、私は交換してくださいよcatecho、同じ問題が存在します。

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

echo "$x"

xを含むことを期待するかもしれfooませんが、そうではありません。xあなたのセットは、実行するために生み出されたサブシェルにあったwhileループを。xパイプラインを開始したシェルでは、無関係な値があるか、まったく設定されていません。

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

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

そしてx、再びローカルにあるwhileのサブシェル。


5
厳密にはPOSIXシェルでは、パイプを回避するための文字列やプロセスの置換がないため、これはトリッキーな問題になる可能性があります。その場合でも、BashFAQ 24にはいくつかの便利なソリューションがあります。
kojiro

4
では、いくつかのシェル、図示されたパイプは、サブシェルを作成しません。例には、KornとZが含まれます。これらは、プロセス置換とhereストリングもサポートします。もちろん、それらは厳密には POSIXではありません。Bash 4はshopt -s lastpipeサブシェルの作成を回避する必要があります。
追って通知があるまで一時停止。

13

これと他の多くのシェルプログラミングアンチパターンを定期的に指摘している人として、私は遅ればせながら、検討する義務があると感じています。

シェルスクリプトは非常にコピー/貼り付け言語です。シェルスクリプトを書くほとんどの人にとって、彼らは言語を学ぶのに参加していません。それは、彼らが実際にある程度慣れている言語で物事を続け続けるために彼らが克服しなければならない障害にすぎません。

その意味で、私はさまざまなシェルスクリプトのアンチパターンを広めることは破壊的であり、場合によっては破壊的でさえあると考えています。Stack Overflowで見つけたコードは、最小限の変更と不完全な理解で環境にコピー/貼り付けできることが理想的です。

ネット上の多くのシェルスクリプトリソースの中で、Stack Overflowは、ユーザーがサイトの質疑応答を編集することでサイトの品質を形作ることができるという点で珍しいものです。ただし、コードの編集には問題がある可能性がありますの作成者が意図していない変更を簡単にためます。したがって、コードの変更を提案するコメントを残す傾向があります。

UUCAおよび関連するアンチパターンコメントは、コメントするコードの作成者だけのものではありません。彼らは読者を助けるのと同じくらい注意の空虚な人です、サイトのがここで見つけたコードの問題に気付くのです。

Stack Overflowで回答がないと役に立たないcats(または引用符で囲まれていない変数、またはchmod 777その他のさまざまなアンチパターンペスト)がが、少なくともコピーしようとしているユーザーを教育することはできます/このコードを、何百万回も実行するスクリプトの最も内側のタイトなループに貼り付けます。

技術的な理由から言えば、外部プロセスの数を最小限に抑えるようにすべきであるという伝統的な知識があります。これは、シェルスクリプトを記述する際の優れた一般的なガイダンスとして引き続き保持されます。


1
また、大きなファイルの場合、パイプスルーcatは多くの余分なコンテキストスイッチとメモリ帯域幅(およびcatの読み取りバッファー内のデータの余分なコピーとパイプバッファーによるL3キャッシュの汚染)になります。特に大きなマルチコアマシン(多くのホスティングセットアップのような)では、キャッシュ/メモリ帯域幅は共有リソースです。
Peter Cordes

1
@PeterCordes測定値を投稿してください。ですから、実際にそれが本当に重要なのであれば、それは可能です。私の経験では、通常は問題ではありません。oletange.blogspot.com
Ole

1
あなた自身のブログは高スループットの50%の減速を示しており、(他のコアをビジー状態にしておくものがある場合)総スループットへの影響を見ていません。私がそれに近づいたら、x264またはx265がすべてのコアを使用してビデオをエンコードしている間にテストを実行し、ビデオエンコードの速度がどれだけ低下するかを確認します。 bzip2gzip圧縮の両方catは、それだけで追加されるオーバーヘッドの量(それ以外の場合はマシンがアイドル状態)に比べて非常に低速です。テーブルを読むのは難しいです(数字の途中で行を折り返しますか?)。sys時間は大幅に増加しますが、それでもユーザーまたは本物と比べて小さいですか?
Peter Cordes

8

cat file | myprogram例でよく使用します。猫の無用な使用で非難されていることがあります(http://porkmail.org/era/unix/award.html)。次の理由で同意しません。

  • 何が起こっているのかを理解するのは簡単です。

    UNIXコマンドを読み取るときは、コマンドの後に引数が続き、リダイレクトが続くことが期待されます。リダイレクションをどこにでも置くことは可能ですが、めったに見られないので、人々は例を読むのに苦労するでしょう。私は信じている

    cat foo | program1 -o option -b option | program2

    より読みやすいです

    program1 -o option -b option < foo | program2

    リダイレクトを最初に移動すると、この構文に慣れていない人を混乱させます。

    < foo program1 -o option -b option | program2

    そして、例は理解しやすいはずです。

  • 交換は簡単です。

    プログラムがから読み取ることができることがわかっている場合はcat、STDOUTに出力するすべてのプログラムからの出力を読み取ることができると通常想定できるため、独自のニーズに合わせてプログラムを適合させ、予測可能な結果を​​得ることができます。

  • STDINがファイルでない場合、プログラムは失敗しないことを強調します。

    うまくいけば、program1 < fooうまくcat foo | program1いくと仮定するのは安全ではありません。ただし、その逆を想定しても安全です。このプログラムは、STDINがファイルの場合は機能しますが、入力がパイプの場合は、シークを使用するため失敗します。

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    
    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

パフォーマンスコスト

追加を行うにはコストがかかりcatます。ベースライン(cat)、低スループット(bzip2)、中スループット(gzip)、高スループット(grep)をシミュレートするためにいくつかのテストをどれだけ実行したかを示すために。

cat $ISO | cat
< $ISO cat
cat $ISO | bzip2
< $ISO | bzip2
cat $ISO | gzip
< $ISO gzip
cat $ISO | grep no_such_string
< $ISO grep no_such_string

テストはローエンドシステム(0.6 GHz)と通常のラップトップ(2.2 GHz)で実行されました。各システムで10回実行され、各テストの最適な状況を模倣するために最適なタイミングが選択されました。$ ISOはubuntu-11.04-desktop-i386.isoでした。(ここのよりきれいなテーブル:http : //oletange.blogspot.com/2013/10/useless-use-of-cat.html

CPU                       0.6 GHz ARM
Command                   cat $ISO|                        <$ISO                            Diff                             Diff (pct)
Throughput \ Time (ms)    User       Sys        Real       User       Sys        Real       User       Sys        Real       User       Sys        Real
Baseline (cat)                     55      14453      33090         23       6937      33126         32       7516        -36        239        208         99
Low (bzip2)                   1945148      16094    1973754    1941727       5664    1959982       3420      10430      13772        100        284        100
Medium (gzip)                  413914      13383     431812     407016       5477     416760       6898       7906      15052        101        244        103
High (grep no_such_string)      80656      15133      99049      79180       4336      86885       1476      10797      12164        101        349        114

CPU                       Core i7 2.2 GHz
Command                   cat $ISO|           <$ISO             Diff          Diff (pct)
Throughput \ Time (ms)    User     Sys Real   User   Sys Real   User Sys Real User       Sys Real
Baseline (cat)                    0 356    215      1  84     88    0 272  127          0 423  244
Low (bzip2)                  136184 896 136765 136728 160 137131 -545 736 -366         99 560   99
Medium (gzip)                 26564 788  26791  26332 108  26492  232 680  298        100 729  101
High (grep no_such_string)      264 392    483    216  84    304   48 308  179        122 466  158

結果は、低および中スループットの場合、コストは1%のオーダーであることを示しています。これは測定の不確かさの範囲内なので、実際には違いはありません。

高スループットの場合、違いは大きく、2つの間に明確な違いがあります。

それは結論につながります:次<cat |場合の代わりに使用する必要があります:

  • 処理の複雑さは単純なgrepに似ています
  • パフォーマンスは読みやすさよりも重要です。

それ以外の場合は、<またはを使用するかどうかは関係ありませんcat |

したがって、次の場合にのみ、UUoC賞を授与する必要があります。

  • パフォーマンスの有意差を測定できます(賞を与えるときに測定値を公開します)
  • パフォーマンスは読みやすさよりも重要です。

-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良いベンチマークのためのより長いコマンドを使用して、コマンドを。


9
pipeを使用する(従来の方法)とはどういう意味か、またはこれstraceがより高速であると示す理由を理解できません。2番目のケースでは実行がstraceトレースされていませんwc -l。ここでは、パイプラインの最初のコマンドのみがトレースされます。
kojiro

@kojiro:従来の方法=最も使用される方法(つまり、インダイレクションよりもパイプを使用していると思います)で、高速であるかどうかを確認できません。トレースで、インダイレクションのシステムコールが多く見られました。acプログラムとループを使用して、より多くの時間を費やすことができます。ご興味のある方は、ここに記入してください:)
TOC

3
リンゴ・ツー・りんごの比較は入れてしまうでしょうstrace -f sh -c 'wc -l < wrong_output.c'一緒にstrace -f sh -c 'cat wrong_output.c | wc -l'
Charles Duffy

5
ideone.comからの結果は次のとおりです。これは現在明らかになしで支持されていcatます:ideone.com/2w1W42#stderr
tripleee

1
@CharlesDuffy:名前付きパイプをmkfifo作成します。で匿名パイプがセットアップされ、次に分岐して、親と子がパイプの異なる端を閉じるようにします。しかし、はい、この答えはまったくナンセンスであり、システムコールをカウントしたり、オーバーヘッドを測定したり、最後のコールと比較して各コールにタイムスタンプを付けたりすることさえしませんでした...pipe(2)strace -O-r
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.