Uso inutile del gatto?


101

Questo è probabilmente in molte domande frequenti, invece di utilizzare:

cat file | command

(che si chiama uso inutile del gatto), il modo corretto dovrebbe essere:

command < file

Nel secondo modo, "corretto", il sistema operativo non deve generare un processo aggiuntivo.
Nonostante lo sapessi, ho continuato a usare gatto inutile per 2 motivi.

  1. più estetico: mi piace quando i dati si spostano in modo uniforme solo da sinistra a destra. Ed è più facile da sostituire catcon qualcos'altro ( gzcat, echo, ...), aggiungere un secondo file o inserire un nuovo filtro ( pv, mbuffer, grep...).

  2. Ho "sentito" che potrebbe essere più veloce in alcuni casi. Più veloce perché ci sono 2 processi, 1st ( cat) fa la lettura e il secondo fa qualunque cosa. E possono funzionare in parallelo, il che significa un'esecuzione a volte più veloce.

La mia logica è corretta (per il secondo motivo)?


22
catè un tubo di identità . Trasmette solo il suo input al suo output. Se il secondo programma nella catena può prendere il suo input dallo stesso argomento a cui si passa cat(o dallo standard input, se non si passa alcun argomento), allora catè assolutamente inutile e si traduce solo in un processo aggiuntivo biforcuto e in una pipe aggiuntiva creato.
Frédéric Hamidi

11
@ FrédéricHamidi quando cat non ha argomenti o il suo argomento è -, è un'identità pipe. Quando ha più di un argomento di nome di file senza trattini, diventa qualcosa di più di una pipe di identità, tuttavia, e inizia a servire a uno scopo reale.
kojiro

3
Il collegamento precedentemente popolare a partmaps.org è purtroppo morto. Il contenuto è ora su porkmail.org/era/unix/award.html
tripleee


2
Osservo che se vuoi mostrare il flusso di dati verso destra (motivo 1) puoi farlo mettendo il reindirizzamento del file prima del comando, come in <file command1 | command2, anche se ci sarebbe disaccordo sull'estetica.
holdenweb

Risposte:


81

Non ero a conoscenza del premio fino ad oggi, quando qualche debuttante ha cercato di appuntarmi l' UUOC per una delle mie risposte. È stato un cat file.txt | grep foo | cut ... | cut .... Gli ho dato un pezzo della mia mente, e solo dopo averlo fatto ho visitato il link che mi ha dato riferendomi alle origini del premio e alla pratica di farlo. Ulteriori ricerche mi hanno portato a questa domanda. Un po 'sfortunatamente nonostante la considerazione cosciente, nessuna delle risposte includeva la mia logica.

Non avevo intenzione di mettermi sulla difensiva nel rispondere a lui. Dopotutto, nella mia giovinezza, avrei scritto il comando come grep foo file.txt | cut ... | cut ...perché ogni volta che fai i singoli frequenti grepimpari la posizione dell'argomento file ed è pronta conoscenza che il primo è il pattern e gli ultimi sono i nomi dei file.

È stata una scelta consapevole da utilizzare catquando ho risposto alla domanda, in parte per un motivo di "buon gusto" (nelle parole di Linus Torvalds) ma principalmente per un motivo convincente di funzione.

Quest'ultimo motivo è più importante, quindi lo pubblicherò per primo. Quando offro una pipeline come soluzione, mi aspetto che sia riutilizzabile. È molto probabile che una pipeline venga aggiunta alla fine o collegata a un'altra pipeline. In quel caso, avere un argomento file per grep rovina la riusabilità, e molto probabilmente farlo silenziosamente senza un messaggio di errore se l'argomento file esiste. I. e. grep foo xyz | grep bar xyz | wcti darà quante righe xyzcontengono barmentre ti aspetti il ​​numero di righe che contengono sia fooe bar. Dover modificare gli argomenti in un comando in una pipeline prima di utilizzarlo è soggetto a errori. Aggiungeteci la possibilità di silenziosi fallimenti e diventerà una pratica particolarmente insidiosa.

Anche il primo motivo non è irrilevante poiché molto " buon gusto " è semplicemente una logica subconscia intuitiva per cose come i fallimenti silenziosi di cui sopra a cui non si può pensare proprio nel momento in cui una persona bisognosa di istruzione dice "ma non lo è quel gatto inutile ".

Cercherò comunque di rendere consapevole anche l'ex motivo di "buon gusto" che ho citato. Questo motivo ha a che fare con lo spirito di progettazione ortogonale di Unix. grepnon lo fa cute lsnon lo fa grep. Quindi grep foo file1 file2 file3va almeno contro lo spirito del design. Il modo ortogonale di farlo è cat file1 file2 file3 | grep foo. Ora, grep foo file1è solo un caso speciale di grep foo file1 file2 file3, e se non lo tratti allo stesso modo stai almeno usando i cicli dell'orologio cerebrale cercando di evitare l'inutile premio del gatto.

Questo ci porta all'argomento che grep foo file1 file2 file3sta concatenando e catconcatenando così è proprio, cat file1 file2 file3ma poiché catnon sta concatenando in cat file1 | grep fooquindi stiamo violando lo spirito sia catdell'onnipotente Unix. Bene, se così fosse, Unix avrebbe bisogno di un comando diverso per leggere l'output di un file e sputarlo su stdout (non impaginarlo o altro solo un puro sputo su stdout). Quindi avresti la situazione in cui dici cat file1 file2o dici dog file1e ricorda coscienziosamente di evitare cat file1di evitare di ottenere il premio, evitando anche di evitare dog file1 file2poiché si spera che il design di doggenererebbe un errore se vengono specificati più file.

Si spera, a questo punto, che simpatizzi con i progettisti di Unix per non aver incluso un comando separato per sputare un file su stdout, mentre nomina anche catper concatenate piuttosto che dargli un altro nome. <edit>rimossi commenti errati su <, infatti, <è un'efficiente funzione no-copy per sputare un file su stdout che puoi posizionare all'inizio di una pipeline in modo che i progettisti di Unix abbiano incluso qualcosa di specifico per questo</edit>

La domanda successiva è: perché è importante avere comandi che si limitano a sputare un file o la concatenazione di più file su stdout, senza ulteriori elaborazioni? Uno dei motivi è evitare di avere ogni singolo comando Unix che opera sullo standard input per sapere come analizzare almeno un argomento del file della riga di comando e usarlo come input se esiste. La seconda ragione è per evitare che gli utenti debbano ricordare: (a) dove vanno gli argomenti del nome del file; e (b) evitare il bug della pipeline silenziosa come menzionato sopra.

Questo ci porta al motivo per cui grepha la logica in più. La logica è di consentire all'utente la fluidità dei comandi utilizzati frequentemente e su base autonoma (piuttosto che come pipeline). È un leggero compromesso di ortogonalità per un significativo guadagno di usabilità. Non tutti i comandi dovrebbero essere progettati in questo modo ei comandi che non sono usati frequentemente dovrebbero evitare completamente la logica extra degli argomenti del file (ricorda che la logica extra porta a fragilità inutili (la possibilità di un bug)). L'eccezione è consentire argomenti di file come nel caso di grep. (A proposito, nota che lsha una ragione completamente diversa non solo per accettare ma praticamente richiedere argomenti di file)

Infine, ciò che si sarebbe potuto fare meglio è se comandi eccezionali come grep(ma non necessariamente ls) generassero un errore se l'input standard è disponibile anche quando vengono specificati gli argomenti del file.


53
Notare che quando grepviene invocato con più nomi di file, aggiunge il prefisso alle righe trovate con il nome del file in cui è stato trovato (a meno che non si disattivi tale comportamento). Può anche riportare i numeri di riga nei singoli file. Se si utilizza solo catper alimentare grep, si perdono i nomi dei file ei numeri di riga sono continui su tutti i file, non per file. Quindi ci sono ragioni per grepgestire più file che catnon possono essere gestiti. I casi di file singolo e zero file sono semplicemente casi speciali dell'uso generale di più file di grep.
Jonathan Leffler

38
Come notato nella risposta di kojiro , è perfettamente possibile e legale avviare l'oleodotto < file command1 .... Sebbene la posizione convenzionale per gli operatori di reindirizzamento I / O sia dopo il nome del comando e i suoi argomenti, questa è solo la convenzione e non un posizionamento obbligatorio. Il <deve precedere il nome del file. Quindi, c'è una stretta di perfetta simmetria tra >outpute <inputreindirizzamenti: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler

15
Penso che uno dei motivi per cui le persone lanciano la pietra dell'UUoC (me compreso) sia principalmente quello di educare. A volte le persone elaborano gigabyte enormi file di testo, nel qual caso ridurre al minimo i tubi (UUoC, collassare greps sequenziali in uno, ecc.) È cruciale e spesso si può tranquillamente presumere in base alla domanda che l'OP non sa davvero che piccole modifiche potrebbero avere enormi impatti sulle prestazioni. Sono pienamente d'accordo con il tuo punto di vista sui cicli cerebrali ed è per questo che mi trovo a usare il gatto regolarmente anche quando non è necessario. Ma è importante sapere che non è necessario.
Adrian Frühwirth

13
Ti prego di capire; Non sto affatto dicendo che catsia inutile. Non è che catsia inutile; è che un particolare costrutto non necessita dell'uso di cat. Se lo desideri, nota che è UUoC (Useless Use of cat) e non UoUC (Use of Useless cat). Ci sono molte occasioni in cui catè lo strumento corretto da usare; Non ho alcun problema che venga utilizzato quando è lo strumento corretto da utilizzare (e, in effetti, menziono un caso nella mia risposta).
Jonathan Leffler

6
@randomstring ti sento, ma penso che dipenda davvero dal caso d'uso. Quando viene utilizzato sulla riga di comando, uno in più catnella pipe potrebbe non essere un grosso problema a seconda dei dati, ma se utilizzato come ambiente di programmazione può essere assolutamente necessario implementare queste cose critiche per le prestazioni; soprattutto quando si ha a bashche fare con la quale, dal punto di vista delle prestazioni, è come una ruota di forma rettangolare (rispetto a kshcomunque. Sto parlando fino a 10 volte più lentamente qui - non sto scherzando). È cosa vuole ottimizzare le forche (e non solo quello) quando si tratta di script più grandi o enormi loop.
Adrian Frühwirth

58

No!

Prima di tutto, non importa dove avviene il reindirizzamento in un comando. Quindi, se ti piace il tuo reindirizzamento a sinistra del tuo comando, va bene:

< somefile command

equivale a

command < somefile

Secondo, ci sono n + 1 processi e una subshell che si verifica quando si utilizza una pipe. È decisamente più lento. In alcuni casi n sarebbe stato zero (ad esempio, quando stai reindirizzando a un builtin di shell), quindi usando catstai aggiungendo un nuovo processo del tutto inutilmente.

In generale, ogni volta che ti ritrovi a usare un tubo vale la pena dedicare 30 secondi per vedere se puoi eliminarlo. (Ma probabilmente non vale la pena impiegare molto più di 30 secondi.) Di seguito sono riportati alcuni esempi in cui pipe e processi vengono spesso utilizzati inutilmente:

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)

Sentiti libero di modificare per aggiungere altri esempi.


2
Bene, l'aumento di velocità non sarà molto.
Dakkaron

9
posizionare "<unfile" prima del "comando" tecnicamente ti dà da sinistra a destra, ma rende la lettura ambigua perché non c'è demarcazione sintattica: < cat grep dogè un esempio artificioso per mostrare che non puoi facilmente distinguere tra il file di input, il comando che riceve l'input e gli argomenti del comando.
negromante

2
La regola pratica che ho adottato per decidere dove va il reindirizzamento STDIN è di fare tutto ciò che riduce al minimo l' aspetto di ambiguità / potenziale di sorpresa. Dire dogmaticamente che va prima solleva il problema del negromante, ma dire dogmaticamente che va dopo può fare la stessa cosa. Prendere in considerazione: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). D. Si <quxapplica a foo, o a baz, che è -exec'd da foo? R. Si applica a foo, ma può sembrare ambiguo. Mettere <qux prima foo in questo caso è più chiaro, anche se meno comune, ed è analogo al trailing ENV=VAR quux.
Mark G.

3
@necromancer, <"cat" grep dogè più facile da leggere, lì. (Di solito sono a favore degli spazi bianchi, ma questo caso particolare è davvero un'eccezione).
Charles Duffy

1
@kojiro "È decisamente più lento." Non puoi scriverlo senza supportarlo con i numeri. I miei numeri sono qui: oletange.blogspot.com/2013/10/useless-use-of-cat.html (e mostrano che è più lento solo quando hai un alto flusso) Dove sono i tuoi?
Ole Tange

30

Non sono d'accordo con la maggior parte dei casi del premio UUOC eccessivamente compiaciuto perché, quando insegno a qualcun altro, catè un comodo segnaposto per qualsiasi comando o catena complicata di comandi che producono un output adatto al problema o all'attività in discussione.

Ciò è particolarmente vero su siti come Stack Overflow, ServerFault, Unix e Linux o uno qualsiasi dei siti SE.

Se qualcuno chiede specificamente dell'ottimizzazione, o se hai voglia di aggiungere ulteriori informazioni al riguardo, allora, bene, parla di come l'uso di cat sia inefficiente. Ma non rimproverare le persone perché hanno scelto di mirare alla semplicità e alla facilità di comprensione nei loro esempi piuttosto che guardarmi-come-sono-fico-io! complessità.

Insomma, perché il gatto non è sempre gatto.

Anche perché la maggior parte delle persone a cui piace andare in giro a premiare gli UUOC lo fa perché sono più preoccupati di mostrare quanto sono "intelligenti" che di aiutare o insegnare alle persone. In realtà, dimostrano di essere probabilmente solo un altro principiante che ha trovato un minuscolo bastone con cui battere i loro coetanei.


Aggiornare

Ecco un altro UUOC che ho pubblicato in una risposta su 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
}

I pedanti UUOC direbbero che questo è un UUOC perché è facilmente possibile $filterimpostare come predefinito la stringa vuota e avere l' ifistruzione do filter='| grep -v "^$"'ma IMO, non incorporando il carattere pipe $filter, questo "inutile" catserve allo scopo estremamente utile di auto-documentare il fatto quello $filtersulla printfriga non è solo un altro argomento sqlplus, è un filtro di output selezionabile dall'utente opzionale.

Se non c'è alcuna necessità di avere più filtri di uscita opzionali, l'elaborazione opzione potrebbe basta aggiungere | whatevera $filtertutte le volte che necessario - uno in più catin cantiere non sta andando male a nessuno o causare perdite di prestazioni.


11
Per inciso, l' ==interno [ ]non è specificato da POSIX e non tutte le implementazioni lo accettano. L'operatore standardizzato è giusto =.
Charles Duffy

27

Con la versione UUoC, catdeve leggere il file in memoria, quindi scriverlo sulla pipe, e il comando deve leggere i dati dalla pipe, quindi il kernel deve copiare l'intero file tre volte mentre nel caso reindirizzato, il kernel deve solo copiare il file una volta. È più veloce fare qualcosa una volta che farlo tre volte.

Utilizzando:

cat "$@" | command

è un uso completamente diverso e non necessariamente inutile di cat. È ancora inutile se il comando è un filtro standard che accetta zero o più argomenti di nomi di file e li elabora a turno. Considera il trcomando: è un filtro puro che ignora o rifiuta gli argomenti del nome del file. Per alimentare più file ad esso, devi usare catcome mostrato. (Ovviamente, c'è una discussione separata in cui la progettazione di trnon è molto buona; non c'è alcun motivo reale che non possa essere stato progettato come filtro standard.) Questo potrebbe anche essere valido se si desidera che il comando tratti tutto l'input come un singolo file anziché come più file separati, anche se il comando accetterebbe più file separati: ad esempio, wcè un tale comando.

È il cat single-filecaso che è incondizionatamente inutile.


26

In difesa del gatto:

Sì,

   < input process > output 

o

   process < input > output 

è più efficiente, ma molte invocazioni non hanno problemi di prestazioni, quindi non ti interessa.

ragioni ergonomiche:

Siamo abituati a leggere da sinistra a destra, quindi un comando come

    cat infile | process1 | process2 > outfile

è banale da capire.

    process1 < infile | process2 > outfile

deve saltare su process1 e quindi leggere da sinistra a destra. Questo può essere curato da:

    < infile process1 | process2 > outfile

sembra in qualche modo, come se ci fosse una freccia che punta a sinistra, dove non c'è niente. Più confuso e dall'aspetto di citazioni fantasiose è:

    process1 > outfile < infile

e la generazione di script è spesso un processo iterativo,

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

dove vedi i tuoi progressi gradualmente, mentre

    < file 

non funziona nemmeno. I metodi semplici sono meno soggetti a errori e la catenazione dei comandi ergonomica è semplice con il gatto.

Un altro argomento è che la maggior parte delle persone è stata esposta a> e <come operatori di confronto, molto prima di utilizzare un computer e quando si utilizza un computer come programmatori, sono molto più spesso esposti a questi come tali.

E confrontare due operandi con <e> è contro commutativo, il che significa

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

Ricordo la prima volta che utilizzavo <per il reindirizzamento dell'input, temevo

a.sh < file 

potrebbe significare lo stesso di

file > a.sh

e in qualche modo sovrascrivere il mio script a.sh. Forse questo è un problema per molti principianti.

differenze rare

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

Quest'ultimo può essere utilizzato direttamente nei calcoli.

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

Ovviamente anche qui è possibile utilizzare <al posto di un parametro file:

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

ma chi se ne frega - 15k?

Se dovessi imbattermi occasionalmente in problemi, sicuramente cambierei la mia abitudine di invocare il gatto.

Quando si usano file molto grandi o molti, molti, evitare cat va bene. Per la maggior parte delle domande l'uso del gatto è ortogonale, fuori tema, non un problema.

Avviare questi inutili usi inutili della discussione sui gatti su ogni secondo argomento della shell è solo fastidioso e noioso. Fatti una vita e aspetta il tuo minuto di fama, quando hai a che fare con domande sulle prestazioni.


5
+11111 .. In qualità di autore della risposta attualmente accettata, consiglio vivamente questo delizioso complemento. Gli esempi specifici chiariscono le mie argomentazioni spesso astratte e prolisse, e la risata che ricevi dalla trepidazione iniziale dell'autore file > a.shvale da sola il tempo di leggere questo :) Grazie per la condivisione!
negromante

In questa chiamata cat file | wc -c, è wcnecessario leggere lo stdin fino a EOF, contando i byte. Ma in questo, wc -c < filestat solo stdin, scopre che è un file normale e stampa st_size invece di leggere qualsiasi input. Per un file di grandi dimensioni la differenza di prestazioni sarebbe chiaramente visibile.
oguz ismail

18

Un ulteriore problema è che il tubo può mascherare silenziosamente una subshell. Per questo esempio, sostituirò catcon echo, ma esiste lo stesso problema.

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

echo "$x"

Potresti aspettarti xdi contenere foo, ma non è così. Il xset è stato generato in una subshell per eseguire il whileciclo. xnella shell che ha avviato la pipeline ha un valore non correlato o non è impostato affatto.

In bash4, puoi configurare alcune opzioni della shell in modo che l'ultimo comando di una pipeline venga eseguito nella stessa shell di quella che avvia la pipeline, ma poi potresti provare questo

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

ed xè ancora una volta locale per la whilesubshell di.


5
Nelle shell strettamente POSIX questo può essere un problema complicato perché non hai qui stringhe o sostituzioni di processo per evitare il pipe. BashFAQ 24 ha alcune soluzioni utili anche in quel caso.
kojiro

4
In alcune shell, la pipe illustrata non crea una subshell. Gli esempi includono Korn e Z. Supportano anche la sostituzione del processo e qui le stringhe. Ovviamente non sono strettamente POSIX. Bash 4 deve shopt -s lastpipeevitare di creare la subshell.
In pausa fino a nuovo avviso.

13

Come qualcuno che sottolinea regolarmente questo e una serie di altri antipattern di programmazione shell, mi sento obbligato, in ritardo, a pesare.

Lo script di shell è essenzialmente un linguaggio di copia / incolla. Per la maggior parte delle persone che scrivono script di shell, non ci sono per imparare la lingua; è solo un ostacolo che devono superare per continuare a fare le cose nella lingua o nelle lingue con cui hanno effettivamente familiarità.

In quel contesto, vedo come distruttivo e potenzialmente persino distruttivo propagare vari anti-pattern di scripting della shell. Il codice che qualcuno trova su Stack Overflow dovrebbe idealmente essere possibile copiare / incollare nel proprio ambiente con modifiche minime e comprensione incompleta.

Tra le molte risorse di scripting di shell in rete, Stack Overflow è insolito in quanto gli utenti possono contribuire a plasmare la qualità del sito modificando le domande e le risposte sul sito. Tuttavia, le modifiche al codice possono essere problematiche perché è facile apportare modifiche non previste dall'autore del codice. Quindi, tendiamo a lasciare commenti per suggerire modifiche al codice.

L'UUCA ei relativi commenti antipattern non sono solo per gli autori del codice che commentiamo; sono altrettanto un avvertimento per aiutare i lettori del sito a rendersi conto dei problemi nel codice che trovano qui.

Non possiamo sperare di ottenere una situazione in cui nessuna risposta su Stack Overflow consiglia cats inutili (o variabili non quotate, o chmod 777, o una grande varietà di altre piaghe antipattern), ma possiamo almeno aiutare a istruire l'utente che sta per copiare / incolla questo codice nel loop più interno del loro script che viene eseguito milioni di volte.

Per quanto riguarda le ragioni tecniche, la saggezza tradizionale è che dovremmo cercare di ridurre al minimo il numero di processi esterni; questo continua a essere una buona guida generale quando si scrivono script di shell.


2
Inoltre, per i file di grandi dimensioni, il piping catè un sacco di cambi di contesto extra e larghezza di banda della memoria (e l'inquinamento della cache L3 da copie extra di dati nel catbuffer di lettura di e nei buffer pipe). Soprattutto su una grande macchina multi-core (come molte configurazioni di hosting) la larghezza di banda della cache / memoria è una risorsa condivisa.
Peter Cordes

1
@PeterCordes Per favore pubblica le tue misurazioni. Quindi possiamo farlo se è davvero importante nella pratica. La mia esperienza è che normalmente non importa: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange

1
Il tuo blog mostra un rallentamento del 50% per il throughput elevato e non stai nemmeno osservando l'impatto sul throughput totale (se hai cose che tengono occupati gli altri core). Se ci riesco, potrei eseguire i tuoi test mentre x264 o x265 codificano un video utilizzando tutti i core e vedo quanto rallenta la codifica video. bzip2e la gzipcompressione sono entrambe molto lente rispetto alla quantità di overhead che si cataggiunge a quella da sola (con la macchina altrimenti inattiva). È difficile leggere le tue tabelle (ritorno a capo nel mezzo di un numero?). sysil tempo aumenta molto, ma comunque piccolo vs. utente o reale?
Peter Cordes,

8

Uso spesso cat file | myprogramnegli esempi. A volte vengo accusato di uso inutile di cat ( http://porkmail.org/era/unix/award.html ). Non sono d'accordo per i seguenti motivi:

  • È facile capire cosa sta succedendo.

    Durante la lettura di un comando UNIX ti aspetti un comando seguito da argomenti seguito da un reindirizzamento. È possibile mettere il reindirizzamento ovunque, ma è visto raramente, quindi le persone avranno più difficoltà a leggere l'esempio. Credo

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

    è più facile da leggere rispetto a

    program1 -o option -b option < foo | program2

    Se sposti il ​​reindirizzamento all'inizio, confondi le persone che non sono abituate a questa sintassi:

    < foo program1 -o option -b option | program2

    e gli esempi dovrebbero essere facili da capire.

  • È facile cambiare.

    Se sai che il programma può leggere da cat, puoi normalmente presumere che possa leggere l'output da qualsiasi programma che invia a STDOUT, e quindi puoi adattarlo alle tue esigenze e ottenere risultati prevedibili.

  • Sottolinea che il programma non fallisce, se STDIN non è un file.

    Non è lecito ritenere che se program1 < foofunziona, cat foo | program1funzionerà anche. Tuttavia, è lecito ritenere il contrario. Questo programma funziona se STDIN è un file, ma fallisce se l'input è una pipe, perché usa seek:

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

Costo delle prestazioni

C'è un costo per fare l'addizionale cat. Per dare un'idea di quanto ho eseguito alcuni test per simulare baseline ( cat), throughput basso ( bzip2), throughput medio ( gzip) e throughput alto ( 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

I test sono stati eseguiti su un sistema di fascia bassa (0,6 GHz) e un normale laptop (2,2 GHz). Sono stati eseguiti 10 volte su ciascun sistema e il tempismo migliore è stato scelto per imitare la situazione ottimale per ogni test. $ ISO era ubuntu-11.04-desktop-i386.iso. (Tabelle più carine qui: 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

I risultati mostrano che per il throughput medio e basso il costo è dell'ordine dell'1%. Ciò rientra nell'incertezza delle misurazioni, quindi in pratica non c'è differenza.

Per una produttività elevata la differenza è maggiore e c'è una chiara differenza tra i due.

Questo porta alla conclusione: dovresti usare <invece di cat |se:

  • la complessità dell'elaborazione è simile a un semplice grep
  • le prestazioni sono più importanti della leggibilità.

Altrimenti non importa se usi <o cat |.

E quindi dovresti dare un premio UUoC solo se e solo se:

  • puoi misurare una differenza significativa nelle prestazioni (pubblica le tue misurazioni quando assegni il premio)
  • le prestazioni sono più importanti della leggibilità.

-3

Penso che (nel modo tradizionale) usare pipe sia un po 'più veloce; sulla mia scatola ho usato il stracecomando per vedere cosa sta succedendo:

Senza tubo:

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

E con il tubo:

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

Puoi fare alcuni test con stracee timecomandare con comandi sempre più lunghi per un buon benchmarking.


9
Non capisco cosa intendi per (il modo tradizionale) usando pipe , o perché pensi che questo stracedimostri che è più veloce - stracenon sta tracciando l' wc -lesecuzione nel secondo caso. Traccia solo il primo comando della pipeline qui.
kojiro

@kojiro: intendo per modo tradizionale = il modo più utilizzato (penso che usiamo pipe più dell'indirizzamento), non posso confermare che sia più veloce o meno, nella mia traccia ho visto più chiamate di sistema per l'indirizzamento. È possibile utilizzare un programma ac e un ciclo per vedere con uno consumare più tempo. Se sei interessato possiamo metterlo qui :)
TOC

3
Un confronto tra mele e mele si strace -f sh -c 'wc -l < wrong_output.c'affiancherebbe strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy

5
Ecco i risultati di ideone.com, che attualmente sono chiaramente a favore di senza cat: ideone.com/2w1W42#stderr
tripleee

1
@CharlesDuffy: mkfifocrea una pipe con nome . Una pipe anonima viene impostata con pipe(2)e quindi forkata e facendo in modo che il padre e il figlio chiudano diverse estremità della pipe. Ma sì, questa risposta è una totale assurdità, e non ha nemmeno provato a contare le chiamate di sistema o non ha nemmeno provato a contare le chiamate di sistema o ad usare strace -Oper misurare le spese generali, o -rper timestamp ogni chiamata relativa all'ultima ...
Peter Cordes
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.