Penggunaan kucing yang tidak berguna?

101

Ini mungkin ada di banyak FAQ - daripada menggunakan:

cat file | command

(yang disebut penggunaan kucing yang tidak berguna), cara yang benar seharusnya adalah:

command < file

Cara ke-2, "benar" - OS tidak harus mengeluarkan proses tambahan.
Meski tahu itu, saya terus menggunakan kucing yang tidak berguna karena 2 alasan.

  1. lebih estetis - Saya suka saat data bergerak secara seragam hanya dari kiri ke kanan. Dan lebih mudah untuk mengganti catdengan sesuatu yang lain ( gzcat, echo, ...), menambahkan 2 file atau masukkan filter baru ( pv, mbuffer, grep...).

  2. Saya "merasa" mungkin lebih cepat dalam beberapa kasus. Lebih cepat karena ada 2 proses, pertama ( cat) membaca dan kedua melakukan apa saja. Dan mereka dapat berjalan secara paralel, yang berarti terkadang eksekusi lebih cepat.

Apakah logika saya benar (untuk alasan kedua)?

Leonid Volnitsky
sumber
22
catadalah pipa identitas . Itu hanya mengalirkan inputnya ke outputnya. Jika program kedua dalam rangkaian dapat mengambil masukannya dari argumen yang sama yang Anda berikan cat(atau dari masukan standar, jika Anda tidak memberikan argumen), maka catsama sekali tidak berguna dan hanya menghasilkan proses tambahan yang bercabang dan pipa tambahan menjadi dibuat.
Frédéric Hamidi
11
@ FrédéricHamidi jika kucing tidak memiliki argumen atau argumennya adalah -, itu adalah pipa identitas. Ketika itu memiliki lebih dari satu argumen nama file non-tanda hubung, itu menjadi sesuatu yang lebih dari sekadar pipa identitas, dan mulai melayani tujuan yang sebenarnya.
kojiro
3
Sayangnya, tautan yang sebelumnya populer ke partmaps.org sudah mati. Kontennya
tripleee
1
Lihat juga: unix.stackexchange.com/q/511827/20336
Mikko Rantalainen
2
Saya mengamati bahwa jika Anda ingin menampilkan aliran data ke kanan (alasan 1) Anda dapat melakukannya dengan meletakkan pengalihan file sebelum perintah, seperti dalam <file command1 | command2, meskipun akan ada ketidaksepakatan tentang estetika.
holdenweb

Jawaban:

81

Saya tidak mengetahui penghargaan tersebut sampai hari ini ketika beberapa pemula mencoba menyematkan UUOC pada saya untuk salah satu jawaban saya. Itu adalah cat file.txt | grep foo | cut ... | cut .... Saya memberinya sebagian dari pikiran saya, dan hanya setelah melakukannya mengunjungi tautan yang dia berikan kepada saya mengacu pada asal-usul penghargaan dan praktik melakukannya. Pencarian lebih lanjut membawa saya ke pertanyaan ini. Agak sayangnya, meski telah dipertimbangkan secara sadar, tidak ada jawaban yang sesuai dengan alasan saya.

Saya tidak bermaksud untuk bersikap defensif dalam menanggapi dia. Lagi pula, di tahun-tahun muda saya, saya akan menulis perintah grep foo file.txt | cut ... | cut ...karena setiap kali Anda melakukan single yang sering grepAnda pelajari penempatan argumen file dan sudah menjadi pengetahuan bahwa yang pertama adalah pola dan yang kemudian adalah nama file.

Itu adalah pilihan sadar untuk digunakan catketika saya menjawab pertanyaan, sebagian karena alasan "selera yang baik" (dalam kata-kata Linus Torvalds) tetapi terutama karena alasan fungsi yang memaksa.

Alasan terakhir lebih penting jadi saya akan memadamkannya dulu. Ketika saya menawarkan pipa sebagai solusi, saya berharap pipa itu dapat digunakan kembali. Kemungkinan besar pipa akan ditambahkan di ujung atau disambung ke pipa lain. Dalam hal ini memiliki argumen file untuk grep mengacaukan penggunaan kembali, dan sangat mungkin melakukannya secara diam - diam tanpa pesan kesalahan jika argumen file ada. I. e. grep foo xyz | grep bar xyz | wcakan memberi Anda berapa banyak baris yang xyzdimuat barsementara Anda mengharapkan jumlah baris yang berisi keduanya foodan bar. Harus mengubah argumen menjadi perintah dalam pipeline sebelum menggunakannya rentan terhadap kesalahan. Tambahkan kemungkinan kegagalan diam-diam dan ini menjadi praktik yang sangat berbahaya.

Alasan yang pertama juga tidak penting karena banyak " selera yang baik " hanyalah alasan bawah sadar intuitif untuk hal-hal seperti kegagalan diam-diam di atas yang tidak dapat Anda pikirkan tepat pada saat seseorang yang membutuhkan pendidikan berkata "tetapi tidak kucing itu tidak berguna ".

Namun, saya akan mencoba untuk juga menyadarkan alasan "rasa enak" yang saya sebutkan. Alasan itu berkaitan dengan semangat desain ortogonal Unix. greptidak cutdan lstidak grep. Oleh karena itu setidaknya grep foo file1 file2 file3bertentangan dengan semangat desain. Cara ortogonal untuk melakukannya adalah cat file1 file2 file3 | grep foo. Sekarang, grep foo file1hanya kasus khusus grep foo file1 file2 file3, dan jika Anda tidak memperlakukannya sama Anda setidaknya menggunakan siklus jam otak mencoba untuk menghindari penghargaan kucing tidak berguna.

Itu membawa kita ke argumen yang grep foo file1 file2 file3menggabungkan, dan catmenggabungkan sehingga itu tepat cat file1 file2 file3tetapi karena cattidak digabungkan cat file1 | grep foosehingga kita melanggar semangat catUnix yang maha kuasa. Nah, jika itu masalahnya maka Unix akan membutuhkan perintah yang berbeda untuk membaca output dari satu file dan memuntahkannya ke stdout (bukan mem-paginasi atau apa pun hanya meludah murni ke stdout). Jadi Anda akan memiliki situasi di mana Anda mengatakan cat file1 file2atau Anda mengatakan dog file1dan dengan cermat ingat untuk menghindari cat file1untuk menghindari mendapatkan penghargaan, sambil juga menghindari dog file1 file2karena semoga desain dogakan menimbulkan kesalahan jika beberapa file ditentukan.

Mudah-mudahan, pada titik ini, Anda bersimpati dengan desainer Unix karena tidak menyertakan perintah terpisah untuk memuntahkan file ke stdout, sementara juga memberi nama catuntuk penggabungan daripada memberinya nama lain. <edit>menghapus komentar yang salah pada <, pada kenyataannya, <adalah fasilitas no-copy yang efisien untuk meludahkan file ke stdout yang dapat Anda posisikan di awal pipeline sehingga desainer Unix memasukkan sesuatu yang khusus untuk ini</edit>

Pertanyaan selanjutnya adalah mengapa penting untuk memiliki perintah yang hanya memuntahkan file atau penggabungan beberapa file ke stdout, tanpa pemrosesan lebih lanjut? Salah satu alasannya adalah untuk menghindari setiap perintah Unix yang beroperasi pada input standar untuk mengetahui bagaimana mengurai setidaknya satu argumen file baris perintah dan menggunakannya sebagai input jika ada. Alasan kedua adalah untuk menghindari pengguna harus mengingat: (a) ke mana perginya argumen nama file; dan (b) menghindari bug pipa diam seperti yang disebutkan di atas.

Itu membawa kita pada mengapa grepmemiliki logika ekstra. Alasannya adalah untuk memungkinkan kelancaran pengguna untuk perintah yang sering digunakan dan berdiri sendiri (bukan sebagai pipeline). Ini adalah sedikit kompromi ortogonalitas untuk keuntungan yang signifikan dalam kegunaan. Tidak semua perintah harus dirancang dengan cara ini dan perintah yang tidak sering digunakan harus sepenuhnya menghindari logika ekstra dari argumen file (ingat logika tambahan menyebabkan kerapuhan yang tidak perlu (kemungkinan bug)). Pengecualiannya adalah mengizinkan argumen file seperti dalam kasus grep. (Ngomong-ngomong, perhatikan bahwa lsmemiliki alasan yang sama sekali berbeda untuk tidak hanya menerima tetapi cukup banyak membutuhkan argumen file)

Akhirnya, apa yang bisa dilakukan dengan lebih baik adalah jika perintah luar biasa seperti grep(tetapi tidak harus ls) menghasilkan kesalahan jika input standar juga tersedia ketika argumen file ditentukan.

ahli nujum
sumber
53
Perhatikan bahwa ketika grepdipanggil dengan beberapa nama file, itu mengawali baris yang ditemukan dengan nama file yang ditemukannya (kecuali Anda mematikan perilaku itu). Itu juga dapat melaporkan nomor baris di file individual. Jika hanya digunakan catuntuk memberi makan grep, Anda akan kehilangan nama file, dan nomor baris terus menerus di semua file, bukan per file. Jadi ada alasan untuk grepmenangani banyak file itu sendiri yang cattidak dapat ditangani. Kasus file tunggal dan file nol hanyalah kasus khusus dari penggunaan multi-file umum grep.
Jonathan Leffler
38
Sebagaimana dicatat dalam jawaban oleh kojiro , sangat mungkin dan legal untuk memulai pipeline < file command1 .... Meskipun posisi konvensional untuk operator pengalihan I / O adalah setelah nama perintah dan argumennya, itu hanya konvensi dan bukan penempatan wajib. Itu <harus mendahului nama file. Jadi, ada dekat dengan simetri sempurna antara >outputdan <inputpengalihan: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler
15
Saya pikir salah satu alasan mengapa orang melempar batu UUoC (termasuk saya) adalah untuk mendidik. Kadang-kadang orang melakukan proses gigabyte besar file tekstil dalam hal ini meminimalkan pipa (UUoC, menciutkan grep berurutan menjadi satu, aso) sangat penting dan seringkali dapat diasumsikan dengan aman berdasarkan pertanyaan bahwa OP benar-benar tidak tahu bahwa perubahan kecil mungkin terjadi dampak kinerja yang sangat besar. Saya sepenuhnya setuju dengan poin Anda tentang siklus otak dan itulah mengapa saya menemukan diri saya menggunakan kucing secara teratur bahkan ketika tidak diperlukan. Tetapi penting untuk diketahui bahwa itu tidak diperlukan.
Adrian Frühwirth
13
Tolong mengerti; Saya sama sekali tidak mengatakan itu cattidak berguna. Bukan itu tidak catberguna; itu adalah konstruksi tertentu tidak membutuhkan penggunaan cat. Jika Anda suka, perhatikan bahwa itu adalah UUoC (Useless Use of cat), dan bukan UoUC (Use of Useless cat). Ada banyak kesempatan ketika catalat yang tepat untuk digunakan; Saya tidak punya masalah dengan itu digunakan ketika itu adalah alat yang tepat untuk digunakan (dan, memang, sebutkan kasus dalam jawaban saya).
Jonathan Leffler
6
@randomstring Aku mendengarmu, tapi menurutku itu sangat tergantung pada kasus penggunaan. Ketika digunakan pada baris perintah, satu tambahan catdalam pipa mungkin bukan masalah besar tergantung pada datanya, tetapi ketika digunakan sebagai lingkungan pemrograman, sangat penting untuk mengimplementasikan kinerja hal-hal penting ini; terutama ketika berurusan dengan bashyang, dari segi kinerja, seperti roda berbentuk persegi panjang (dibandingkan dengan kshbagaimanapun juga. Saya berbicara hingga 10x lebih lambat di sini - tidak main-main). Anda benar- benar ingin mengoptimalkan fork Anda (dan bukan hanya itu) saat berhadapan dengan skrip yang lebih besar atau loop besar.
Adrian Frühwirth
58

Nggak!

Pertama-tama, tidak masalah di mana dalam perintah pengalihan terjadi. Jadi jika Anda suka pengalihan ke kiri perintah Anda, tidak apa-apa:

< somefile command

sama dengan

command < somefile

Kedua, ada n + 1 proses dan subkulit terjadi saat Anda menggunakan pipa. Ini jelas lebih lambat. Dalam beberapa kasus n akan menjadi nol (misalnya, saat Anda mengarahkan ke shell bawaan), jadi dengan menggunakan catAnda menambahkan proses baru sama sekali tidak perlu.

Sebagai generalisasi, kapan pun Anda menemukan diri Anda menggunakan pipa, perlu waktu 30 detik untuk melihat apakah Anda dapat menghilangkannya. (Tapi mungkin tidak perlu memakan waktu lebih dari 30 detik.) Berikut adalah beberapa contoh di mana pipa dan proses sering digunakan secara tidak perlu:

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)

Jangan ragu untuk mengedit untuk menambahkan lebih banyak contoh.

kojiro
sumber
2
Nah, peningkatan kecepatannya tidak akan banyak.
Dakkaron
9
menempatkan "<somefile" before "command" secara teknis memberi Anda kiri ke kanan, tetapi itu membuat pembacaan ambigu karena tidak ada demarkasi sintaksis: < cat grep dogadalah contoh yang dibuat-buat untuk menunjukkan bahwa Anda tidak dapat dengan mudah membedakan antara file input, perintah yang menerima masukan, dan argumen untuk perintah tersebut.
ahli nujum
2
Aturan praktis yang saya gunakan untuk memutuskan ke mana arah pengalihan STDIN adalah melakukan apa pun yang meminimalkan munculnya ambiguitas / potensi kejutan. Secara dogmatis mengatakan itu pergi sebelum memunculkan masalah necromancer, tetapi secara dogmatis mengatakan itu pergi dapat melakukan hal yang sama. Pertimbangkan: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). T. Apakah <quxberlaku untuk foo, atau untuk baz, yang -exec'd by foo? A. Ini berlaku untuk foo, tetapi bisa tampak ambigu. Menempatkan <qux sebelumnya foo dalam kasus ini lebih jelas, meskipun kurang umum, dan analog dengan trailing ENV=VAR quux.
Mark G.
3
@necromancer, <"cat" grep doglebih mudah dibaca, ya. (Saya biasanya pro-whitespace, tetapi kasus khusus ini sangat pengecualian).
Charles Duffy
1
@kojiro "Ini jelas lebih lambat." Anda tidak dapat menulisnya tanpa mendukungnya dengan angka. Nomor saya ada di sini: oletange.blogspot.com/2013/10/useless-use-of-cat.html (dan mereka menunjukkan itu hanya lebih lambat ketika Anda memiliki throughput tinggi) Di mana Anda?
Ole Tange
30

Saya tidak setuju dengan sebagian besar contoh Penghargaan UUOC yang terlalu sombong karena, ketika mengajar orang lain, catadalah tempat yang nyaman untuk setiap perintah atau alur perintah rumit yang menghasilkan keluaran yang sesuai untuk masalah atau tugas yang sedang dibahas.

Ini terutama berlaku di situs-situs seperti Stack Overflow, ServerFault, Unix & Linux atau situs SE lainnya.

Jika seseorang secara khusus bertanya tentang pengoptimalan, atau jika Anda ingin menambahkan informasi tambahan tentang itu, bagus, bicarakan tentang bagaimana menggunakan cat tidak efisien. Tetapi jangan mencaci orang karena mereka memilih untuk bertujuan untuk kesederhanaan dan kemudahan pemahaman dalam contoh mereka daripada melihat-saya-bagaimana-keren-saya-! kompleksitas.

Singkatnya, karena kucing tidak selalu kucing.

Juga karena kebanyakan orang yang senang berkeliling memberikan UUOC melakukannya karena mereka lebih mementingkan pamer tentang betapa 'pintar' mereka daripada membantu atau mengajar orang. Pada kenyataannya, mereka menunjukkan bahwa mereka mungkin hanyalah pemula lain yang telah menemukan tongkat kecil untuk mengalahkan rekan-rekan mereka.


Memperbarui

Ini UUOC lain yang saya posting sebagai jawaban di 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
}

Pedant UUOC akan mengatakan bahwa itu adalah UUOC karena sangat mungkin untuk membuat $filterdefault ke string kosong dan memiliki ifpernyataan do filter='| grep -v "^$"'tapi IMO, dengan tidak menyematkan karakter pipa di $filter, "tidak berguna" ini catmelayani tujuan yang sangat berguna untuk mendokumentasikan fakta secara mandiri bahwa $filterpada printfbaris itu bukan hanya argumen lain sqlplus, itu adalah filter keluaran opsional yang dapat dipilih pengguna.

Jika ada kebutuhan untuk memiliki beberapa keluaran filter opsional, pengolahan pilihan bisa hanya append | whateverke $filtersesering yang diperlukan - satu ekstra catdi dalam pipa tidak akan sakit apa pun atau menyebabkan kerugian terlihat dari kinerja.

cas
sumber
11
Sebagai tambahan - ==inside [ ]tidak ditentukan oleh POSIX, dan tidak semua implementasi menerimanya. Operator standar itu adil =.
Charles Duffy
27

Dengan versi UUoC, catharus membaca file ke memori, lalu menulisnya ke pipa, dan perintah harus membaca data dari pipa, jadi kernel harus menyalin seluruh file tiga kali sedangkan dalam kasus yang diarahkan ulang, kernel hanya perlu menyalin file satu kali. Lebih cepat melakukan sesuatu sekali daripada melakukannya tiga kali.

Menggunakan:

cat "$@" | command

adalah penggunaan yang sepenuhnya berbeda dan tidak selalu tidak berguna cat. Masih tidak berguna jika perintahnya adalah filter standar yang menerima nol atau lebih argumen nama file dan memprosesnya secara bergantian. Perhatikan trperintahnya: ini adalah filter murni yang mengabaikan atau menolak argumen nama file. Untuk memberi makan banyak file ke dalamnya, Anda harus menggunakan catseperti yang ditunjukkan. (Tentu saja, ada diskusi terpisah bahwa desainnya trtidak terlalu bagus; tidak ada alasan sebenarnya itu tidak dapat dirancang sebagai filter standar.) Ini mungkin juga valid jika Anda ingin perintah memperlakukan semua input sebagai a file tunggal daripada sebagai beberapa file terpisah, bahkan jika perintah akan menerima beberapa file terpisah: misalnya, wcadalah perintah seperti itu.

Ini adalah cat single-filekasus yang tidak berguna tanpa syarat.

Jonathan Leffler
sumber
26

Untuk membela kucing:

Iya,

   < input process > output 

atau

   process < input > output 

lebih efisien, tetapi banyak pemanggilan tidak memiliki masalah kinerja, jadi Anda tidak peduli.

alasan ergonomis:

Kami terbiasa membaca dari kiri ke kanan, jadi perintah seperti

    cat infile | process1 | process2 > outfile

sepele untuk dipahami.

    process1 < infile | process2 > outfile

harus melompati proses1, dan kemudian membaca dari kiri ke kanan. Ini dapat disembuhkan dengan:

    < infile process1 | process2 > outfile

terlihat entah bagaimana, seolah-olah ada anak panah yang menunjuk ke kiri, di mana tidak ada apa-apa. Lebih membingungkan dan tampak seperti kutipan mewah adalah:

    process1 > outfile < infile

dan membuat skrip sering kali merupakan proses yang berulang,

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

di mana Anda melihat kemajuan Anda secara bertahap, sementara

    < file 

bahkan tidak berhasil. Cara-cara sederhana lebih sedikit rawan kesalahan dan perintah katenasi ergonomis sederhana dengan kucing.

Topik lainnya adalah, bahwa kebanyakan orang terpapar> dan <sebagai operator pembanding, jauh sebelum menggunakan komputer dan saat menggunakan komputer sebagai pemrogram, jauh lebih sering terpapar pada hal ini.

Dan membandingkan dua operan dengan <dan> adalah kontra komutatif, yang artinya

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

Saya ingat pertama kali menggunakan <untuk pengalihan input, saya takut

a.sh < file 

bisa berarti sama dengan

file > a.sh

dan entah bagaimana menimpa skrip a.sh saya. Mungkin ini menjadi masalah bagi banyak pemula.

perbedaan langka

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

Yang terakhir dapat digunakan dalam perhitungan secara langsung.

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

Tentu saja <dapat digunakan di sini juga, sebagai ganti parameter file:

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

tapi siapa yang peduli - 15k?

Jika saya sesekali mengalami masalah, pasti saya akan mengubah kebiasaan saya memanggil kucing.

Saat menggunakan file yang sangat besar atau banyak, menghindari cat tidak masalah. Untuk sebagian besar pertanyaan, penggunaan kucing bersifat ortogonal, di luar topik, bukan masalah.

Memulai diskusi tentang penggunaan kucing yang tidak berguna dan tidak berguna pada setiap topik shell kedua hanya akan mengganggu dan membosankan. Dapatkan kehidupan dan tunggu menit ketenaran Anda, saat berhadapan dengan pertanyaan kinerja.

Pengguna tidak diketahui
sumber
5
+11111 .. Sebagai penulis jawaban yang diterima saat ini, saya sangat merekomendasikan pelengkap yang menyenangkan ini. Contoh spesifiknya menjelaskan argumen saya yang sering abstrak dan bertele-tele, dan tawa yang Anda dapatkan dari keraguan awal penulis file > a.shadalah sepadan dengan waktu membaca ini :) Terima kasih telah berbagi!
ahli nujum
Dalam pemanggilan ini cat file | wc -c, wcperlu membaca stdin hingga EOF, menghitung byte. Tetapi dalam hal ini, wc -c < filehanya statistik stdin, menemukan itu file biasa dan mencetak st_size daripada membaca masukan apa pun. Untuk file besar perbedaan performanya akan terlihat jelas.
oguz ismail
18

Masalah tambahan adalah bahwa pipa dapat menutupi subkulit secara diam-diam. Untuk contoh ini, saya akan mengganti catdengan echo, tetapi masalah yang sama ada.

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

echo "$x"

Anda mungkin berharap xmengandung foo, tetapi ternyata tidak. Yang xAnda setel berada di subkulit yang ditelurkan untuk menjalankan whileloop. xdi shell yang memulai pipeline memiliki nilai yang tidak terkait, atau tidak disetel sama sekali.

Di bash4, Anda bisa mengonfigurasi beberapa opsi shell sehingga perintah terakhir dari pipeline dijalankan di shell yang sama dengan yang memulai pipeline, tetapi Anda dapat mencobanya.

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

dan xsekali lagi lokal untuk whilesubkulit itu.

chepner
sumber
5
Di shell POSIX yang ketat, ini bisa menjadi masalah yang rumit karena Anda tidak memiliki string atau proses substitusi di sini untuk menghindari pipa. BashFAQ 24 memiliki beberapa solusi berguna bahkan dalam kasus itu.
kojiro
4
Di beberapa shell, pipa bergambar tidak membuat subkulit. Contohnya termasuk Korn dan Z. Mereka juga mendukung proses substitusi dan di sini string. Tentu saja mereka tidak sepenuhnya POSIX. Bash 4 harus shopt -s lastpipemenghindari pembuatan subkulit.
Dijeda sampai pemberitahuan lebih lanjut.
13

Sebagai seseorang yang secara teratur menunjukkan ini dan sejumlah antipattern pemrograman shell lainnya, saya merasa berkewajiban, terlambat, mempertimbangkan.

Skrip shell sangat mirip dengan bahasa salin / tempel. Bagi kebanyakan orang yang menulis skrip shell, mereka tidak di dalamnya untuk mempelajari bahasa; itu hanya kendala yang harus mereka atasi untuk terus melakukan hal-hal dalam bahasa yang sebenarnya mereka kenal.

Dalam konteks itu, saya melihatnya sebagai mengganggu dan bahkan berpotensi merusak untuk menyebarkan berbagai anti-pola shell scripting. Kode yang ditemukan seseorang di Stack Overflow idealnya dapat disalin / ditempelkan ke lingkungan mereka dengan sedikit perubahan, dan pemahaman yang tidak lengkap.

Di antara banyak sumber daya skrip shell di internet, Stack Overflow tidak biasa karena pengguna dapat membantu membentuk kualitas situs dengan mengedit pertanyaan dan jawaban di situs. Namun, pengeditan kode bisa menjadi masalah karena mudah untuk membuat perubahan yang tidak dimaksudkan oleh pembuat kode. Karenanya, kami cenderung meninggalkan komentar untuk menyarankan perubahan pada kode.

UUCA dan komentar antipattern terkait tidak hanya untuk penulis kode yang kami komentari; mereka sebagai peringatan untuk membantu pembaca situs menjadi sadar akan masalah dalam kode yang mereka temukan di sini.

Kami tidak dapat berharap untuk mencapai situasi di mana tidak ada jawaban di Stack Overflow yang merekomendasikan cats yang tidak berguna (atau variabel yang tidak dikutip, atau chmod 777, atau berbagai macam wabah antipattern lainnya), tetapi setidaknya kami dapat membantu mendidik pengguna yang akan menyalin / tempel kode ini ke loop ketat terdalam dari skrip mereka yang dieksekusi jutaan kali.

Sejauh alasan teknis, kearifan tradisional adalah bahwa kita harus mencoba meminimalkan jumlah proses eksternal; ini terus berlaku sebagai panduan umum yang baik saat menulis skrip shell.

tripleee
sumber
2
Juga untuk file besar, piping melalui catbanyak switch konteks tambahan dan bandwidth memori (dan polusi cache L3 dari salinan tambahan data dalam catbuffer baca, dan buffer pipa). Terutama pada mesin multi-core yang besar (seperti banyak pengaturan hosting) cache / bandwidth memori adalah sumber daya bersama.
Peter Cordes
1
@PeterCordes Silakan posting pengukuran Anda. Jadi kita bisa jika itu benar-benar penting dalam praktiknya. Pengalaman saya biasanya tidak masalah: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange
1
Blog Anda sendiri menunjukkan 50% perlambatan untuk throughput tinggi, dan Anda bahkan tidak melihat dampaknya pada total throughput (jika Anda memiliki hal-hal yang membuat core lain sibuk). Jika saya menyiasatinya, saya mungkin menjalankan pengujian Anda sementara x264 atau x265 sedang menyandikan video menggunakan semua inti, dan melihat seberapa besar itu memperlambat pengkodean video. bzip2dan gzipkompresi keduanya sangat lambat dibandingkan dengan jumlah overhead yang catditambahkan ke dalamnya saja (dengan mesin jika tidak menganggur). Sulit untuk membaca tabel Anda (garis membungkus di tengah angka?). syswaktu meningkat pesat, tetapi masih kecil vs. pengguna atau nyata?
Peter Cordes
8

Saya sering menggunakan cat file | myprogramcontoh. Kadang-kadang saya dituduh menggunakan kucing yang tidak berguna ( http://porkmail.org/era/unix/award.html ). Saya tidak setuju karena alasan berikut:

  • Mudah untuk memahami apa yang sedang terjadi.

    Saat membaca perintah UNIX Anda mengharapkan perintah yang diikuti oleh argumen yang diikuti dengan pengalihan. Anda dapat meletakkan pengalihan di mana saja tetapi jarang terlihat - sehingga orang akan lebih sulit membaca contoh. aku percaya

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

    lebih mudah dibaca daripada

    program1 -o option -b option < foo | program2

    Jika Anda memindahkan pengalihan ke awal, Anda membingungkan orang-orang yang tidak terbiasa dengan sintaks ini:

    < foo program1 -o option -b option | program2

    dan contoh harus mudah dipahami.

  • Mudah untuk berubah.

    Jika Anda mengetahui bahwa program dapat membaca cat, biasanya Anda dapat berasumsi bahwa program dapat membaca keluaran dari program apa pun yang menghasilkan STDOUT, dan dengan demikian Anda dapat menyesuaikannya untuk kebutuhan Anda sendiri dan mendapatkan hasil yang dapat diprediksi.

  • Ini menekankan bahwa program tidak gagal, jika STDIN bukan file.

    Tidaklah aman untuk mengasumsikan bahwa jika program1 < fooberhasil maka cat foo | program1akan juga berhasil. Namun, aman untuk mengasumsikan sebaliknya. Program ini berfungsi jika STDIN adalah file, tetapi gagal jika inputnya adalah pipa, karena menggunakan seek:

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

Biaya kinerja

Ada biaya untuk melakukan tambahan cat. Untuk memberikan gambaran tentang seberapa banyak saya menjalankan beberapa tes untuk mensimulasikan baseline ( cat), throughput rendah ( bzip2), throughput sedang ( gzip), dan throughput tinggi ( 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

Pengujian dijalankan pada sistem low end (0,6 GHz) dan laptop biasa (2,2 GHz). Tes tersebut dijalankan 10 kali pada setiap sistem dan waktu terbaik dipilih untuk meniru situasi optimal untuk setiap pengujian. $ ISO adalah ubuntu-11.04-desktop-i386.iso. (Tabel yang lebih cantik di sini: 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

Hasil penelitian menunjukkan bahwa untuk throughput rendah dan sedang biaya berada di urutan 1%. Ini masih dalam ketidakpastian pengukuran, jadi dalam praktiknya tidak ada perbedaan.

Untuk throughput yang tinggi, perbedaannya lebih besar dan ada perbedaan yang jelas antara keduanya.

Yang mengarah pada kesimpulan: Anda harus menggunakan <bukan cat |jika:

  • kompleksitas pemrosesannya mirip dengan grep sederhana
  • kinerja lebih penting daripada keterbacaan.

Jika tidak, tidak masalah apakah Anda menggunakan <atau cat |.

Dan dengan demikian Anda hanya harus memberikan UUoC-award jika dan hanya jika:

  • Anda dapat mengukur perbedaan yang signifikan dalam kinerja (publikasikan pengukuran Anda saat Anda memberikan penghargaan)
  • kinerja lebih penting daripada keterbacaan.
Ole Tange
sumber
-3

Menurut saya (cara tradisional) menggunakan pipa lebih cepat; di kotak saya, saya menggunakan straceperintah untuk melihat apa yang terjadi:

Tanpa pipa:

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

Dan dengan pipa:

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

Anda dapat melakukan beberapa pengujian dengan stracedan timeperintah dengan perintah yang lebih banyak dan lebih panjang untuk pembandingan yang baik.

TOC
sumber
9
Saya tidak mengerti apa yang Anda maksud dengan (cara tradisional) menggunakan pipa , atau mengapa menurut Anda ini stracemenunjukkan bahwa ini lebih cepat - stracetidak menelusuri wc -leksekusi dalam kasus kedua. Ini hanya melacak perintah pertama dari pipeline di sini.
kojiro
@ Kojiro: Maksud saya dengan cara tradisional = cara yang paling sering digunakan (saya pikir kami menggunakan pipa lebih dari tipuan), saya tidak dapat memastikan apakah itu lebih cepat atau tidak, dalam jejak saya saya melihat lebih banyak panggilan sistem untuk tipuan. Anda dapat menggunakan program ac dan loop untuk melihat dengan satu waktu pemakaian lebih banyak. Jika Anda tertarik, kami dapat meletakkannya di sini :)
TOC
3
Perbandingan apel dengan apel akan strace -f sh -c 'wc -l < wrong_output.c'disandingkan strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy
5
Berikut adalah hasil dari ideone.com, yang saat ini jelas mendukung tanpa cat: ideone.com/2w1W42#stderr
tripleee
1
@CharlesDuffy: mkfifomembuat pipa bernama . Pipa anonim disiapkan dengan pipe(2)dan kemudian bercabang, dan meminta induk dan anak menutup ujung pipa yang berbeda. Tapi ya, jawaban ini benar-benar tidak masuk akal, dan bahkan tidak mencoba menghitung panggilan sistem atau digunakan strace -Ountuk mengukur overhead, atau -rmemberi stempel waktu setiap panggilan relatif terhadap yang terakhir ...
Peter Cordes