Dovrei preoccuparmi dei gatti non necessari?


50

Molte utility della riga di comando possono prendere il loro input da una pipe o come argomento del nome file. Per gli script di shell lunghe, trovo che iniziare la catena con un catrenda più leggibile, specialmente se il primo comando avrebbe bisogno di argomenti a più righe.

Confrontare

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

e

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

Quest'ultimo metodo è meno efficiente? In tal caso, la differenza è sufficiente a preoccuparsi se lo script viene eseguito, diciamo, una volta al secondo? La differenza nella leggibilità non è enorme.


30
Trascorro molto più tempo a guardare le persone attaccarsi a vicenda sull'uso di gatti inutili su questo sito rispetto al mio sistema in realtà avvia i processi del gatto
Michael Mrozek

4
@Michael: 100% d'accordo. Cavolo, mi ci è voluto più tempo per collegarmi al vecchio premio usenet una volta che il mio computer non perderà mai un'istanza cat. Tuttavia, penso che la domanda più grande qui sia la leggibilità del codice che spesso è una priorità rispetto alle prestazioni. Quando più veloce può effettivamente essere scritto più bello , perché no? Sottolineare il problema con di catsolito porta l'utente ad avere una migliore comprensione delle condotte e dei processi in generale. Ne vale la pena, quindi la prossima volta scrivono un codice comprensibile.
Caleb,

3
In realtà ho un'altra ragione per cui non mi piace il primo modulo: se vuoi aggiungere un altro comando all'inizio della pipeline, devi spostare anche l'argomento, quindi la modifica è più fastidiosa. (Naturalmente, questo non significa che devi usare cat; il punto di Caleb sull'uso delle funzioni e del reindirizzamento risolve anche questo.)
Cascabel,


1
È sera al lavoro, il mio aveva si rifiuta di lavorare. Apro StackOverflow e trovo una domanda, intitolata "Devo preoccuparmi dei gatti non necessari?" e vedere alcuni animali senzatetto e un programmatore, meditando di dar loro da mangiare o no ...
Boris Burkov,

Risposte:


46

La risposta "definitiva" è ovviamente fornita da The Useless Use of catAward .

Lo scopo di cat è di concatenare (o "catenare") i file. Se è solo un file, concatenarlo con niente è una perdita di tempo e ti costa un processo.

L'istanza di cat in modo che il tuo codice legga diversamente rende solo un altro processo e un altro set di flussi di input / output che non sono necessari. In genere, il vero ostacolo nei tuoi script sarà costituito da cicli inefficienti ed elaborazione effettiva. Sulla maggior parte dei sistemi moderni, un extra catnon ucciderà le tue prestazioni, ma c'è quasi sempre un altro modo per scrivere il tuo codice.

La maggior parte dei programmi, come notate, sono in grado di accettare un argomento per il file di input. Tuttavia, esiste sempre l'integrato della shell <che può essere utilizzato ovunque sia previsto un flusso STDIN che consente di salvare un processo eseguendo il lavoro nel processo della shell già in esecuzione.

Puoi persino essere creativo con DOVE lo scrivi. Normalmente verrebbe posto alla fine di un comando prima di specificare eventuali reindirizzamenti o pipe in questo modo:

sed s/blah/blaha/ < data | pipe

Ma non deve essere così. Può anche venire prima. Ad esempio il tuo codice di esempio potrebbe essere scritto in questo modo:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

Se la leggibilità degli script è la tua preoccupazione e il tuo codice è abbastanza disordinato che l'aggiunta di una riga per catrendere più facile da seguire, ci sono altri modi per ripulire il codice. Uno che uso molto per aiutare gli script a capire facilmente in seguito è la suddivisione dei tubi in insiemi logici e il loro salvataggio in funzioni. Il codice dello script diventa quindi molto naturale e qualsiasi parte della pipeline è più facile da eseguire il debug.

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

È quindi possibile continuare con fix_blahs < data | fix_frogs | reorder | format_for_sql. Una pipeline che legge in questo modo è davvero facile da seguire e i singoli componenti possono essere facilmente sottoposti a debug nelle rispettive funzioni.


26
Non sapevo che <filepotesse venire prima del comando. Questo risolve tutti i miei problemi!

3
@Tim: Bash e Zsh lo supportano entrambi, anche se penso che sia brutto. Quando sono preoccupato che il mio codice sia carino e gestibile, di solito uso le funzioni per ripulirlo. Vedi la mia ultima modifica.
Caleb

8
@Tim <filepuò arrivare ovunque sulla riga di comando: <file grep needleoppure grep <file needleoppure grep needle <file. L'eccezione sono i comandi complessi come loop e raggruppamenti; lì il reindirizzamento deve arrivare dopo la chiusura done/ }/ )/ ecc. @Caleb Questo vale per tutte le shell Bourne / POSIX. E non sono d'accordo che sia brutto.
Gilles 'SO- smetti di essere malvagio' l'

9
@Gilles, in bash puoi sostituirlo $(cat /some/file)con $(< /some/file), che fa la stessa cosa ma evita di generare un processo.
cjm,

3
Solo per confermare che $(< /some/file)è di portabilità limitata. Funziona con bash, ma non con Ash BusyBox, per esempio, o FreeBSD sh. Probabilmente non funziona neanche nel trattino, poiché quelle ultime tre conchiglie sono tutte cugine vicine.
dubiousjim,

22

Ecco un riepilogo di alcuni degli svantaggi di:

cat $file | cmd

al di sopra di

< $file cmd
  • Innanzitutto, una nota: mancano (intenzionalmente ai fini della discussione) le doppie virgolette mancanti $filesopra. Nel caso di cat, questo è sempre un problema tranne per zsh; nel caso del reindirizzamento, questo è solo un problema per bashoe ksh88per alcune altre shell solo quando interattivo (non negli script).
  • Lo svantaggio più spesso citato è il processo aggiuntivo che viene generato. Nota che se cmdè incorporato, ci sono anche 2 processi in alcune shell come bash.
  • Sempre dal punto di vista delle prestazioni, tranne che nelle shell in cui catè incorporato, viene eseguito anche un comando aggiuntivo (e ovviamente caricato e inizializzato (e anche le librerie a cui è collegato)).
  • Sempre sul fronte delle prestazioni, per file di grandi dimensioni, che significa che il sistema dovrà pianificare il alternativamente cate cmdprocessi e costantemente riempire e svuotare il buffer tubo. Anche se cmdesegue chiamate di sistema di 1GBgrandi dimensioni read()alla volta, il controllo dovrà andare avanti e indietro tra cate cmdperché una pipe non può contenere più di pochi kilobyte di dati alla volta.
  • Alcuni cmds (come wc -c) possono fare alcune ottimizzazioni quando il loro stdin è un file normale che non possono fare in cat | cmdquanto il loro stdin è solo una pipe allora. Con cate una pipe, significa anche che non possono seek()all'interno del file. Per comandi come taco tail, ciò fa un'enorme differenza nelle prestazioni in quanto ciò significa che con catessi è necessario memorizzare l'intero input in memoria.
  • La cat $file, e anche la sua versione più corretta cat -- "$file", non funzionerà correttamente per alcuni nomi di file specifici come -(o --helpo qualsiasi altra cosa che inizi -se si dimentica --). Se uno insiste sull'uso cat, probabilmente dovrebbe usare cat < "$file" | cmdinvece per affidabilità.
  • Se $filenon può essere aperto per la lettura (accesso negato, non esiste ...), < "$file" cmdsegnalerà un messaggio di errore coerente (dalla shell) e non verrà eseguito cmd, mentre cat $file | cmdverrà comunque eseguito cmdma con lo stdin che sembra un file vuoto. Ciò significa anche che in cose del genere < file cmd > file2, file2non filepuò essere ostruito se non può essere aperto.

2
Per quanto riguarda le prestazioni: questo test mostra che la differenza è nell'ordine dell'1% a meno che non si stia eseguendo pochissima elaborazione sullo stream oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange

2
@OleTange. Ecco un altro test: truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. Ci sono molti parametri che entrano in scena. La penalità di prestazione può andare dallo 0 al 100%. In ogni caso, non credo che la penalità possa essere negativa.
Stéphane Chazelas,

2
wc -cè un caso piuttosto singolare, perché ha una scorciatoia. Se invece lo fai wc -w, è paragonabile al grepmio esempio (ovvero elaborazione molto ridotta - che è la situazione in cui '<' può fare la differenza).
Ole Tange,

@OleTange, anche ( wc -wsu un file sparse da 1 GB nella locale C su linux 4.9 amd64) quindi trovo che l'approccio cat impieghi il 23% in più di tempo su un sistema multicore e il 5% quando li lega a un core. Mostra l'overhead aggiuntivo dovuto all'accesso ai dati da più di un core. Probabilmente otterrai risultati diversi se cambi la dimensione della pipe, usi dati diversi, coinvolgi I / O reali e usi un'implementazione cat che usa splice () ... Tutto ciò conferma che ci sono molti parametri che entrano nella foto e che in ogni caso catnon aiuterà.
Stéphane Chazelas,

1
Per me con un file da 1 GB wc -wè una differenza di circa il 2% ... 15% di differenza se si tratta di un semplice grep semplice. Quindi, stranamente, se si trova su una condivisione di file NFS, in realtà è più veloce del 20% leggerlo se trasmesso da cat( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Strano ...
rogerdpack

16

Mettere <filealla fine di una pipeline è meno leggibile che avere cat fileall'inizio. L'inglese naturale legge da sinistra a destra.

Mettere <fileun inizio della pipeline è anche meno leggibile del gatto, direi. Una parola è più leggibile di un simbolo, specialmente un simbolo che sembra indicare la direzione sbagliata.

L'uso catconserva il command | command | commandformato.


Sono d'accordo, l'uso di <una volta rende il codice meno leggibile, poiché distrugge la coerenza della sintassi di una multipipeline.
A. Danischewski,

@Jim È possibile risolvere la leggibilità creando un alias <come questo: alias load='<'e quindi usare ad es load file | sed .... Gli alias possono essere utilizzati negli script dopo l'esecuzione shopt -s expand_aliases.
niieani,

1
Sì, lo so sugli alias. Tuttavia, sebbene questo alias sostituisca il simbolo con una parola, richiede al lettore di conoscere le impostazioni del proprio alias personale, quindi non è molto portatile.
Jim,

8

Una cosa che le altre risposte qui non sembrano aver affrontato direttamente è che l'uso in catquesto modo non è "inutile", nel senso che "viene generato un processo di gatto estraneo che non funziona"; è inutile nel senso che "viene generato un processo cat che fa solo lavori non necessari".

Nel caso di questi due:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

la shell avvia un processo sed che legge da alcuni file o stdin (rispettivamente) e quindi esegue alcune elaborazioni - legge fino a quando non colpisce una nuova riga, sostituisce il primo "pippo" (se presente) su quella linea con "barra", quindi stampa quella linea a stdout e loop.

In caso di:

cat somefile | sed 's/foo/bar/'

La shell genera un processo cat e un processo sed e collega lo stdout del gatto allo stdin di sed. Il processo cat legge un file di diversi chilogrammi o forse mega byte dal file, quindi lo scrive nel suo stdout, dove il sed sommand raccoglie da lì come nel secondo esempio sopra. Mentre sed sta elaborando quel pezzo, il gatto sta leggendo un altro pezzo e lo sta scrivendo sul suo stdout per sed su cui lavorare.

In altre parole, il lavoro extra richiesto aggiungendo il catcomando non è solo il lavoro extra di generare un catprocesso extra , è anche il lavoro extra di leggere e scrivere i byte del file due volte anziché una volta. Ora, praticamente parlando e su sistemi moderni, ciò non fa una grande differenza: potrebbe far fare al tuo sistema alcuni microsecondi di lavoro inutile. Ma se è per uno script che prevedi di distribuire, potenzialmente alle persone che lo usano su macchine che sono già sottodimensionate, alcuni microsecondi possono sommarsi a molte iterazioni.


2
Vedere oletange.blogspot.dk/2013/10/useless-use-of-cat.html per un test delle spese generali di utilizzo dell'addizionale cat.
Ole Tange,

@OleTange: mi sono appena imbattuto in questo e ho visitato il tuo blog. (1) Mentre vedo il contenuto (principalmente) in inglese, vedo un mucchio di parole in (immagino) danese: "Klassisk", "Flipcard", "Magasin", "Mosaik", "Sidebjælke", "Øjebliksbillede" , "Tidsskyder", "Blog-arkiv", "Om mig", "Skrevet" e "Vis kommentarer" (ma "Tweet", "Mi piace" e il banner dei cookie sono in inglese). Lo sapevi ed è sotto il tuo controllo? (2) Ho difficoltà a leggere le tue tabelle (2a) perché le linee della griglia sono incomplete e (2b) non capisco cosa intendi con "Diff (pct)".
G-Man dice "Ripristina Monica" il

blogspot.dk è gestito da Google. Prova a sostituire con blogspot.com. Il "Diff (pct)" è il ms con catdiviso per il ms senza catin percentuale (es. 264 ms / 216 ms = 1,22 = 122% = 22% più lento con cat)
Ole Tange
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.