web-dev-qa-db-ja.com

猫の無駄使い?

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

cat file | command

(これは猫の無駄な使用と呼ばれます)、正しい方法:

command < file

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

  1. より美的-データが左から右にのみ均一に移動するのが好きです。 catを他のもの(gzcatecho、...)に置き換えたり、2番目のファイルを追加したり、新しいフィルターを挿入したり(pvmbuffergrep ...)。

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

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

88

ある新人が私の答えの1つとして [〜#〜] uuoc [〜#〜] をピン留めしようとした今日まで、私はこの賞を知らなかった。 cat file.txt | grep foo | cut ... | cut ...でした。私は彼に私の心の一部を与えました、そして、そうすることをした後にだけ、彼は賞の起源とそうする習慣について言及してくれました。さらに検索すると、この質問につながりました。意識的に考慮しているにも関わらず、やや残念ながら、答えにはどれも私の理論的根拠が含まれていませんでした。

私は彼に応じるのを防御するつもりはなかった。結局、私の若い年には、コマンドをgrep foo file.txt | cut ... | cut ...として書いていたでしょう。なぜなら、頻繁に単一のgrepsを実行するたびに、ファイル引数の配置を学び、最初のものがパターンであり、後のものがファイル名であることを知っているからです。

私は質問に答えたときにcatを使用することを意識的に選択しました。これは、「Linus Torvaldsの言葉による」という理由もありますが、主に機能の理由からです。

後者の理由はより重要なので、最初に説明します。ソリューションとしてパイプラインを提供するとき、再利用できると期待しています。パイプラインは、最後に追加されるか、別のパイプラインにスプライスされる可能性が非常に高いです。その場合、grepにファイル引数を指定すると再利用性が損なわれ、ファイル引数が存在する場合はエラーメッセージなしでsilentlyを使用する可能性が高くなります。 I. e。 grep foo xyz | grep bar xyz | wcは、xyzbarの両方を含む行数を期待しているときに、foobarを含む行数を示します。使用する前にパイプラインのコマンドの引数を変更する必要があると、エラーが発生しやすくなります。それに静かな失敗の可能性を加えてください、そして、それは特に陰湿な習慣になります。

前者の理由も重要ではありません。多くの「 good taste 」は、上記のサイレント障害のようなものに対する直感的な潜在意識の根拠であり、教育を必要とする人の中には、「しかし、その猫は役に立たないわけではありません」と言っています。

しかし、私が言った前者の「良い味」の理由を意識することも試みます。その理由は、Unixの直交設計の精神に関係しています。 grepcutではなく、lsgrepではありません。したがって、少なくともgrep foo file1 file2 file3は設計の精神に反します。直交する方法はcat file1 file2 file3 | grep fooです。現在、grep foo file1grep foo file1 file2 file3の特殊なケースにすぎず、同じように扱わなければ、少なくとも無駄な猫賞を避けるために脳のクロックサイクルを使い果たしていることになります。

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

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

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

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

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

70
necromancer

いや!

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

< somefile command

と同じです

command < somefile

第二に、パイプを使用するときにn + 1プロセスとサブシェルが発生します。最も明らかに遅いです。場合によってはnがゼロになっていたため(たとえば、Shellビルトインにリダイレクトする場合)、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)

サンプルを追加するには、自由に編集してください。

55
kojiro

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

を使用して:

cat "$@" | command

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

cat single-file無条件に役に立たないケース。

27

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

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

誰かが最適化について具体的に尋ねる場合、またはそれについて追加の情報を追加したい場合は、素晴らしい、猫の使用が非効率的である方法について話してください。しかし、人々をrateるな。彼らは、見た目だけでなく、見た目よりも単純さと理解しやすさを目指しているからだ!複雑。

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

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


更新

https://unix.stackexchange.com/a/301194/7696 の回答に投稿した別のUUOCを次に示します。

_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
}
_

_$filter_をデフォルトで空の文字列にし、_filter='| grep -v "^$"'_にパイプ文字を埋め込まないことで、ifステートメントに_$filter_を実行させることが簡単にできるため、UUOCの指導者はUUOCであると言うでしょう。 、この「役に立たない」catは、printf行の_$filter_がsqlplusへの単なる別の引数ではなく、オプションのユーザー選択可能な出力フィルターであるという事実を自己文書化するという非常に便利な目的に役立ちます。

複数のオプションの出力フィルターが必要な場合、オプション処理は、必要に応じて_| whatever_を_$filter_に追加するだけです。パイプラインの余分なcatは、何も傷つけたり、目立ったりすることはありません。パフォーマンスの損失。

26
cas

defenseの猫:

はい、

   < 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で簡単です。

もう1つのトピックは、ほとんどの人が比較演算子として>および<にさらされており、コンピューターを使用するずっと前から、コンピューターをプログラマーとして使用しているときに、これらに頻繁にさらされることです。

また、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を使用しないでください。ほとんどの質問では、catの使用は直交していて、トピックから外れており、問題ではありません。

シェルの2番目のトピックごとに猫の議論のこれらの役に立たない役に立たない使用を開始することは、単に迷惑で退屈です。パフォーマンスに関する質問に対処するときは、人生を得て、一分間の名声を待ってください。

19
user unknown

追加の問題は、パイプがサブシェルを静かにマスクできることです。この例では、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 '...'

xwhileのサブシェルに対して再びローカルです。

17
chepner

定期的にこれと他の多くのシェルプログラミングアンチパターンを指摘している人として、遅かれ早かれ、重くのしかかる義務を感じます。

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

その文脈では、さまざまなシェルスクリプトアンチパターンを広めることは破壊的であり、潜在的に破壊的であると考えています。誰かがStack Overflowで見つけたコードは、理想的には、最小限の変更と不完全な理解で環境にコピー/ペーストできるはずです。

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

UUCAおよび関連するアンチパターンコメントは、コメントするコードの作成者だけのものではありません。それらは、サイトのreadersになるのを助けるcaveat emptorここで見つけたコードの問題を認識しています。

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

技術的な理由に関する限り、従来の知恵は、外部プロセスの数を最小限に抑えようとすることです。これは、シェルスクリプトを作成する際の適切な一般的なガイダンスとして引き続き保持されます。

13
tripleee

例では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賞を授与する必要があります。

  • パフォーマンスの大きな違いを測定できます(賞を与えるときに測定値を公開します)
  • パフォーマンスは読みやすさよりも重要です。
7
Ole Tange