Suspeito que o porquê tenha muito a ver com a visão / design que moldou o Unix (e, consequentemente, o Linux), e as vantagens decorrentes dele.
Sem dúvida, há um benefício de desempenho não negligenciável em não gerar um processo extra, mas acho que há mais: o Early Unix tinha uma metáfora "tudo é um arquivo", que tem uma vantagem não óbvia, mas elegante, se você olhar para do ponto de vista do sistema, em vez de uma perspectiva de script de shell.
Digamos que você tenha seu null
programa de linha de comando e /dev/null
o nó do dispositivo. De uma perspectiva de script de shell, o foo | null
programa é realmente realmente útil e conveniente , e foo >/dev/null
demora um pouco mais para digitar e pode parecer estranho.
Mas aqui estão dois exercícios:
Vamos implementar o programa null
usando ferramentas existentes Unix e /dev/null
- fácil: cat >/dev/null
. Feito.
Você pode implementar /dev/null
em termos de null
?
Você está absolutamente certo de que o código C para descartar apenas a entrada é trivial; portanto, talvez ainda não seja óbvio por que é útil ter um arquivo virtual disponível para a tarefa.
Considere: quase todas as linguagens de programação já precisam trabalhar com arquivos, descritores e caminhos de arquivos, porque faziam parte do paradigma "tudo é um arquivo" do Unix desde o início.
Se tudo o que você tem são programas que gravam no stdout, bem, o programa não se importa se você os redirecionar para um arquivo virtual que engole todas as gravações ou um canal para um programa que engole todas as gravações.
Agora, se você possui programas que utilizam caminhos de arquivos para leitura ou gravação de dados (o que a maioria dos programas faz) - e deseja adicionar a funcionalidade "entrada em branco" ou "descartar esta saída" a esses programas - bem, /dev/null
isso é de graça.
Observe que a elegância disso é que reduz a complexidade do código de todos os programas envolvidos - para cada caso de uso comum, mas especial, que o sistema pode fornecer como "arquivo" com um "nome do arquivo" real, seu código pode evitar adicionar comandos personalizados opções de linha e caminhos de código personalizados para manipular.
A boa engenharia de software geralmente depende da descoberta de metáforas boas ou "naturais" para abstrair algum elemento de um problema de uma maneira que se torna mais fácil de pensar, mas permanece flexível , para que você possa resolver basicamente a mesma faixa de problemas de nível superior sem precisar gaste tempo e energia mental reimplementando soluções para os mesmos problemas de nível inferior constantemente.
"Tudo é um arquivo" parece ser uma metáfora para acessar recursos: você chama open
um determinado caminho em um espaço de nome heirárquico, obtendo uma referência (descritor de arquivo) ao objeto e pode read
e write
etc nos descritores de arquivo. Seu stdin / stdout / stderr também são descritores de arquivos que foram pré-abertos para você. Seus pipes são apenas arquivos e descritores de arquivos, e o redirecionamento de arquivos permite colar todas essas peças.
O Unix conseguiu tanto quanto em parte por causa de quão bem essas abstrações funcionaram juntas, e /dev/null
é melhor entendido como parte desse todo.
PS Vale a pena olhar para a versão Unix de "tudo é um arquivo" e coisas como /dev/null
os primeiros passos para uma generalização mais flexível e poderosa da metáfora que foi implementada em muitos sistemas que se seguiram.
Por exemplo, no Unix, objetos especiais semelhantes a arquivos /dev/null
tiveram que ser implementados no próprio kernel, mas acontece que é útil o suficiente para expor a funcionalidade no formato de arquivo / pasta que, desde então, foram criados vários sistemas que possibilitam programas fazer isso.
Um dos primeiros foi o sistema operacional Plan 9, feito por algumas das mesmas pessoas que criaram o Unix. Mais tarde, o GNU Hurd fez algo semelhante com seus "tradutores". Enquanto isso, o Linux acabou recebendo o FUSE (que já se espalhou para outros sistemas convencionais).
cat foo | bar
é por que é muito pior (em escala) do quebar <foo
.cat
é um programa trivial, mas mesmo um programa trivial cria custos (alguns deles específicos da semântica do FIFO - porque os programas não podem serseek()
incluídos no FIFO, por exemplo, um programa que poderia ser implementado com eficiência na busca pode acabar fazendo operações muito mais caras quando recebe um pipeline; com um dispositivo de caractere como/dev/null
ele pode falsificar essas operações ou com um arquivo real, ele pode implementá-las, mas um FIFO não permite nenhum tipo de tratamento com reconhecimento de contexto).