Eu não estava ciente do prêmio até hoje, quando um novato tentou prender o UUOC em mim por uma das minhas respostas. Foi um cat file.txt | grep foo | cut ... | cut ...
. Pensei um pouco sobre ele e só depois visitei o link que ele me deu, referindo-se às origens do prêmio e à prática de fazê-lo. Outras pesquisas me levaram a essa pergunta. Infelizmente, apesar da consideração consciente, nenhuma das respostas incluiu minha lógica.
Eu não pretendia ficar na defensiva ao educá-lo. Afinal, nos meus anos mais jovens, eu teria escrito o comando, grep foo file.txt | cut ... | cut ...
porque sempre que você faz os singles frequentes, grep
aprende a colocação do argumento do arquivo e é fácil saber que o primeiro é o padrão e os últimos são os nomes de arquivo.
Foi uma escolha consciente quando respondi à pergunta com o cat
prefixo, em parte por uma razão de "bom gosto" (nas palavras de Linus Torvalds), mas principalmente por uma razão convincente de função.
A última razão é mais importante, por isso vou colocá-la em primeiro lugar. Quando ofereço um pipeline como solução, espero que seja reutilizável. É bem provável que um pipeline seja adicionado no final ou emendado em outro pipeline. Nesse caso, ter um argumento de arquivo para grep aumenta a capacidade de reutilização e, possivelmente, o faz silenciosamente sem uma mensagem de erro se o argumento de arquivo existir. I. e. grep foo xyz | grep bar xyz | wc
fornecerá quantas linhas xyz
contêm bar
enquanto você espera o número de linhas que contêm ambos foo
e bar
. A necessidade de alterar os argumentos para um comando em um pipeline antes de usá-lo é suscetível a erros. Acrescente a isso a possibilidade de falhas silenciosas e isso se torna uma prática particularmente insidiosa.
A razão anterior não é sem importância, já que muito "bom gosto" é meramente uma lógica subconsciente intuitiva para coisas como as falhas silenciosas acima das quais você não consegue pensar exatamente no momento em que uma pessoa que precisa de educação diz "mas não é" aquele gato inútil ".
No entanto, tentarei também conscientizar a razão anterior de "bom gosto" que mencionei. Essa razão tem a ver com o espírito de design ortogonal do Unix. grep
não faz cut
e ls
não faz grep
. Portanto, pelo menos, grep foo file1 file2 file3
vai contra o espírito de design. A maneira ortogonal de fazê-lo é 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 o tratar da mesma maneira, estará usando pelo menos os ciclos do relógio cerebral tentando evitar o prêmio inútil do gato.
Isso nos leva ao argumento que grep foo file1 file2 file3
está concatenando, e cat
concatena, por isso é apropriado, cat file1 file2 file3
mas porque cat
não está concatenando, cat file1 | grep foo
portanto, estamos violando o espírito do cat
Unix e do Todo-Poderoso. Bem, se esse fosse o caso, o Unix precisaria de um comando diferente para ler a saída de um arquivo e cuspi-lo no stdout (não paginá-lo ou qualquer coisa apenas um cuspo puro no stdout). Portanto, você teria a situação em que você diz cat file1 file2
ou diz, dog file1
e lembre-se conscientemente de evitar cat file1
evitar o prêmio, além de evitar, dog file1 file2
pois esperançosamente, o design de dog
geraria um erro se vários arquivos fossem especificados.
Esperamos que, neste momento, você simpatize com os designers do Unix por não incluir um comando separado para cuspir um arquivo no stdout, enquanto também nomeia cat
concatenar, em vez de dar outro nome a ele. <edit>
existe um cão assim, o <
operador infeliz . É uma pena a sua colocação no final do pipeline, impedindo a fácil composição. Não existe uma maneira sintática ou esteticamente limpa de colocá-lo no início. Também é lamentável não ser suficientemente genérico, de modo que você comece com o cão, mas simplesmente adicione outro nome de arquivo, se quiser que ele seja processado após o anterior. (Por >
outro lado, a metade não é tão ruim. Tem um posicionamento quase perfeito no final. Normalmente não é uma parte reutilizável de um pipeline e, portanto, é distinguida simbolicamente.)</edit>
A próxima pergunta é por que é importante ter comandos que apenas cospem um arquivo ou a concatenação de vários arquivos no stdout, sem nenhum processamento adicional? 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 precisem 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 grep
ter uma lógica extra. A lógica é permitir a fluência do usuário para comandos usados com freqüência e de forma independente (e não como um pipeline). É um pequeno comprometimento da ortogonalidade para um ganho significativo na usabilidade. Nem todos os comandos devem ser projetados dessa maneira, e os comandos que não são usados com frequência devem evitar completamente a lógica extra dos argumentos do 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 ls
há um motivo completamente diferente para não apenas aceitar, mas praticamente exigir argumentos de arquivo)
Finalmente, o que poderia ter sido feito melhor é se comandos excepcionais como grep
(mas não necessariamente ls
) geram um erro se a entrada padrão estiver disponível. Isso é razoável porque os comandos incluem lógica que viola o espírito ortogonal do todo-poderoso Unix para conveniência do usuário. Para maior comodidade do usuário, ou seja, para impedir o sofrimento causado por uma falha silenciosa, esses comandos não devem hesitar em violar sua própria violação, alertando o usuário se houver uma possibilidade de falha silenciosa.