Em que ordem os comandos canalizados são executados?

89

Eu nunca pensei sobre como o shell realmente executa comandos canalizados. Sempre me disseram que o "stdout de um programa é canalizado para o stdin de outro", como uma maneira de pensar sobre os pipes. Então, naturalmente, pensei que, no caso de dizer, A | B, A seria executado primeiro, depois B obtém o desvio padrão de A e usa o desvio padrão de A como entrada.

Mas notei que quando as pessoas pesquisam um processo específico no ps, elas incluem grep -v "grep" no final do comando para garantir que o grep não apareça na saída final. Isso significa que no comando ps aux | grep "bash" | grep -v "grep", o que significa que o ps sabia que o grep estava em execução e, portanto, está na saída do ps. Mas se o ps terminar de executar antes que sua saída seja canalizada para grep, como ele sabia que o grep estava em execução?

flamingtoast@FTOAST-UBUNTU: ~$ ps | grep ".*"
PID TTY          TIME CMD
3773 pts/0    00:00:00 bash
3784 pts/0    00:00:00 ps
3785 pts/0    00:00:00 grep
action_potato
fonte
por que não aceitar uma resposta?
törzsmókus

Respostas:

64

Os comandos canalizados são executados simultaneamente. Quando você corre ps | grep …, é a sorte do empate (ou uma questão de detalhes do funcionamento do shell, combinada com o agendador que afina profundamente as entranhas do kernel) sobre se deve psou grepcomeça primeiro e, em qualquer caso, eles continuam a executar simultaneamente.

Isso é muito comumente usado para permitir que o segundo programa processe dados à medida que saem do primeiro programa, antes que o primeiro programa tenha concluído sua operação. Por exemplo

grep pattern very-large-file | tr a-z A-Z

começa a exibir as linhas correspondentes em maiúsculas mesmo antes de grepterminar de atravessar o arquivo grande.

grep pattern very-large-file | head -n 1

exibe a primeira linha correspondente e pode parar o processamento bem antes de grepterminar de ler seu arquivo de entrada.

Se você ler em algum lugar que os programas canalizados sejam executados em sequência, fuja deste documento. Programas canalizados são executados simultaneamente e sempre foram executados.

Gilles
fonte
7
E o legal desse exemplo é que, quando o head obtém a linha de que precisa, ele termina e quando o grep percebe isso, ele também termina sem fazer muito trabalho adicional por nada.
5133 Joe
Eu acho que existe algum tipo de buffer IO referente ao pipe ... como eu sei o tamanho em bytes? O que eu quero ler para saber mais sobre isso? :)
n611x007
3
@ Naxa Na verdade, existem dois buffers. Existe o buffer stdio dentro do grepprograma e existe um buffer gerenciado pelo kernel no próprio pipe. Para o último, consulte Qual é o tamanho do buffer do pipe?
Gilles
49

A ordem em que os comandos são executados não importa e não é garantida. Deixando de lado os detalhes secretos de pipe(), fork(), dup()e execve(), a concha primeiro cria o tubo, a conduta para o fluxo de dados que vai entre os processos, e, em seguida, cria os processos com as extremidades do tubo ligado a eles. O primeiro processo executado pode bloquear a espera pela entrada do segundo processo ou bloquear a espera pelo segundo processo para começar a ler dados do canal. Essas esperas podem ser arbitrariamente longas e não importam. Qualquer que seja a ordem em que os processos são executados, os dados são transferidos e tudo funciona.

Kyle Jones
fonte
5
Boa resposta, mas o OP parece pensar que os processos são executados seqüencialmente. Você pode deixar mais claro aqui que os processos são executados simultaneamente e o cano é como ... um cano entre baldes, onde a água flui através do mesmo tempo (aprox.).
28412 Keith
Obrigado pelo esclarecimento. As fontes que eu estava lendo faziam parecer que os programas canalizados eram executados sequencialmente, e não simultaneamente.
action_potato
Para ver a experiência dos processos iniciados de forma indeterminada, tente executar isso 1000 vezes: echo -na> & 2 | echo b> & 2
Ole Tange
28

Correndo o risco de bater em um cavalo morto, o equívoco parece ser que

    Um | B

é equivalente a

    Um > temporary_file 
    B < temporary_file 
    rm temporary_file

Porém, quando o Unix foi criado e as crianças levavam dinossauros para a escola, os discos eram muito pequenos e era comum um comando bastante benigno consumir todo o espaço livre em um sistema de arquivos. Se Bfosse algo assim , a saída final do pipeline poderia ser muito menor que o arquivo intermediário. Portanto, o tubo foi desenvolvido, não como uma abreviação para a “executar Um primeiro, e em seguida, executar B com a entrada de um ‘s saída”modelo, mas como uma forma para executar concorrentemente com e eliminar a necessidade de armazenar o ficheiro intermédio no disco.grep some_very_obscure_stringBA

Scott
fonte
2
Isso responde por que e, portanto, recebe meu voto.
Little Ancient Forest Kami
1

Normalmente, você executa isso no bash. processo funcionando e iniciando simultaneamente, mas está sendo executado pelo shell em paralelo. Como isso é possível?

  1. se não for o último comando no pipe, crie um pipe sem nome com um par de soquetes
  2. garfo
  3. na criança reatribuir stdin / stdout para soquetes, se necessário (para o primeiro processo no pipe stdin não é reatribuído, o mesmo para o último processo e seu stdout)
  4. no comando EXEC filho especificado com argumentos que varrem o código do shell original, mas deixam todos abertos por eles soquetes. O ID do processo filho não será alterado porque este é o mesmo processo filho
  5. simultaneamente com o filho, mas paralelo no shell principal, vá para a etapa 1.

O sistema não garante a rapidez com que o exec será executado e o comando especificado será iniciado. é independente do shell, mas do sistema. Isto é porque:

ps auxww| grep ps | cat

uma vez mostre grepe / ou pscomando, e depois agora. Depende de quão rápido o kernel realmente inicia os processos usando a função exec do sistema.

Znik
fonte
11
Execução simultânea significa que dois ou mais processos são executados no mesmo período, geralmente com algum tipo de dependência entre eles. Execução paralela significa que dois ou mais processos são executados simultaneamente (por exemplo, em núcleos de CPU separados ao mesmo tempo). O paralelismo não é relevante para a questão, nem "quão rápido" exec()é executado, mas como as exec()chamadas e a execução dos programas em um canal são intercaladas .
Thomas Nyman