Kedinin gereksiz kullanımı?


101

Bu muhtemelen birçok SSS'de bulunmaktadır - kullanmak yerine:

cat file | command

(kedinin yararsız kullanımı olarak adlandırılır), doğru yol şöyle olmalıdır:

command < file

İkinci olarak, "doğru" şekilde - işletim sisteminin fazladan bir işlem üretmesi gerekmez.
Bunu bilmeme rağmen 2 nedenden dolayı işe yaramaz kediyi kullanmaya devam ettim.

  1. daha estetik - Verilerin yalnızca soldan sağa eşit olarak hareket etmesini seviyorum. Ve daha kolay yerine catbaşka bir şeyle ( gzcat, echo, ...), 2. dosya ekleyebilir veya yeni filtreyi takın ( pv, mbuffer, grep...).

  2. Bazı durumlarda daha hızlı olabileceğini "hissettim". Daha hızlı çünkü 2 işlem var, 1. ( cat) okuma yapıyor ve ikincisi her şeyi yapıyor. Ve paralel olarak çalışabilirler, bu da bazen daha hızlı uygulama anlamına gelir.

Mantığım doğru mu (2. nedenden dolayı)?


22
catbir kimlik borusudur . Yalnızca girdisini çıktıya aktarır. Zincirdeki ikinci program, girişini ilettiğiniz aynı argümandan cat(veya hiçbir argüman iletmezseniz standart girişten) alabiliyorsa , o catzaman kesinlikle işe yaramaz ve yalnızca ek bir işlemin çatallanması ve ek bir borunun oluşmasıyla sonuçlanır. oluşturuldu.
Frédéric Hamidi

11
@ FrédéricHamidi kedinin argümanı olmadığında veya argümanı olduğunda -, bu bir kimlik borusudur. Birden fazla tire olmayan dosya adı argümanı içerdiğinde, bir kimlik kanalından daha fazlası haline gelir ve gerçek bir amaca hizmet etmeye başlar.
kojiro

3
Partmaps.org için eskiden popüler olan bağlantı maalesef öldü. İçerik şimdi porkmail.org/era/unix/award.html adresinde
tripleee


2
Doğru veri akışını göstermek istiyorsanız (neden 1) bunu <file command1 | command2, estetik konusunda anlaşmazlık olsa da komuttan önce olduğu gibi dosya yeniden yönlendirmeyi koyarak yapabileceğinizi gözlemliyorum .
holdenweb

Yanıtlar:


82

Bazı çaylakların cevaplarımdan biri için UUOC'yi bana bağlamaya çalıştığı bugüne kadar ödülün farkında değildim . Bu bir cat file.txt | grep foo | cut ... | cut .... Ona aklımdan bir parça verdim ve ancak bunu yaptıktan sonra bana verdiği bağlantıyı ziyaret ederek ödülün kökenlerine ve bunu yapma pratiğine atıfta bulundu. Daha fazla araştırma beni bu soruya yönlendirdi. Bilinçli değerlendirmeye rağmen maalesef cevapların hiçbiri mantığımı içermiyordu.

Ona cevap verirken savunma yapmak istememiştim. Ne de olsa, daha genç yaşlarımda, bu komutu yazardım, grep foo file.txt | cut ... | cut ...çünkü ne zaman sık single greps yapsanız, dosya argümanının yerleşimini öğreniyorsunuz ve ilkinin kalıp, sonrakilerin ise dosya adları olduğunu bilmeye hazırsınız.

Bu catsoruyu cevaplarken kullanmak için bilinçli bir seçimdi , kısmen "iyi zevk" nedeniyle (Linus Torvalds'ın sözleriyle) ama esas olarak zorlayıcı bir işlev nedeni için.

İkinci neden daha önemli, bu yüzden önce onu ortaya koyacağım. Çözüm olarak bir boru hattı sunduğumda, yeniden kullanılabilir olmasını bekliyorum. Bir boru hattının başka bir boru hattının sonuna eklenmesi veya eklenmesi oldukça muhtemeldir. Bu durumda, yeniden kullanılabilirliği mahvetmek için bir dosya argümanına sahip olmak ve dosya argümanı varsa bunu bir hata mesajı olmadan büyük olasılıkla sessizce yapmak . I. e. hem ve hem de içeren satırların sayısını beklerken grep foo xyz | grep bar xyz | wckaç satır xyziçerdiğini size verecektir . Kullanmadan önce bir boru hattındaki bir komuta argümanları değiştirmek zorunda kalmak, hatalara meyillidir. Buna sessiz başarısızlık olasılığını da ekleyin ve özellikle sinsi bir uygulama haline gelir.barfoobar

İlk neden önemsiz değildir, çünkü çok sayıda " iyi tat " yalnızca, yukarıdaki sessiz başarısızlıklar gibi şeyler için sezgisel bir bilinçaltı mantığıdır, ancak eğitime ihtiyacı olan bir kişinin söylediği anda doğru düşünemeyebilirsiniz. " o kedi işe yaramaz ".

Ancak bahsettiğim eski "güzel tat" sebebini de bilinçlendirmeye çalışacağım. Bu sebep, Unix'in ortogonal tasarım ruhuyla ilgilidir. grepyapmaz cutve lsyapmaz grep. Bu nedenle en azından grep foo file1 file2 file3tasarım ruhuna aykırıdır. Bunu yapmanın ortogonal yolu cat file1 file2 file3 | grep foo. Şimdi, grep foo file1sadece özel bir durum grep foo file1 file2 file3ve eğer ona aynı şekilde davranmazsanız, en azından beyin saati döngülerini kullanarak işe yaramaz kedi ödülünden kaçınmaya çalışıyorsunuz.

Bu bizi bir araya gelen argümana götürür grep foo file1 file2 file3ve catbu şekilde uygun, cat file1 file2 file3ancak birbirini birleştirmediği için birleştiren argümana götürür cat, cat file1 | grep foobu nedenle hem catUnix'in hem de yüce Unix'in ruhunu ihlal ediyoruz . Pekala, eğer durum böyleyse, Unix'in bir dosyanın çıktısını okumak ve onu stdout'a tükürmek için farklı bir komuta ihtiyacı olurdu (onu sayfalandırmak ya da sadece stdout'a tükürmek). Öyleyse, söylediğiniz cat file1 file2veya söylediğiniz dog file1ve cat file1ödülü almaktan kaçınmayı vicdanen hatırladığınız bir duruma sahip olursunuz , aynı zamanda kaçınırsınız, dog file1 file2çünkü umarım dogbirden fazla dosya belirtilirse tasarımı bir hata verir.

Umarım bu noktada, Unix tasarımcılarına, bir dosyayı stdout'a tükürmek için ayrı bir komut dahil etmedikleri ve aynı zamanda catbaşka bir ad vermek yerine birleştirme için adlandırdıkları için sempati duyarsınız . <edit>üzerinde yanlış yorumlar kaldırıldı <, aslında, <bir ardışık düzeneğin başlangıcında konumlandırabileceğiniz bir dosyayı stdout'a tükürmek için verimli bir kopyalamayan tesis, böylece Unix tasarımcıları bunun için özel olarak bir şeyler eklemişlerdi.</edit>

Bir sonraki soru, daha fazla işlem yapmadan yalnızca bir dosyayı tüküren veya birkaç dosyanın stdout'a birleştirilmesini sağlayan komutlara sahip olmanın neden önemli olduğudur? Bunun bir nedeni, en az bir komut satırı dosyası argümanının nasıl ayrıştırılacağını bilmek ve varsa girdi olarak kullanmak için standart girdi üzerinde çalışan her bir Unix komutuna sahip olmaktan kaçınmaktır. İkinci neden, kullanıcıların şunları hatırlamak zorunda kalmamalarıdır: (a) dosya adı argümanlarının nereye gittiği; ve (b) yukarıda belirtildiği gibi sessiz boru hattı hatasından kaçının.

Bu da bizi neden grepekstra mantığa sahip olduğuna getiriyor . Mantık, sık kullanılan ve tek başına (bir ardışık düzen yerine) kullanılan komutlar için kullanıcı akıcılığına izin vermektir . Kullanılabilirlikte önemli bir kazanç sağlamak için hafif bir ortogonaliteden ödün vermektir. Tüm komutlar bu şekilde tasarlanmamalıdır ve sık kullanılmayan komutlar, dosya argümanlarının ekstra mantığından tamamen kaçınmalıdır (ekstra mantığın gereksiz kırılganlığa (hata olasılığı) yol açtığını unutmayın). Bunun istisnası, durumunda olduğu gibi dosya argümanlarına izin vermektir grep. (Bu arada, lssadece kabul etmekle kalmayıp aynı zamanda dosya argümanlarını da gerektirmesi için tamamen farklı bir nedeni olduğunu unutmayın )

Son olarak, dosya bağımsız değişkenleri belirtildiğinde standart girdi de mevcutsa , bu tür istisnai komutların grep(ancak zorunlu olmamak kaydıyla ls) bir hata oluşturması daha iyi olabilirdi .


53
Not zaman o grepçoklu dosya adları ile çağrılır, bunun bulundu dosyanın adıyla bulunan çizgiler önek (bu davranışı kapatmak sürece). Ayrı dosyalardaki satır numaralarını da bildirebilir. Yalnızca catbeslemek için kullanırsanız grep, dosya adlarını kaybedersiniz ve satır numaraları dosya başına değil tüm dosyalarda süreklidir. Bu nedenle, grepbirden fazla dosyanın kendi başına işlenemeyen nedenleri vardır cat. Tek dosya ve sıfır dosya durumları, genel çoklu dosya kullanımının özel durumlarıdır grep.
Jonathan Leffler

38
Belirtildiği üzere cevap tarafından Kojiro , boru hattının başlangıç mükemmel mümkün ve yasaldır < file command1 .... G / Ç yeniden yönlendirme işleçleri için geleneksel konum, komut adı ve argümanlarından sonra olsa da, bu yalnızca kuraldır ve zorunlu bir yerleşim değildir. <Dosya adından önce ilgisi var. Yani, mükemmel arasındaki simetri yakın bir var >outputve <inputyönlendirmeler: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler

15
İnsanların UUoC taşını atmasının bir nedeninin (ben de dahil) öncelikle eğitmek olduğunu düşünüyorum. Bazen insanlar gigabaytlık büyük tekstil dosyalarını işlerler, bu durumda boruları en aza indirmek (UUoC, sıralı grepleri bire indirgemek, aso) çok önemlidir ve çoğu zaman OP'nin gerçekten küçük ayarların olabileceğini bilmediği sorusuna dayanarak güvenli bir şekilde varsayılabilir. devasa performans etkileri. Beyin döngüleri hakkındaki düşüncenize tamamen katılıyorum ve bu yüzden ihtiyaç olmadığında bile kendimi düzenli olarak kediyi kullanırken buluyorum. Ancak buna ihtiyaç olmadığını bilmek önemlidir .
Adrian Frühwirth

13
Lütfen anlayın; Hiçbir şekilde bunun catfaydasız olduğunu söylemiyorum . Bu catişe yaramaz değil ; belirli bir yapının kullanımına ihtiyaç duymamasıdır cat. Notu isterseniz o olduğunu UUoC (Yararsız Kullanımı cat) ve değil UoUC (Yararsız Kullanımı cat). catDoğru aracın kullanılacağı birçok durum vardır ; Kullanılacak doğru araç olduğunda (ve gerçekten cevabımda bir durumdan bahsederken) kullanılmasıyla ilgili bir sorunum yok.
Jonathan Leffler

6
@randomstring Seni duyuyorum, ama gerçekten kullanım durumuna bağlı olduğunu düşünüyorum. Komut satırında kullanıldığında , verilere bağlı olarak catborudaki bir ek önemli bir sorun olmayabilir , ancak bir programlama ortamı olarak kullanıldığında, bu performans açısından kritik şeyleri uygulamak kesinlikle gerekli olabilir; özellikle bashperformans açısından dikdörtgen şekilli bir tekerlek gibi olanla uğraşırken ( kshyine de karşılaştırıldığında . Burada 10 kat daha yavaş konuşuyorum - şaka yapmıyorum). Sen do büyük komut dosyaları veya büyük döngüler ile uğraşırken senin çatal (ve sadece o değil) optimize etmek istiyorum.
Adrian Frühwirth

58

Hayır!

Her şeyden önce, yönlendirmenin bir komutta nerede olduğu önemli değildir. Dolayısıyla, komutunuzun sol tarafına yönlendirilmenizi seviyorsanız, sorun değil:

< somefile command

aynıdır

command < somefile

İkincisi, boruyu kullandığınızda n + 1 süreç ve bir alt kabuk oluşur. Kesinlikle daha yavaştır. Bazı durumlarda n sıfır olabilirdi (örneğin, bir kabuk yerleşikine yeniden yönlendirirken), bu nedenle kullanarak cattamamen gereksiz yere yeni bir işlem ekliyorsunuz.

Genelleme olarak, kendinizi bir boru kullanırken bulduğunuzda, onu ortadan kaldırıp kaldıramayacağınızı görmek için 30 saniye ayırmaya değer. (Ama muhtemelen 30 saniyeden fazla sürmeye değmez.) İşte boruların ve işlemlerin gereksiz yere sıklıkla kullanıldığı bazı örnekler:

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)

Daha fazla örnek eklemek için düzenlemekten çekinmeyin.


2
Eh, hız artışı fazla olmayacak.
Dakkaron

9
"<somefile" öğesini "komut" dan önce yerleştirmek teknik olarak size soldan sağa verir, ancak sözdizimsel sınırlama olmadığı için belirsiz okumaya neden olur: < cat grep doggirdi dosyası, komut arasında kolayca ayırt edemeyeceğinizi göstermek için uydurma bir örnektir girdiyi ve komutun argümanlarını alır.
necromancer

2
STDIN yönlendirmesinin nereye gideceğine karar vermek için benimsediğim temel kural, belirsizlik / sürpriz potansiyeli görünümünü en aza indiren her şeyi yapmaktır . Dogmatically daha önce gider ölüçağıranın gündeme getirir söyleyerek ancak dogmatik o sonra gider söyleyerek olabilir aynı şeyi yapın. Düşünün: . S. Hangisine göre 'd' için geçerli mi? A. için de geçerlidir , ancak olabilir belirsiz görünür. Bu durumda daha önce koymak , daha az yaygın olsa da daha açıktır ve takip edenle benzerdir . stdout=$(foo bar -exec baz <qux | ENV=VAR quux)<quxfoobaz-execfoofoo<qux fooENV=VAR quux
Mark G.

3
@necromancer, <"cat" grep dogorada okumak daha kolay. (Genellikle beyaz boşluk yanlısıyım, ancak bu özel durum büyük ölçüde bir istisnadır).
Charles Duffy

1
@kojiro "Kesinlikle daha yavaş." Bunu sayılarla yedeklemeden yazamazsınız. Numaralarım burada: oletange.blogspot.com/2013/10/useless-use-of-cat.html (ve yüksek çukur hızınız olduğunda sadece daha yavaş olduğunu gösterirler) Sizinki nerede?
Ole Tange

30

Aşırı derecede kendini beğenmiş UUOC Ödülünün çoğu örneğine katılmıyorum çünkü, başka birine öğretirken, cattartışılan probleme veya göreve uygun çıktı üreten herhangi bir komut veya huysuz karmaşık komutlar için uygun bir yer tutucudur.

Bu özellikle Stack Overflow, ServerFault, Unix & Linux veya herhangi bir SE sitesi gibi sitelerde geçerlidir.

Birisi özellikle optimizasyon hakkında soru sorarsa veya bununla ilgili ekstra bilgi eklemek isterseniz harika, kediyi kullanmanın ne kadar verimsiz olduğu hakkında konuşun. Ancak insanları azarlamayın çünkü örneklerinde bana-nasıl-havalı-mıyım-bakmak yerine basitliği ve anlaşılma kolaylığını hedeflemeyi seçtiler! karmaşıklık.

Kısacası, çünkü kedi her zaman kedi değildir.

Ayrıca, UUOC'leri ödüllendirmekten hoşlanan çoğu insan bunu, insanlara yardım etmek veya öğretmekten çok ne kadar 'zeki' olduklarını göstermekle ilgilendikleri için yapıyor. Gerçekte, muhtemelen akranlarını yenmek için küçük bir sopa bulan başka bir acemi olduklarını kanıtlıyorlar.


Güncelleme

İşte https://unix.stackexchange.com/a/301194/7696 adresindeki bir yanıtta yayınladığım başka bir 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
}

UUOC bilginleri bunun bir UUOC olduğunu söylerlerdi çünkü $filterboş dizeye varsayılan yapmak ve ifyapmak ifadesini yapmak kolayca mümkündür, filter='| grep -v "^$"'ancak IMO, boru karakterini içine yerleştirmeden $filter, bu "yararsız" catgerçeği kendi kendine belgelemek için son derece yararlı bir amaca hizmet eder. bu $filterilgili printfhat sadece bir bağımsız değişken değildir için sqlplus, bu isteğe bağlı kullanıcı tarafından seçilebilen çıkış filtresi.

Birden opsiyonel çıkış filtreleri olması gerek varsa, opsiyon işleme sadece ekleme olabilir | whateveriçin $filtersık gerektiği gibi - bir ekstra catboru hattında yaralı şey olacak ya da performans fark edilebilir kaybına neden değildir.


12
Bir kenara - ==içi [ ]POSIX tarafından belirtilmez ve tüm uygulamalar bunu kabul etmez. Standartlaştırılmış operatör adildir =.
Charles Duffy

27

UUoC sürümüyle, catdosyayı belleğe okumalı, ardından boruya yazmalı ve komut, borudan verileri okumalıdır, bu nedenle çekirdek, tüm dosyayı üç kez kopyalamak zorundadır, oysa yeniden yönlendirilmiş durumda, çekirdeğin dosyayı yalnızca bir kez kopyalaması gerekir. Bir şeyi bir kez yapmak, üç kez yapmaktan daha hızlıdır.

Kullanarak:

cat "$@" | command

tamamen farklı ve ille de gereksiz bir kullanım değildir cat. Komut, sıfır veya daha fazla dosya adı bağımsız değişkenini kabul eden ve bunları sırayla işleyen standart bir filtre ise, yine de yararsızdır. Şu trkomutu göz önünde bulundurun : bu, dosya adı argümanlarını yok sayan veya reddeden saf bir filtredir. Birden çok dosyayı ona beslemek için catgösterildiği gibi kullanmanız gerekir . (Elbette, tasarımının trçok iyi olmadığına dair ayrı bir tartışma var ; standart bir filtre olarak tasarlanamaması için gerçek bir neden yok.) Bu, komutun tüm girdileri bir komut birden çok ayrı dosyayı kabul etse bile, birden çok ayrı dosya yerine tek bir dosya: örneğin, wcböyle bir komuttur.

cat single-fileKayıtsız şartsız yararsız olan durum budur.


27

In savunma kedi:

Evet,

   < input process > output 

veya

   process < input > output 

daha verimlidir, ancak birçok çağrının performans sorunları olmadığı için umursamıyorsunuz.

ergonomik nedenler:

Soldan sağa okumaya alışkınız, bu nedenle şöyle bir komut

    cat infile | process1 | process2 > outfile

anlamak önemsizdir.

    process1 < infile | process2 > outfile

process1'in üzerinden atlamalı ve sonra soldan sağa okumalıdır. Bu, şu şekilde iyileştirilebilir:

    < infile process1 | process2 > outfile

sanki hiçbir şeyin olmadığı solu gösteren bir ok varmış gibi görünüyor. Daha kafa karıştırıcı ve süslü alıntılar gibi görünmek:

    process1 > outfile < infile

ve komut dosyaları oluşturmak genellikle yinelemeli bir süreçtir,

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

ilerlemenizi adım adım görürken

    < file 

çalışmıyor bile. Cat ile basit yollar daha az hataya meyillidir ve ergonomik komut kapsamı basittir.

Diğer bir konu ise, çoğu insanın bir bilgisayarı kullanmadan çok önce> ve <karşılaştırma operatörü olarak maruz kalması ve bir bilgisayarı programcı olarak kullanırken, bunlara çok daha fazla maruz kalmasıdır.

Ve iki operandın <ve> ile karşılaştırılması kontralandırmalı, yani

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

Giriş yönlendirmesi için <kullandığımı ilk kez hatırlıyorum,

a.sh < file 

aynı anlama gelebilir

file > a.sh

ve bir şekilde a.sh betiğimin üzerine yazıyorum. Belki bu yeni başlayanlar için bir sorundur.

nadir farklılıklar

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

İkincisi, doğrudan hesaplamalarda kullanılabilir.

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

Elbette bir dosya parametresi yerine <burada da kullanılabilir:

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

ama kimin umurunda - 15k?

Ara sıra sorunlarla karşılaşırsam, kesinlikle kedi çağırma alışkanlığımı değiştirirdim.

Çok büyük veya çok sayıda dosya kullanırken, kediden kaçınmak iyidir. Çoğu soruya göre kedi kullanımı diktir, konu dışıdır, bir sorun değildir.

Her iki kabuk konusunda bu gereksiz faydasız kullanımı tartışmaya başlamak sadece sinir bozucu ve sıkıcıdır. Performans sorularıyla uğraşırken bir can alın ve şöhret dakikalarınızı bekleyin.


6
+11111 .. Şu anda kabul edilen cevabın yazarı olarak bu güzel tamamlamayı şiddetle tavsiye ediyorum. Spesifik örnekler, çoğu zaman soyut ve sözlü argümanlarımı açıklığa kavuşturur ve yazarın erken korkusundan aldığınız gülüş, bunu file > a.shokurken zaman ayırmaya değer :) Paylaştığınız için teşekkürler!
necromancer

Bu çağrıda cat file | wc -c, wcbaytları sayarak EOF'ye kadar stdin okuması gerekir. Ama wc -c < filebunda, sadece stdin statüsünü verir, bunun normal bir dosya olduğunu bulur ve herhangi bir girişi okumak yerine st_size yazdırır. Büyük bir dosya için performanstaki fark açıkça görülebilir.
oguz ismail

18

Ek bir sorun, borunun bir alt kabuğu sessizce maskeleyebilmesidir. Bu örnek için, catile değiştireceğim echo, ancak aynı sorun var.

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

echo "$x"

Kontrol altına xalmayı bekleyebilirsin fooama öyle değil. xBir altkabuk yürütmek için kökenli içinde set oldu whiledöngü. xboru hattını başlatan kabukta alakasız bir değere sahip veya hiç ayarlanmamış.

Bash4'te, bazı kabuk seçeneklerini yapılandırabilirsiniz, böylece bir ardışık düzenin son komutu, ardışık düzeni başlatanla aynı kabukta yürütülür, ancak sonra bunu deneyebilirsiniz

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

ve xbir kez daha whilealt kabuğu için yereldir .


5
Kesin olarak POSIX kabuklarda bu zor bir problem olabilir çünkü burada borudan kaçınmak için dizileriniz veya işlem ikameleri yoktur. BashFAQ 24 , bu durumda bile bazı yararlı çözümlere sahiptir.
kojiro

4
Gelen bir kabukları, gösterilen boru altkabuk oluşturmaz. Örnekler arasında Korn ve Z bulunur. Ayrıca süreç ikamesini ve buradaki dizeleri de desteklerler. Tabii ki kesinlikle POSIX değiller . Bash 4, shopt -s lastpipealt kabuğu oluşturmaktan kaçınmalıdır.
sonraki duyuruya kadar duraklatıldı.

14

Bunu ve bir dizi başka kabuk programlama antipatternini düzenli olarak belirten biri olarak, gecikmiş bir şekilde tartıya girmek zorunda hissediyorum.

Kabuk betiği daha çok bir kopyala / yapıştır dilidir. Kabuk betikleri yazan çoğu insan için, bunlar dili öğrenmek için değildir; bu, işleri aslında biraz aşina oldukları dilde / dillerde yapmaya devam etmek için aşmaları gereken bir engeldir.

Bu bağlamda, çeşitli kabuk komut dosyası yazma karşıtı modellerini yaymayı yıkıcı ve hatta potansiyel olarak yıkıcı olarak görüyorum. Birinin Stack Overflow'da bulduğu kod, ideal olarak ortamlarına minimum değişikliklerle ve eksik anlayışla kopyalayıp / yapıştırabilir olmalıdır.

İnternetteki birçok kabuk komut dosyası oluşturma kaynağı arasında, Stack Overflow olağandışıdır, çünkü kullanıcılar sitedeki soruları ve yanıtları düzenleyerek sitenin kalitesini şekillendirmeye yardımcı olabilirler. Bununla birlikte, kod düzenlemeleri sorunlu olabilir çünkü kod yazarı tarafından amaçlanmayan değişiklikleri yapmak kolaydır. Bu nedenle, kodda değişiklik önermek için yorum bırakma eğilimindeyiz.

UUCA ve ilgili anti-model yorumları sadece yorum yaptığımız kodun yazarları için değildir; onlar , site okuyucularının burada buldukları koddaki sorunların farkına varmalarına yardımcı olmak için bir uyarıdır .

Stack Overflow'da hiçbir cevabın gereksiz cate-postaları (veya alıntılanmamış değişkenleri veya chmod 777veya çok çeşitli diğer antipattern salgınlarını) önermediği bir duruma ulaşmayı umamayız, ancak en azından kopyalamak üzere olan kullanıcıyı eğitmeye yardımcı olabiliriz. bu kodu, milyonlarca kez çalıştırılan betiğinin en dar döngüsüne yapıştırın.

Teknik nedenlere gelince, geleneksel kanı, dış süreçlerin sayısını en aza indirmeye çalışmamız gerektiğidir; bu, kabuk komut dosyaları yazarken iyi bir genel kılavuz olmaya devam etmektedir.


2
Ayrıca, büyük dosyalar için borulama cat, fazladan bağlam anahtarı ve bellek bant genişliği (ve catokuma arabelleğindeki ve boru arabelleklerindeki fazladan veri kopyalarından L3 önbelleğinin kirlenmesi ) anlamına gelir. Özellikle büyük bir çok çekirdekli makinede (birçok barındırma kurulumu gibi) önbellek / bellek bant genişliği paylaşılan bir kaynaktır.
Peter Cordes

1
@PeterCordes Lütfen ölçümlerinizi yazınız. Yani pratikte gerçekten önemliyse yapabiliriz. Deneyimlerime göre normalde önemi yok: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange

1
Kendi blogunuz, yüksek verim için% 50'lik bir yavaşlama gösteriyor ve toplam iş hacmi üzerindeki etkiye bakmıyorsunuz bile (diğer çekirdekleri meşgul eden bir şeyleriniz varsa). Bunu aşarsam, x264 veya x265 tüm çekirdekleri kullanarak bir videoyu kodlarken testlerinizi çalıştırabilir ve video kodlamasını ne kadar yavaşlattığını görebilirim. bzip2ve gzipsıkıştırma, catyalnızca buna eklenen ek yük miktarına kıyasla çok yavaştır (makine başka türlü boşta iken). Tablolarınızı okumak zor (bir sayının ortasında satır kaydırma?). syszaman çok artıyor ama yine de kullanıcıya göre küçük mü yoksa gerçek mi?
Peter Cordes

8

cat file | myprogramÖrneklerde sıklıkla kullanırım . Bazen gereksiz kedi kullanımıyla suçlanıyorum ( http://porkmail.org/era/unix/award.html ). Aşağıdaki nedenlerden dolayı katılmıyorum:

  • Neler olduğunu anlamak çok kolay.

    Bir UNIX komutunu okurken, bir komutun ardından argümanlar ve ardından yeniden yönlendirme beklersiniz. Yönlendirmeyi herhangi bir yere koymak mümkündür, ancak nadiren görülür - bu nedenle insanlar örneği okumakta daha zorlanacaktır. inanıyorum

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

    okumaktan daha kolay

    program1 -o option -b option < foo | program2

    Yönlendirmeyi başlangıca taşırsanız, bu sözdizimine alışkın olmayan insanların kafasını karıştırırsınız:

    < foo program1 -o option -b option | program2

    ve örneklerin anlaşılması kolay olmalıdır.

  • Değiştirmesi kolaydır.

    Programın okuyabildiğini catbiliyorsanız, normalde STDOUT'a çıktı veren herhangi bir programdan çıktıyı okuyabileceğini varsayabilir ve böylece kendi ihtiyaçlarınıza göre uyarlayabilir ve öngörülebilir sonuçlar elde edebilirsiniz.

  • STDIN bir dosya değilse programın başarısız olmadığını vurgulamaktadır.

    Çalışırsa program1 < fooo cat foo | program1zaman da işe yarayacağını varsaymak güvenli değildir . Ancak bunun tersini varsaymak güvenlidir. Bu program, STDIN bir dosya ise çalışır, ancak giriş bir boru ise başarısız olur, çünkü arama kullanır:

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

Performans maliyeti

Ek yapmanın bir maliyeti vardır cat. Baseline ( cat), düşük verim ( bzip2), orta verim ( gzip) ve yüksek verim ( grep) simülasyonu için birkaç testi ne kadar çalıştırdığıma dair bir fikir vermek için .

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

Testler düşük uçlu bir sistemde (0,6 GHz) ve sıradan bir dizüstü bilgisayarda (2,2 GHz) gerçekleştirildi. Her sistemde 10 kez çalıştırıldılar ve her test için en uygun durumu taklit etmek için en iyi zamanlama seçildi. $ ISO, ubuntu-11.04-desktop-i386.iso idi. (Daha güzel tablolar burada: 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

Sonuçlar, düşük ve orta çıktı için maliyetin% 1 seviyesinde olduğunu göstermektedir. Bu, ölçümlerin belirsizliği dahilindedir, bu nedenle pratikte hiçbir fark yoktur.

Yüksek verim için fark daha büyüktür ve ikisi arasında açık bir fark vardır.

Bu, şu sonuca götürür: <Aşağıdaki cat |durumlarda yerine kullanmalısınız :

  • işlemenin karmaşıklığı basit bir grep'e benzer
  • performans okunabilirlikten daha önemlidir.

Kullanmak ister Aksi fark etmez <ya cat |.

Ve bu nedenle, yalnızca ve ancak aşağıdaki durumlarda UUoC ödülü vermelisiniz:

  • Performanstaki önemli bir farkı ölçebilirsiniz (ödülü verirken ölçümlerinizi yayınlayın)
  • performans okunabilirlikten daha önemlidir.

-3

Boru kullanmanın (geleneksel yol) biraz daha hızlı olduğunu düşünüyorum; kutumda straceneler olduğunu görmek için komut kullandım :

Borusuz:

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

Ve boruyla:

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

Bazı ile testleri yapabilirsiniz straceve timeiyi kıyaslama için daha uzun komutlarla komutu.


9
Pipo kullanarak (geleneksel yolla) ne demek istediğinizi veya bunun neden stracedaha hızlı olduğunu gösterdiğini düşündüğünüzü anlamıyorum - bu ikinci durumda uygulamanın straceizini sürmek değil wc -l. Burada yalnızca boru hattının ilk komutunu izler.
kojiro

@kojiro: Geleneksel yolla demek istiyorum = en çok kullanılan yol (boruyu dolaylı yoldan daha fazla kullandığımızı düşünüyorum), bunun daha hızlı olup olmadığını teyit edemiyorum, izimde daha fazla yönlendirme için sistem çağrısı gördüm. Birinin daha fazla zaman tüketmesini görmek için ac programı ve bir döngü kullanabilirsiniz. Eğer ilgileniyorsanız buraya koyabiliriz :)
TOC

3
Elmalarla elma karşılaştırması strace -f sh -c 'wc -l < wrong_output.c'yan yana koyar strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy

5
Şu anda ideone.com'dan açıkça lehine olan sonuçlar cat: ideone.com/2w1W42#stderr
üçlü

1
@CharlesDuffy: adlandırılmış bir kanal mkfifooluşturur . İsimsiz bir boru kurulur ve ardından çatallanır ve ana ve alt borunun farklı uçlarını kapatır. Ama evet, bu cevap tamamen saçma ve sistem çağrılarını saymaya ya da genel giderleri ölçmek için ya da her çağrıyı bir pipe(2)strace -O-r
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.