Devo me importar com gatos desnecessários?

49

Muitos utilitários de linha de comando podem receber sua entrada de um pipe ou como um argumento de nome de arquivo. Para scripts de shell longos, acho que iniciar a cadeia com um cat torna-a mais legível, especialmente se o primeiro comando precisar de argumentos de várias linhas.

Compare

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? Em caso afirmativo, a diferença é suficiente para se preocupar se o script for executado, digamos, uma vez por segundo? A diferença de legibilidade não é enorme.

    
por Tshepang 08.07.2011 / 15:33

4 respostas

45

A resposta "definitiva" é, obviamente, levada até você pelo Uso inútil do cat Award .

The purpose of cat is to concatenate (or "catenate") files. If it's only one file, concatenating it with nothing at all is a waste of time, and costs you a process.

O Instantiating cat apenas para que seu código seja lido de maneira diferente, faz apenas mais um processo e mais um conjunto de fluxos de entrada / saída que não são necessários. Normalmente, a real retenção de seus scripts será um loop ineficiente e um processamento real. Na maioria dos sistemas modernos, um cat extra não vai matar seu desempenho, mas existe quase sempre outra forma de escrever seu código.

A maioria dos programas, como você nota, é capaz de aceitar um argumento para o arquivo de entrada. No entanto, há sempre o shell embutido < que pode ser usado sempre que um fluxo STDIN for esperado, o que economizará um processo, fazendo o trabalho no processo de shell que já está em execução.

Você pode até ser criativo com ONDE o escreve. Normalmente, ele seria colocado no final de um comando antes de você especificar quaisquer redirecionamentos de saída ou canais como este:

sed s/blah/blaha/ < data | pipe

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

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

Se a legibilidade do script for sua preocupação e seu código estiver confuso o suficiente para permitir que seja mais fácil seguir a adição de uma linha para cat , existem outras maneiras de limpar seu código. Um que eu uso muito que ajuda a tornar os scripts mais fáceis de descobrir é dividir os pipes em conjuntos lógicos e salvá-los em funções. O código de script então se torna muito natural, e qualquer parte da piplina é mais fácil de depurar.

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

fix_blahs < data

Você pode continuar com fix_blahs < data | fix_frogs | reorder | format_for_sql . Uma linha de dados que pareça assim é realmente fácil de seguir, e os componentes individuais podem ser depurados facilmente em suas respectivas funções.

    
por 08.07.2011 / 15:35
20

Aqui está um resumo de algumas das desvantagens de:

cat $file | cmd

mais de

< $file cmd
  • Primeiro, uma observação: há (intencionalmente para o propósito da discussão) a falta de aspas duplas em torno de $file acima. No caso de cat , isso é sempre um problema, exceto por zsh ; no caso do redirecionamento, isso é apenas um problema para bash ou ksh88 e, para alguns outros shells apenas quando interativos (não em scripts).
  • A desvantagem mais citada é o processo extra que está sendo gerado. Observe que, se cmd estiver embutido, haverá até 2 processos em alguns shells, como bash .
  • Ainda na frente de desempenho, exceto em shells em que cat está embutido, que também um comando extra está sendo executado (e, claro, carregado e inicializado (e as bibliotecas também estão vinculadas)).
  • Ainda na frente de desempenho, para arquivos grandes, isso significa que o sistema terá que agendar alternadamente os processos cat e cmd e constantemente preencher e esvaziar o buffer de tubulação. Mesmo se cmd fizer 1GB large read() chamadas do sistema de cada vez, o controle terá que ir e voltar entre cat e cmd porque um pipe não pode conter mais do que alguns kilobytes de dados em um tempo.
  • Alguns cmd s (como wc -c ) podem fazer algumas otimizações quando seu stdin é um arquivo regular que eles não podem fazer com cat | cmd , já que seu stdin é apenas um canal. Com cat e um canal, isso também significa que eles não podem seek() no arquivo. Para comandos como tac ou tail , isso faz uma enorme diferença no desempenho, já que com cat eles precisam armazenar toda a entrada na memória.
  • O cat $file e até mesmo sua versão mais correta cat -- "$file" não funcionará corretamente para alguns nomes de arquivos específicos, como - (ou --help ou qualquer coisa que comece com - se você esquecer o -- ). Se alguém insiste em usar cat , ele provavelmente deve usar cat < "$file" | cmd para confiabilidade.
  • Se $file não puder ser aberto para leitura (acesso negado, não existir ...), < "$file" cmd informará uma mensagem de erro consistente (pelo shell) e não executará cmd , enquanto cat $file | cmd ainda executará cmd , mas com seu stdin parecendo um arquivo vazio. Isso também significa que, em coisas como < file cmd > file2 , file2 não será prejudicado se file não puder ser aberto.
por 26.08.2015 / 14:36
13

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

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

Usar cat preserva o formato command | command | command .

    
por 23.02.2013 / 23:58
7

Uma coisa que as outras respostas aqui não parecem ter abordado diretamente é que usar cat como essa não é "inútil" no sentido de que "um processo de gato estranho é gerado e não funciona"; é inútil no sentido de que "um processo de gato é gerado que faz apenas um 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 então faz algum processamento - ele lê até atingir uma nova linha, substitui o primeiro 'foo' (se houver) nessa linha por 'bar' , em seguida, imprime a linha para stdout e loops.

No caso de:

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

O shell gera um processo cat e um processo sed, e liga o stdout do gato ao stdin do sed. O processo cat lê um pedaço de kilo ou talvez um megabyte do arquivo, depois grava isso no stdout, onde o som sedido é captado a partir do segundo exemplo acima. Enquanto o sed está processando esse fragmento, o cat está lendo outro fragmento e escrevendo para o stdout para que o sed funcione em seguida.

Em outras palavras, o trabalho extra necessário adicionando o comando cat não é apenas o trabalho extra de gerar um processo cat extra, mas também o trabalho extra de ler e gravar os bytes do arquivo duas vezes. de uma vez. Agora, praticamente falando e nos sistemas modernos, isso não faz uma grande diferença - pode fazer seu sistema fazer alguns microssegundos de trabalho desnecessário. Mas se for para um script que você planeja distribuir, potencialmente para pessoas que o usam em máquinas que já são fracas, alguns microssegundos podem se acumular em muitas iterações.

    
por 30.06.2014 / 18:24