Devo me preocupar com gatos desnecessários?

50

Muitos utilitários de linha de comando podem receber suas entradas de um canal ou como um argumento de nome de arquivo. Para scripts shell longos, acho que iniciar a cadeia com a cattorna mais legível, principalmente se o primeiro comando precisar de argumentos de várias linhas.

Comparar

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

e

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

O último método é menos eficiente? Se sim, a diferença é suficiente para se preocupar se o script é executado, digamos, uma vez por segundo? A diferença de legibilidade não é enorme.

tshepang
fonte
30
Eu passo mais tempo assistindo pessoas atacar uns aos outros sobre o uso gato inútil neste site que o meu sistema não realmente começando os processos gato
Michael Mrozek
4
@ Michael: 100% de acordo. Caramba, levei mais tempo para vincular ao antigo prêmio da usenet uma vez do que meu computador perderá tempo instanciando cat. No entanto, acho que a questão maior aqui é a legibilidade do código, que geralmente é uma prioridade sobre o desempenho. Quando mais rápido pode realmente ser escrito mais bonito , por que não? Apontar o problema catgeralmente leva o usuário a entender melhor os pipelines e processos em geral. Vale a pena o esforço para que eles escrevam códigos compreensíveis da próxima vez.
Caleb
3
Na verdade, tenho outro motivo para não gostar da primeira forma - se você quiser adicionar outro comando no início do pipeline, também precisará mover o argumento, para que a edição seja mais irritante. (Claro, isso não significa que você tem que usar cat; ponto de Calebe sobre como utilizar funções e resolve redirecionamento isso também.)
Cascabel
Relacionado: Remover usos inúteis de gato ou não?   (Meta)
G-Man Diz 'Reinstate Monica'
11
É noite no trabalho, meu trabalho está se recusando a trabalhar. Abro o stackoverflow e encontro uma pergunta, intitulada "Devo me preocupar com gatos desnecessários?" e ver alguns animais de rua e um programador, ponderando sobre alimentá-los ou não ...
Boris Burkov

Respostas:

46

A resposta "definitiva" é obviamente trazida a você pelo The Useless Use of catAward .

O objetivo do gato é concatenar (ou "catenar") os arquivos. Se for apenas um arquivo, concatená-lo sem nada é uma perda de tempo e custa um processo.

A instanciação de um gato apenas para que seu código seja lido de maneira diferente gera apenas mais um processo e mais um conjunto de fluxos de entrada / saída que não são necessários. Normalmente, a retenção real em seus scripts será loops ineficientes e processamento real. Na maioria dos sistemas modernos, um extra catnão prejudica seu desempenho, mas quase sempre existe outra maneira de escrever seu código.

A maioria dos programas, como você observa, pode aceitar um argumento para o arquivo de entrada. No entanto, sempre há o shell interno <que pode ser usado onde quer que um fluxo STDIN seja esperado, o que poupa um processo, fazendo o trabalho no processo do shell que já está em execução.

Você pode até ser criativo com ONDE escrevê-lo. Normalmente, ele seria colocado no final de um comando antes de você especificar qualquer redirecionamento ou canal de saída como este:

sed s/blah/blaha/ < data | pipe

Mas não precisa ser assim. Pode até vir em primeiro lugar. Por exemplo, seu código de exemplo pode ser escrito assim:

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

Se a sua legibilidade é um problema e seu código é confuso o suficiente para que a adição de uma linha catseja esperada para facilitar o acompanhamento, existem outras maneiras de limpar seu código. Um que eu uso muito que ajuda a tornar os scripts fáceis de descobrir depois é dividir os pipes em conjuntos lógicos e salvá-los em funções. O código do script se torna muito natural e qualquer parte do pipline é mais fácil de depurar.

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

fix_blahs < data

Você pode então continuar com fix_blahs < data | fix_frogs | reorder | format_for_sql. Um pipleline com a seguinte leitura é realmente fácil de seguir e os componentes individuais podem ser depurados facilmente em suas respectivas funções.

Caleb
fonte
26
Eu não sabia que isso <filepoderia acontecer antes do comando. Isso resolve todos os meus problemas!
3
@ Tim: Bash e Zsh apoiam isso, embora eu ache feio. Quando estou preocupado com o meu código ser bonito e sustentável, geralmente uso funções para limpá-lo. Veja minha última edição.
Caleb
8
O @Tim <filepode chegar a qualquer lugar na linha de comando: <file grep needleou grep <file needleou grep needle <file. A exceção são comandos complexos, como loops e agrupamentos; lá o redirecionamento deve ocorrer após o fechamento done/ }/ )/ etc. @Caleb Isso ocorre em todos os shells Bourne / POSIX. E eu discordo que é feio.
Gilles 'SO- stop be evil'
9
@Gilles, em bash você pode substituir $(cat /some/file)com $(< /some/file), que faz a mesma coisa, mas evita disparando um processo.
CJM
3
Só para confirmar que $(< /some/file)é de portabilidade limitada. Ele funciona no bash, mas não no cinza do BusyBox, por exemplo, ou no FreeBSD sh. Provavelmente também não funciona apressado, já que essas três últimas conchas são todas primas próximas.
dubiousjim
22

Aqui está um resumo de algumas das desvantagens de:

cat $file | cmd

sobre

< $file cmd
  • Primeiro, uma observação: faltam (intencionalmente para o objetivo da discussão) aspas duplas $fileacima. No caso de cat, isso é sempre um problema, exceto zsh; no caso do redirecionamento, isso é apenas um problema para bashou ksh88e, para alguns outros shells, apenas quando interativos (não em scripts).
  • A desvantagem mais citada é o processo extra que está sendo gerado. Note que se cmdestá embutido, são 2 processos em alguns shells bash.
  • Ainda na frente do desempenho, exceto nos shells onde catestá embutido, esse também é um comando extra sendo executado (e, é claro, carregado e inicializado (e as bibliotecas às quais está vinculado também)).
  • Ainda em frente ao desempenho, para arquivos grandes, isso significa que o sistema terá de agendar o alternadamente cate cmdprocessos e constantemente encher e esvaziar o buffer pipe. Mesmo cmdque 1GBgrandes read()chamadas de sistema por vez, o controle precise ir e voltar entre cate cmdporque um canal não pode conter mais do que alguns kilobytes de dados por vez.
  • Alguns cmds (como wc -c) podem fazer algumas otimizações quando o stdin é um arquivo regular, com o qual não conseguem fazer, cat | cmdjá que o stdin é apenas um cano. Com cate um pipe, isso também significa que eles não podem estar seek()no arquivo. Para comandos como tacou tail, isso faz uma enorme diferença no desempenho, pois significa que cateles precisam armazenar toda a entrada na memória.
  • O cat $file, e até a versão mais correta cat -- "$file", não funcionarão corretamente para alguns nomes de arquivos específicos, como -( --helpou qualquer coisa que comece com -se você esquecer o --). Se alguém insistir em usar cat, provavelmente deve usar em cat < "$file" | cmdvez de confiabilidade.
  • Se $filenão puder ser aberto para leitura (acesso negado, não existe ...), < "$file" cmdreportará uma mensagem de erro consistente (pelo shell) e não será executado cmd, enquanto cat $file | cmdainda será executado, cmdmas com seu stdin parecendo um arquivo vazio. Isso também significa que, em coisas como < file cmd > file2, file2não é derrotado se filenão puder ser aberto.
Stéphane Chazelas
fonte
2
Em relação ao desempenho: este teste mostra que a diferença está na ordem de 1 pct, a menos que você esteja executando muito pouco processamento no fluxo oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange
2
@OleTange. Aqui está outro teste: truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. Existem muitos parâmetros que entram em cena. A penalidade de desempenho pode ir de 0 a 100%. De qualquer forma, não acho que a penalidade possa ser negativa.
Stéphane Chazelas
2
wc -cé um caso bastante exclusivo, porque possui um atalho. Se você preferir wc -w, é comparável ao grepmeu exemplo (ou seja, muito pouco processamento - que é a situação em que '<' pode fazer a diferença).
precisa
@OleTange, mesmo ( wc -wem um arquivo esparso de 1 GB no local C em linux 4.9 amd64), acho que a abordagem cat leva 23% mais tempo quando em um sistema multicore e 5% ao vinculá-los a um núcleo. Mostrando a sobrecarga extra incorrida ao ter dados acessados ​​por mais de um núcleo. Você poderá obter resultados diferentes se alterar o tamanho do canal, usar dados diferentes, envolver E / S real, usar uma implementação cat que use splice () ... Tudo confirmando que há muitos parâmetros entrando em cena e que, em qualquer caso cat, não ajudará.
Stéphane Chazelas
11
Para mim, com um arquivo de 1 GB wc -w, é uma diferença de cerca de 2% ... 15% de diferença se estiver em um grep simples e direto. Então, estranhamente, se estiver em um compartilhamento de arquivo NFS, na verdade, é 20% mais rápido para lê-lo se canalizado cat( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Weird ...
rogerdpack
16

Colocar <fileno final de um pipeline é menos legível do que ter cat fileno início. O inglês natural lê da esquerda para a direita.

Colocar <fileo início do pipeline também é menos legível que o gato, eu diria. Uma palavra é mais legível que um símbolo, especialmente um símbolo que parece apontar o caminho errado.

O uso catpreserva o command | command | commandformato.

Jim
fonte
Concordo que o uso de <uma vez torna o código menos legível, pois destrói a consistência da sintaxe de uma linha múltipla.
A.Danischewski 29/03
@ Jim Você pode resolver a legibilidade criando um apelido para o <seguinte: alias load='<'e use, por exemplo load file | sed .... Aliases podem ser usados ​​em scripts após a execução shopt -s expand_aliases.
Niieani
11
Sim, eu sei sobre aliases. No entanto, embora esse alias substitua o símbolo por uma palavra, ele exige que o leitor saiba sobre sua configuração de alias pessoal, portanto, não é muito portátil.
Jim
8

Uma coisa que as outras respostas aqui não parecem ter abordado diretamente é que usar catdessa forma não é "inútil" no sentido de que "um processo estranho de gato é gerado e não funciona"; é inútil no sentido de que "é gerado um processo de gato que faz apenas trabalho desnecessário".

No caso destes dois:

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

o shell inicia um processo sed que lê de algum arquivo ou stdin (respectivamente) e depois faz algum processamento - ele lê até atingir uma nova linha, substitui o primeiro 'foo' (se houver) nessa linha por 'bar' e depois imprime essa linha para stdout e loops.

No caso de:

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

A concha gera um processo de gato e um processo de sed e liga o stdout do gato ao stdin do sed. O processo do gato lê um pedaço de vários quilos ou talvez megabytes do arquivo e, em seguida, grava isso em seu stdout, onde o sommand começa a partir daí, como no segundo exemplo acima. Enquanto o sed está processando esse pedaço, o gato está lendo outro pedaço e escrevendo-o no stdout para que o sed trabalhe em seguida.

Em outras palavras, o trabalho extra necessário para adicionar o catcomando não é apenas o trabalho extra de gerar um catprocesso extra , é também o trabalho extra de ler e gravar os bytes do arquivo duas vezes em vez de uma vez. Agora, praticamente falando e em sistemas modernos, isso não faz muita diferença - pode fazer com que seu sistema faça alguns microssegundos de trabalho desnecessário. Mas se for para um script que você planeja distribuir, potencialmente para as pessoas que o usam em máquinas que já estão com pouca potência, alguns microssegundos podem adicionar várias iterações.

godlygeek
fonte
2
Consulte oletange.blogspot.dk/2013/10/useless-use-of-cat.html para obter um teste da sobrecarga do uso do adicional cat.
precisa
@OleTange: Acabei de tropeçar nisso e visitei seu blog. (1) Enquanto vejo o conteúdo (principalmente) em inglês, vejo várias palavras em (acho) dinamarquês: "Klassisk", "Flipcard", "Magasin", "Mosaik", "Sidebjælke", "Øjebliksbillede" , "Tidsskyder", "Blog-arkiv", "Om mig", "Skrevet" e "Vis comentários" (mas "Tweet", "Curtir" e o banner de cookies estão em inglês). Você sabia disso e está sob seu controle? (2) Tenho problemas para ler suas tabelas (2a) porque as linhas de grade estão incompletas e (2b) não entendo o que você quer dizer com “Diff (pct)”.
G-Man diz 'Reinstate Monica'
blogspot.dk é executado pelo Google. Tente substituir por blogspot.com. O "Dif (PCT)" é as ms com catdivididas pelas ms, sem catno por cento (por exemplo, 264 ms / 216 ms = 1,22 = 122% = 22% mais lento com cat)
Ole Tange