Uso inútil de gato?


101

Isso provavelmente está em muitas perguntas frequentes - em vez de usar:

cat file | command

(que é chamado de uso inútil de gato), a maneira correta deve ser:

command < file

Na segunda maneira, "correta" - o SO não precisa gerar um processo extra.
Apesar de saber disso, continuei a usar gato inútil por 2 motivos.

  1. mais estético - gosto quando os dados se movem uniformemente apenas da esquerda para a direita. E é mais fácil de substituir catcom algo mais ( gzcat, echo...), adicionar um segundo arquivo ou inserir novo filtro ( pv, mbuffer, grep...).

  2. Eu "senti" que poderia ser mais rápido em alguns casos. Mais rápido porque há 2 processos, o primeiro ( cat) faz a leitura e o segundo faz tudo. E eles podem ser executados em paralelo, o que às vezes significa uma execução mais rápida.

Minha lógica está correta (pelo segundo motivo)?


22
caté um tubo de identidade . Ele apenas transmite sua entrada para sua saída. Se o segundo programa na cadeia pode obter sua entrada do mesmo argumento que você passou cat(ou da entrada padrão, se você não passar nenhum argumento), então caté absolutamente inútil e apenas resulta em um processo adicional sendo bifurcado e um canal adicional sendo criada.
Frédéric Hamidi

11
@ FrédéricHamidi quando o gato não tem argumentos ou o seu argumento é -, é um tubo de identidade. Quando ele tem mais de um argumento de nome de arquivo sem traço, ele se torna algo mais do que um canal de identidade e começa a servir a um propósito real.
kojiro

3
O link anteriormente popular para partmaps.org infelizmente está morto. O conteúdo está agora em porcomail.org/era/unix/award.html
tripleee


2
Eu observo que se você quiser mostrar o fluxo de dados para a direita (motivo 1), você pode fazer isso colocando o redirecionamento de arquivo antes do comando, como em <file command1 | command2, embora haja desacordo sobre a estética.
holdenweb

Respostas:


81

Eu não sabia do prêmio até hoje, quando um novato tentou apontar o UUOC para mim por uma das minhas respostas. Era umcat file.txt | grep foo | cut ... | cut ... . Eu dei a ele um pedaço do que eu pensava, e só depois de fazer isso visitei o link que ele me deu referindo-se à origem do prêmio e à prática de fazê-lo. Pesquisas adicionais me levaram a esta pergunta Infelizmente, apesar da consideração consciente, nenhuma das respostas incluiu meu raciocínio.

Eu não queria ficar na defensiva ao responder a ele. Afinal, na minha juventude, eu teria escrito o comando grep foo file.txt | cut ... | cut ...porque sempre que você faz os simples greps frequentes, você aprende a localização do argumento do arquivo e já sabe que o primeiro é o padrão e os últimos são os nomes dos arquivos.

Foi uma escolha consciente a ser usada catquando respondi à pergunta, em parte por uma razão de "bom gosto" (nas palavras de Linus Torvalds), mas principalmente por uma razão convincente de função.

O último motivo é mais importante, então irei divulgá-lo primeiro. Quando eu ofereço um pipeline como solução, espero que ele seja reutilizável. É bastante provável que um pipeline seja adicionado no final ou unido em outro pipeline. Nesse caso, ter um argumento de arquivo para grep atrapalha a capacidade de reutilização, e muito possivelmente fazê-lo silenciosamente sem uma mensagem de erro se o argumento de arquivo existir. I. e. grep foo xyz | grep bar xyz | wclhe dará quantas linhas em xyzcontêm barenquanto você espera o número de linhas que contêm fooe bar. Ter que alterar os argumentos de um comando em um pipeline antes de usá-lo está sujeito a erros. Acrescente a isso a possibilidade de falhas silenciosas e torna-se uma prática particularmente insidiosa.

A primeira razão também não é sem importância, uma vez que muito " bom gosto " é meramente uma razão subconsciente intuitiva para coisas como as falhas silenciosas acima, nas quais você não consegue pensar direito no momento em que alguma pessoa que precisa de educação diz "mas não é aquele gato inútil ".

No entanto, tentarei também tornar consciente o antigo motivo do "bom gosto" que mencionei. Essa razão tem a ver com o espírito de design ortogonal do Unix. grepnão faz cute lsnão faz grep. Portanto, no mínimo grep foo file1 file2 file3vai contra o espírito do design. A maneira ortogonal de fazer isso é cat file1 file2 file3 | grep foo. Agora, grep foo file1é apenas um caso especial de grep foo file1 file2 file3, e se você não tratá-lo da mesma forma, você está pelo menos usando os ciclos do relógio cerebral tentando evitar o prêmio do gato inútil.

Isso nos leva ao argumento de que grep foo file1 file2 file3está concatenando, e catconcatena de forma apropriada, cat file1 file2 file3mas porque catnão está concatenando, cat file1 | grep fooportanto, estamos violando o espírito do cate do todo-poderoso Unix. Bem, se fosse esse o caso, então o Unix precisaria de um comando diferente para ler a saída de um arquivo e despejá-lo em stdout (não paginá-lo ou qualquer coisa apenas um spit puro em stdout). Portanto, você teria a situação em que diz cat file1 file2ou diz dog file1e conscienciosamente lembre-se de evitar cat file1para evitar o recebimento do prêmio, ao mesmo tempo que evita, dog file1 file2uma vez que, esperançosamente, o design de doggeraria um erro se vários arquivos fossem especificados.

Esperançosamente, neste ponto, você simpatiza com os designers do Unix por não incluir um comando separado para cuspir um arquivo para stdout, enquanto também nomeia catpara concatenar em vez de dar a ele algum outro nome. <edit>removeu comentários incorretos sobre <, de fato, <é um recurso eficiente de não copiar para cuspir um arquivo em stdout que você pode posicionar no início de um pipeline, então os designers do Unix incluíram algo especificamente para isso</edit>

A próxima pergunta é: por que é importante ter comandos que apenas geram um arquivo ou a concatenação de vários arquivos para o stdout, sem nenhum processamento posterior? Uma razão é evitar que cada comando Unix que opera na entrada padrão saiba como analisar pelo menos um argumento do arquivo de linha de comando e usá-lo como entrada, se existir. A segunda razão é evitar que os usuários tenham que se lembrar: (a) para onde vão os argumentos do nome do arquivo; e (b) evitar o bug do pipeline silencioso, conforme mencionado acima.

Isso nos leva ao porquê de grepter uma lógica extra. A lógica é permitir a fluência do usuário para comandos que são usados ​​com frequência e de forma autônoma (em vez de um pipeline). É um pequeno compromisso de ortogonalidade para um ganho significativo em usabilidade. Nem todos os comandos devem ser projetados dessa forma e os comandos que não são usados ​​com frequência devem evitar completamente a lógica extra de argumentos de arquivo (lembre-se de que a lógica extra leva a uma fragilidade desnecessária (a possibilidade de um bug)). A exceção é permitir argumentos de arquivo como no caso de grep. (A propósito, observe que lshá uma razão completamente diferente para não apenas aceitar, mas também exigir argumentos de arquivo)

Finalmente, o que poderia ter sido feito melhor seria se comandos excepcionais como grep(mas não necessariamente ls) gerassem um erro se a entrada padrão também estivesse disponível quando os argumentos do arquivo fossem especificados.


52
Observe que, quando grepé chamado com vários nomes de arquivo, ele prefixa as linhas encontradas com o nome do arquivo em que foi encontrado (a menos que você desative esse comportamento). Ele também pode relatar os números de linha nos arquivos individuais. Se usar apenas catpara alimentar grep, você perde os nomes dos arquivos e os números das linhas são contínuos em todos os arquivos, não por arquivo. Portanto, há razões para greplidar com vários arquivos que catnão podem ser manipulados. Os casos de arquivo único e nenhum arquivo são simplesmente casos especiais do uso geral de vários arquivos grep.
Jonathan Leffler

38
Conforme observado na resposta de kojiro , é perfeitamente possível e legal iniciar o pipeline < file command1 .... Embora a posição convencional para os operadores de redirecionamento de E / S seja após o nome do comando e seus argumentos, essa é apenas a convenção e não um posicionamento obrigatório. O <tem que preceder o nome do arquivo. Então, há um perto de perfeita simetria entre >outpute <inputredirecionamentos: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler

15
Acho que uma das razões pelas quais as pessoas jogam a pedra UUoC (incluindo eu) é principalmente educar. Às vezes, as pessoas processam arquivos de texto enormes com gigabytes, caso em que minimizar tubos (UUoC, colapsar greps sequenciais em um, aso) é crucial e muitas vezes pode ser assumido com segurança com base na pergunta de que o OP realmente não sabe que pequenos ajustes podem ter impactos enormes no desempenho. Eu concordo totalmente com o seu ponto sobre os ciclos cerebrais e é por isso que eu uso gatos regularmente, mesmo quando não é necessário. Mas é importante saber que não é necessário.
Adrian Frühwirth

13
Por favor entenda; Não estou de forma alguma dizendo que isso caté inútil. Não é que catseja inútil; é que uma construção particular não precisa do uso de cat. Se quiser, observe que é UUoC (Uso inútil de cat) e não UoUC (Uso inútil cat). Existem muitas ocasiões em que caté a ferramenta correta a ser usada; Não tenho nenhum problema com ele sendo usado quando é a ferramenta correta a ser usada (e, de fato, menciono um caso em minha resposta).
Jonathan Leffler

6
@randomstring eu ouvi, mas acho que realmente depende do caso de uso. Quando usado na linha de comando, um adicional catno canal pode não ser um grande negócio, dependendo dos dados, mas quando usado como um ambiente de programação pode ser absolutamente necessário implementar essas coisas críticas de desempenho; especialmente quando se lida com bashisso, em termos de desempenho, é como uma roda de formato retangular (em comparação com kshqualquer coisa. Estou falando até 10 vezes mais lento aqui - sem brincadeira). Você não quer otimizar seus garfos (e não apenas isso) quando se lida com scripts maiores ou enormes loops.
Adrian Frühwirth

58

Não!

Em primeiro lugar, não importa onde em um comando o redirecionamento acontece. Portanto, se você gosta do redirecionamento para a esquerda do comando, tudo bem:

< somefile command

é o mesmo que

command < somefile

Em segundo lugar, existem n + 1 processos e um subshell acontecendo quando você usa um pipe. É decididamente mais lento. Em alguns casos, n seria zero (por exemplo, quando você está redirecionando para um shell embutido), então, ao usar, catvocê está adicionando um novo processo totalmente desnecessário.

Como generalização, sempre que você estiver usando um cano, vale a pena gastar 30 segundos para ver se você consegue eliminá-lo. (Mas provavelmente não vale a pena demorar muito mais do que 30 segundos.) Aqui estão alguns exemplos em que tubos e processos são usados ​​com frequência desnecessariamente:

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)

Sinta-se à vontade para editar e adicionar mais exemplos.


2
Bem, o aumento de velocidade não será muito.
Dakkaron de

9
colocar o "<algumarquivo" antes do "comando" tecnicamente dá a você da esquerda para a direita, mas torna uma leitura ambígua porque não há demarcação sintática: < cat grep dogé um exemplo inventado para mostrar que você não pode distinguir facilmente entre o arquivo de entrada, o comando que recebe a entrada e os argumentos para o comando.
necromante de

2
A regra que adotei para decidir para onde vai o redirecionamento STDIN é fazer o que quer que minimize a aparência de ambigüidade / potencial para surpresa. Dizer dogmaticamente que vai antes traz à tona o problema do necromante, mas dizer dogmaticamente que vai depois pode fazer a mesma coisa. Considere o seguinte: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). P. Aplica- <quxse a foo, ou a baz, que é -exec'd by foo? R. Aplica-se a foo, mas pode parecer ambíguo. Colocar <qux antes foo , neste caso, é mais claro, embora menos comum, e é análogo ao trailing ENV=VAR quux.
Mark G.

3
@necromancer, <"cat" grep dogé mais fácil de ler, aí. (Normalmente sou pró-espaço em branco, mas este caso específico é uma exceção).
Charles Duffy,

1
@kojiro "É decididamente mais lento." Você não pode escrever isso sem comprovar isso com números. Meus números estão aqui: oletange.blogspot.com/2013/10/useless-use-of-cat.html (e eles mostram que só é mais lento quando você tem alto rendimento) Onde estão os seus?
Ole Tange

30

Eu discordo da maioria dos casos do prêmio UUOC excessivamente presunçoso porque, ao ensinar outra pessoa, caté um marcador conveniente para qualquer comando ou pipeline complicado de comandos que produza saída adequada para o problema ou tarefa em discussão.

Isso é especialmente verdadeiro em sites como Stack Overflow, ServerFault, Unix e Linux ou qualquer um dos sites SE.

Se alguém perguntar especificamente sobre otimização, ou se você quiser adicionar informações extras sobre isso, ótimo, fale sobre como usar cat é ineficiente. Mas não repreenda as pessoas porque elas escolheram buscar simplicidade e facilidade de compreensão em seus exemplos, em vez de olhar para mim como sou legal! complexidade.

Resumindo, porque o gato nem sempre é gato.

Também porque a maioria das pessoas que gosta de premiar UUOCs o faz porque estão mais preocupadas em se exibir sobre o quão 'inteligentes' são do que em ajudar ou ensinar as pessoas. Na realidade, eles demonstram que provavelmente são apenas mais um novato que encontrou um pequeno graveto para bater em seus colegas.


Atualizar

Aqui está outro UUOC que postei em uma resposta em 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
}

Pedantes UUOC diriam que é um UUOC porque é facilmente possível tornar $filterpadrão para a string vazia e fazer com que a ifinstrução o faça, filter='| grep -v "^$"'mas IMO, por não incorporar o caractere de barra vertical $filter, este "inútil" catserve ao propósito extremamente útil de autodocumentar o fato que $filterna printflinha não é apenas outro argumento para sqlplus, é um filtro de saída opcional selecionável pelo usuário.

Se houver necessidade de vários filtros de saída opcionais, o processamento de opções pode apenas anexar com | whatevera $filterfrequência necessária - um extra catno pipeline não vai prejudicar nada ou causar qualquer perda perceptível de desempenho.


11
Como um aparte - ==dentro [ ]não é especificado pelo POSIX, e nem todas as implementações o aceitam. O operador padronizado é justo =.
Charles Duffy,

26

Com a versão UUoC, catdeve ler o arquivo na memória, depois gravá-lo no pipe, e o comando deve ler os dados do pipe, então o kernel tem que copiar o arquivo inteiro três vezes, enquanto no caso redirecionado, o kernel só precisa copiar o arquivo uma vez. É mais rápido fazer algo uma vez do que três vezes.

Usando:

cat "$@" | command

é um uso totalmente diferente e não necessariamente inútil de cat. Ainda é inútil se o comando for um filtro padrão que aceita zero ou mais argumentos de nome de arquivo e os processa sucessivamente. Considere o trcomando: é um filtro puro que ignora ou rejeita argumentos de nome de arquivo. Para alimentar vários arquivos nele, você deve usar catcomo mostrado. (Claro, há uma discussão separada de que o design do trnão é muito bom; não há nenhuma razão real para ele não ter sido projetado como um filtro padrão.) Isso também pode ser válido se você quiser que o comando trate todas as entradas como um arquivo único em vez de vários arquivos separados, mesmo se o comando aceitar vários arquivos separados: por exemplo, wcé esse comando.

É o cat single-filecaso que é incondicionalmente inútil.


26

Em defesa do gato:

Sim,

   < input process > output 

ou

   process < input > output 

é mais eficiente, mas muitas invocações não têm problemas de desempenho, então você não se importa.

razões ergonômicas:

Estamos acostumados a ler da esquerda para a direita, então um comando como

    cat infile | process1 | process2 > outfile

é trivial de entender.

    process1 < infile | process2 > outfile

tem que pular o processo 1 e depois ler da esquerda para a direita. Isso pode ser curado por:

    < infile process1 | process2 > outfile

parece de alguma forma, como se houvesse uma seta apontando para a esquerda, onde nada está. Mais confuso e parecendo uma citação sofisticada:

    process1 > outfile < infile

e gerar scripts geralmente é um processo iterativo,

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

onde você vê seu progresso passo a passo, enquanto

    < file 

nem mesmo funciona. As formas simples são menos propensas a erros e a catenação ergonômica do comando é simples com o gato.

Outro tópico é que a maioria das pessoas foi exposta a> e <como operadores de comparação, muito antes de usar um computador e ao usar um computador como programadores, é muito mais frequentemente exposta a estes como tal.

E comparar dois operandos com <e> é contra comutativo, o que significa

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

Lembro-me da primeira vez que usei <para redirecionamento de entrada, temi

a.sh < file 

pode significar o mesmo que

file > a.sh

e de alguma forma sobrescrever meu script a.sh. Talvez este seja um problema para muitos iniciantes.

diferenças raras

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

Este último pode ser usado em cálculos diretamente.

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

Claro que o <também pode ser usado aqui, em vez de um parâmetro de arquivo:

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

mas quem se importa - 15k?

Se eu ocasionalmente tivesse problemas, certamente mudaria meu hábito de invocar gatos.

Ao usar arquivos muito grandes ou muitos, muitos, evitar gato é bom. Para a maioria das perguntas, o uso de gato é ortogonal, fora do tópico, não é um problema.

Começar esse uso inútil de discussão sobre gatos em cada segundo tópico de shell é apenas chato e chato. Pegue uma vida e espere pelo seu minuto de fama, ao lidar com questões de desempenho.


5
+11111 .. Como autor da resposta atualmente aceita, recomendo fortemente este delicioso complemento. Os exemplos específicos elucidam meus argumentos muitas vezes abstratos e prolixos, e a risada que você arranca com a ansiedade inicial do autor por file > a.shsi só vale a pena ler isto :) Obrigado por compartilhar!
necromante

Nesta invocação cat file | wc -c, wcprecisa ler stdin até EOF, contando bytes. Mas nisso, wc -c < fileele apenas estatísticas stdin, descobre que é um arquivo normal e imprime st_size em vez de ler qualquer entrada. Para um arquivo grande, a diferença no desempenho seria claramente visível.
oguz ismail

18

Um problema adicional é que o tubo pode mascarar silenciosamente uma subcamada. Para este exemplo, substituirei catpor echo, mas existe o mesmo problema.

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

echo "$x"

Você pode esperar xconter foo, mas não o faz. O que xvocê definiu estava em um subshell gerado para executar o whileloop. xno shell que iniciou o pipeline tem um valor não relacionado ou não está definido.

No bash4, você pode configurar algumas opções de shell para que o último comando de um pipeline seja executado no mesmo shell daquele que inicia o pipeline, mas então você pode tentar isso

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

e xé mais uma vez local para o whilesubshell de.


5
Em shells estritamente POSIX, isso pode ser um problema complicado porque você não tem aqui strings ou substituições de processo para evitar o pipe. O BashFAQ 24 tem algumas soluções úteis mesmo nesse caso.
kojiro

4
Em algumas conchas, o tubo ilustrado não cria uma subcamada. Os exemplos incluem Korn e Z. Eles também suportam substituição de processos e strings here. Claro que eles não são estritamente POSIX. O Bash 4 deve shopt -s lastpipeevitar a criação do subshell.
Pausado até novo aviso.

13

Como alguém que regularmente aponta isso e uma série de outros antipadrões de programação de shell, me sinto obrigado, tardiamente, a ponderar.

Shell script é basicamente uma linguagem de copiar / colar. Para a maioria das pessoas que escrevem scripts de shell, elas não querem aprender a linguagem; é apenas um obstáculo que eles precisam superar para continuar a fazer as coisas no (s) idioma (s) com os quais estão realmente familiarizados.

Nesse contexto, vejo como perturbador e potencialmente até destrutivo propagar vários anti-padrões de script de shell. O código que alguém encontra no Stack Overflow deve ser idealmente possível para copiar / colar em seu ambiente com alterações mínimas e compreensão incompleta.

Entre os muitos recursos de script de shell na rede, Stack Overflow é incomum porque os usuários podem ajudar a moldar a qualidade do site editando as perguntas e respostas no site. No entanto, as edições de código podem ser problemáticas porque é fácil fazer alterações que não foram pretendidas pelo autor do código. Portanto, tendemos a deixar comentários para sugerir alterações no código.

O UUCA e comentários antipadrões relacionados não são apenas para os autores do código que comentamos; eles são um caveat emptor para ajudar os leitores do site a se conscientizarem dos problemas no código que encontram aqui.

Não podemos esperar alcançar uma situação em que nenhuma resposta no Stack Overflow recomende cats inúteis (ou variáveis ​​não citadas, ou chmod 777, ou uma grande variedade de outras pragas antipadrão), mas podemos pelo menos ajudar a educar o usuário que está prestes a copiar / cole esse código no loop mais interno de seu script, que é executado milhões de vezes.

No que diz respeito às razões técnicas, a sabedoria tradicional é que devemos tentar minimizar o número de processos externos; isso continua sendo uma boa orientação geral ao escrever scripts de shell.


1
Além disso, para arquivos grandes, o piping caté um monte de mudanças de contexto extras e largura de banda de memória (e poluição do cache L3 de cópias extras de dados no catbuffer de leitura e nos buffers de pipe). Especialmente em uma grande máquina com vários núcleos (como muitas configurações de hospedagem), a largura de banda de cache / memória é um recurso compartilhado.
Peter Cordes

1
@PeterCordes Por favor, publique suas medições. Então, podemos, se realmente importa na prática. Minha experiência é que normalmente não importa: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange

1
Seu próprio blog mostra uma desaceleração de 50% para alto rendimento, e você nem está olhando para o impacto no rendimento total (se você tivesse coisas mantendo os outros núcleos ocupados). Se eu conseguir fazer isso, posso executar seus testes enquanto x264 ou x265 estão codificando um vídeo usando todos os núcleos e ver o quanto isso torna a codificação de vídeo mais lenta. bzip2e a gzipcompressão são ambas muito lentas em comparação com a quantidade de sobrecarga catadicionada somente a isso (com a máquina ociosa). É difícil ler suas tabelas (quebra de linha no meio de um número?). syso tempo aumenta muito, mas ainda pequeno vs. usuário ou real?
Peter Cordes

8

Costumo usar cat file | myprogramem exemplos. Às vezes estou sendo acusado de uso inútil de gato ( http://porkmail.org/era/unix/award.html ). Eu discordo pelos seguintes motivos:

  • É fácil entender o que está acontecendo.

    Ao ler um comando UNIX, você espera um comando seguido de argumentos seguidos de redirecionamento. É possível colocar o redirecionamento em qualquer lugar, mas raramente é visto - portanto, as pessoas terão mais dificuldade em ler o exemplo. Acredito

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

    é mais fácil de ler do que

    program1 -o option -b option < foo | program2

    Se você mover o redirecionamento para o início, estará confundindo as pessoas que não estão acostumadas com esta sintaxe:

    < foo program1 -o option -b option | program2

    e os exemplos devem ser fáceis de entender.

  • É fácil mudar.

    Se você sabe que o programa pode ler cat, normalmente pode assumir que ele pode ler a saída de qualquer programa com saída para STDOUT e, portanto, pode adaptá-lo às suas próprias necessidades e obter resultados previsíveis.

  • Salienta que o programa não falha, se STDIN não for um arquivo.

    Não é seguro presumir que, se program1 < foofuncionar, cat foo | program1também funcionará. No entanto, é seguro assumir o oposto. Este programa funciona se STDIN for um arquivo, mas falha se a entrada for um canal, porque usa a busca:

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

Custo de desempenho

Há um custo para fazer o adicional cat. Para dar uma ideia de quanto eu executei alguns testes para simular a linha de base ( cat), baixa taxa de transferência ( bzip2), média ( gzip) e alta taxa de transferência ( 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

Os testes foram executados em um sistema low end (0,6 GHz) e um laptop comum (2,2 GHz). Eles foram executados 10 vezes em cada sistema e o melhor tempo foi escolhido para simular a situação ideal para cada teste. O $ ISO era ubuntu-11.04-desktop-i386.iso. (Tabelas mais bonitas aqui: 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

Os resultados mostram que para baixa e média vazão o custo é da ordem de 1%. Isso está dentro da incerteza das medições, portanto, na prática, não há diferença.

Para alto rendimento, a diferença é maior e há uma diferença clara entre os dois.

Isso leva à conclusão: você deve usar em <vez de cat |se:

  • a complexidade do processamento é semelhante a um grep simples
  • o desempenho é mais importante do que a legibilidade.

Caso contrário, não importa se você usa <oucat | .

E, portanto, você só deve dar um prêmio UUoC se e somente se:

  • você pode medir uma diferença significativa no desempenho (publique suas medições quando der o prêmio)
  • o desempenho é mais importante do que a legibilidade.

-3

Acho que (da forma tradicional) usar cachimbo é um pouco mais rápido; na minha caixa eu useistrace comando para ver o que está acontecendo:

Sem 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 com cachimbo:

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

Você pode fazer alguns testes stracee timecomandos com mais e mais comandos para um bom benchmarking.


9
Não entendo o que você quer dizer com (da maneira tradicional) usando pipe , ou por que você acha que isso stracemostra que é mais rápido - stracenão está rastreando a wc -lexecução no segundo caso. Ele rastreia apenas o primeiro comando do pipeline aqui.
kojiro

@kojiro: quero dizer por forma tradicional = a forma mais usada (acho que usamos pipe mais do que indireção), não posso confirmar se é mais rápido ou não, no meu rastreamento vi mais chamadas de sistema para indireção. Você pode usar um programa ac e um loop para ver com um consome mais tempo. Se você estiver interessado, podemos colocá-lo aqui :)
TOC

3
Uma comparação maçãs com maçãs seria colocada strace -f sh -c 'wc -l < wrong_output.c'ao lado strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy,

5
Aqui estão os resultados de ideone.com, que atualmente são claramente a favor de sem cat: ideone.com/2w1W42#stderr
tripleee

1
@CharlesDuffy: mkfifocria um canal nomeado . Um tubo anônimo é configurado com pipe(2)bifurcação e fazendo com que o pai e o filho fechem as extremidades do tubo. Mas sim, essa resposta é um total absurdo, e nem mesmo tentei contar as chamadas do sistema ou usar strace -Opara medir a sobrecarga, ou -rpara registrar a data e hora de cada chamada em relação à última ...
Peter Cordes
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.