Por que alguns comandos não leem da entrada padrão?

19

Eu me pergunto o que quando devemos usar pipeline e quando não devemos.

Digamos, por exemplo, para eliminar um determinado processo que manipula arquivos PDF, o seguinte não funcionará usando o pipeline:

ps aux | grep pdf | awk '{print $2}'|kill

Em vez disso, só podemos fazer isso das seguintes maneiras:

kill $(ps aux| grep pdf| awk '{print $2}')

ou

ps aux | grep pdf | awk '{print $2}'| xargs kill

De acordo com man bash(versão 4.1.2):

The standard output of command is connected via a pipe to the standard input of command2.

Para o cenário acima:

  • o stdin de grepé o stdout de ps. Isso funciona.
  • o stdin de awké o stdout de grep. Isso funciona.
  • o stdin de killé o stdout de awk. Isso não funciona.

O stdin do comando a seguir está sempre recebendo entrada do stdout do comando anterior.

  • Por que não funciona com killou rm?
  • Qual é a diferença entre kill, rmentrada com grep, awkentrada?
  • Existem regras?
sylye
fonte
11
Esta não é uma resposta, mas você pode querer ter um olhar para os pgrep, pkille killallcomandos.
terdon
2
@terdon: Eu só uso acima cenário para mostrar a questão gasoduto, eu entendo que pgrepeo resto pode conseguir isso perfeitamente :)
sylye

Respostas:

17

Existem duas maneiras comuns de fornecer entradas para os programas:

  • fornecer dados ao STDIN dos processos
  • especificar argumentos de linha de comando

killusa apenas argumentos de linha de comando. Não lê de STDIN. Programas como grepe awklidos no STDIN (se nenhum nome de arquivo for fornecido como argumento de linha de comando) e processam os dados de acordo com seus argumentos de linha de comando (padrão, instruções, sinalizadores, ...).

Você pode canalizar apenas para STDIN de outros processos, não para argumentos de linha de comando.

A regra comum é que os programas usam o STDIN para processar uma quantidade arbitrária de dados. Todos os parâmetros de entrada extras ou, se geralmente houver apenas alguns, são passados ​​pelos argumentos da linha de comando. Se a linha de comando puder ficar muito longa, por exemplo, para awktextos longos de programas, geralmente há a possibilidade de lê-los em arquivos de programa extras ( -fopção de awk).

Para usar o STDOUT de programas como argumentos de linha de comando, use $(...)ou no caso de muitos dados xargs. findtambém pode isso diretamente com -exec ... {} +.

Para completar: Para escrever argumentos de linha de comando em STDOUT, use echo.

jofel
fonte
11
Como sabemos que um comando aceita apenas argumentos, mas não STDIN? Existe uma maneira sistemática ou programática em vez de adivinhar ou ler a partir da página de manual? Ao ler apenas a página de manual, não consegui encontrar pistas específicas sobre se o comando pode ou não aceitar STDIN, pois STDIN também faz parte dos argumentos da maneira como uma página de manual é apresentada. Por exemplo, gzipem SYNOPSIS, ele não disse que deveria receber um FILENAME como entrada. Eu estou procurando há uma maneira mais sistemática de determinar isso.
sylye
Há também o argumento "-" que significa "stdin" (ou "stdout") para alguns comandos.
Emmanuel
Não xargspermitirá com precisão "canalizar para argumentos da linha de comando"?
29230
@ T.Verron sim, esta é a tarefa de xargs. Ele chama o comando se necessário mais de uma vez (o tamanho da linha de comando é limitado) e possui muitas outras opções.
Jofel
2
O texto da descrição descreverá como você pode usar o programa. Por exemplo, o gzip diz: "O programa gzip compacta e descompacta arquivos usando a codificação Lempel-Ziv (LZ77). Se nenhum arquivo for especificado, o gzip será compactado da entrada padrão ou descompactado para a saída padrão". Se uma página de manual não mencionar a entrada padrão, ela não será usada.
Alan Shutko
16

Essa é uma pergunta interessante e lida com uma parte da filosofia Unix / Linux.

Então, qual é a diferença entre programas como grep, sed, sortpor um lado e kill, rm, lspor outro lado? Eu vejo dois aspectos.

O aspecto do filtro

  • O primeiro tipo de programas também é chamado de filtros . Eles recebem uma entrada, de um arquivo ou de STDIN, modificam-na e geram alguma saída, principalmente para STDOUT. Eles devem ser usados ​​em um canal com outros programas como fontes e destinos.

  • O segundo tipo de programas atua em uma entrada, mas a saída que eles fornecem geralmente não está relacionada à entrada. killnão tem saída quando trabalha regularmente, nem faz ls. Os apenas têm um valor de retorno para mostrar sucesso. Normalmente, eles não recebem entrada de STDIN, mas geralmente fornecem saída para STDOUT.

Para programas como lso aspecto do filtro não funciona tão bem. Certamente pode ter uma entrada (mas não precisa de uma) e a saída está intimamente relacionada a essa entrada, mas não funciona como um filtro. No entanto, para esse tipo de programa, o outro aspecto ainda funciona:

O aspecto semântico

  • Para filtros, sua entrada não tem significado semântico . Eles apenas lêem dados, modificam dados, produzem dados. Não importa se esta é uma lista de valores numéricos, alguns nomes de arquivos ou código fonte HTML. O significado desses dados é fornecido apenas pelo código que você fornece ao filtro: a regex para grep, as regras para awkou o programa Perl.

  • Para outros programas, como killou ls, sua entrada tem um significado , uma denotação . killespera números de processo, lsespera nomes de arquivos ou caminhos. Eles não podem manipular dados arbitrários e não devem. Muitos deles nem precisam de nenhuma entrada ou parâmetro, como ps. Eles normalmente não leem do STDIN.

Provavelmente, pode-se combinar esses dois aspectos: Um filtro é um programa cuja entrada não tem um significado semântico para o programa.

Tenho certeza de que li sobre essa filosofia em algum lugar, mas não me lembro de nenhuma fonte no momento, desculpe. Se alguém tiver algumas fontes presentes, sinta-se à vontade para editar.

Dubu
fonte
5

Não existem "regras" como tais. Alguns programas recebem entrada do STDIN e outros não. Se um programa pode receber informações do STDIN, ele pode ser canalizado para, se não, não pode.

Normalmente, você pode dizer se um programa irá receber informações ou não, pensando no que faz. Se o trabalho do programa é de alguma forma manipular os conteúdos de um arquivo (por exemplo grep, sed, awketc.), que normalmente leva a entrada de STDIN. Se seu trabalho é manipular o arquivo em si (por exemplo mv, rm, cp) ou um processo (por exemplo kill, lsof) ou às informações de retorno sobre algo (por exemplo top, find, ps), então isso não acontece.

Outra maneira de pensar sobre isso é a diferença entre argumentos e entrada. Por exemplo:

mv foo bar

No comando acima, mvnão tem entrada como tal. O que foi dado são dois argumentos. Ele não sabe nem se importa com o que está em nenhum dos arquivos, apenas sabe que esses são seus argumentos e deve manipulá-los.

Por outro lado

sed -e 's/foo/bar/' < file
--- -- ------------   ----
 |   |       |          |-> input
 |   |       |------------> argument        
 |   |--------------------> option/flag/switch
 |------------------------> command

Aqui, sedfoi dada entrada, bem como um argumento. Uma vez que recebe entrada, ela pode ser lida no STDIN e pode ser canalizada para.

Fica mais complicado quando um argumento pode ser a entrada. Por exemplo

cat file

Aqui fileestá o argumento que foi dado cat. Para ser mais preciso, o nome do arquivo fileé o argumento. No entanto, como caté um programa que manipula o conteúdo dos arquivos, sua entrada é o que estiver dentro file.

Isso pode ser ilustrado usando straceum programa que rastreia as chamadas do sistema feitas por processos. Se cat foorodarmos strace, podemos ver que o arquivo fooé aberto:

$ strace cat foo 2| grep foo
execve("/bin/cat", ["cat", "foo"], [/* 44 vars */]) = 0
open("foo", O_RDONLY)     

A primeira linha acima mostra que o programa /bin/catfoi chamado e seus argumentos foram cate foo(o primeiro argumento é sempre o próprio programa). Posteriormente, o argumento foofoi aberto no modo somente leitura. Agora, compare isso com

$ strace ls foo 2| grep foo 
execve("/bin/ls", ["ls", "foo"], [/* 44 vars */]) = 0
stat("foo", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat("foo", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
write(1, "foo\n", 4foo

Aqui também, lstomou-se e foocomo argumentos. No entanto, não há openchamada, o argumento não é tratado como entrada. Em vez disso, lschama a statbiblioteca do sistema (que não é a mesma coisa que o statcomando) para obter informações sobre o arquivo foo.

Em resumo, se o comando que você está executando ler sua entrada, você poderá canalizar para ele; se não, não poderá.

terdon
fonte
0
  • Por que não funciona com kill ou rm?

kille rmnão precisa de STDIN.

  • Qual é a diferença entre kill, entrada rm e grep, awk?

Para kille rm, os usuários fornecem suas informações personalizadas como argumento e $(cmd)ajudam a obter o STDOUT cmde a convertê-lo no argumento de informações.

Para grepe awk, os usuários fornecem argumentos e, além disso, também STDINou um arquivo regular que será processado pelo comando. STDINpode ser passado com pipeline |ou inserindo manualmente.

  • Existem regras?

Leia o manual ou os códigos-fonte. E se você não encontrar o que precisa, poderá fazer um teste simples, mas talvez perigoso:

Basta digitar o comando que lhe interessa, com argumentos que você já entendeu, e ver se o comando pausa (nada acontece). Se ele parar, ele está realmente à espera de STDIN (você pode tentar cate echover o diferente). Você digita manualmente Ctrl-De o comando prossegue (mostra resultados ou erros) e retorna. Esse comando precisa de STDIN nessa situação (com argumentos que você fornece).

O mesmo comando pode não precisar de STDIN em diferentes situações (por exemplo, cataguarda STDIN, mas cat file.txtnão).

Alex Huang
fonte