AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 672750
Accepted
Binarus
Binarus
Asked: 2021-10-12 03:56:10 +0800 CST2021-10-12 03:56:10 +0800 CST 2021-10-12 03:56:10 +0800 CST

Lutando para entender o redirecionamento em pipes e subshells: a explicação do código seria muito apreciada

  • 772

Por favor, considere o seguinte log de uma sessão de terminal (Debian Buster, Bash 5.0):

root@cerberus ~/scripts # rm -f result
root@cerberus ~/scripts # { { echo test; } | cat > result; }
root@cerberus ~/scripts # cat result
test
root@cerberus ~/scripts #

Nada de especial aqui, este é o comportamento esperado, e eu o entendo.

Mas não entendo o comportamento no seguinte caso:

root@cerberus ~/scripts # rm -f result
root@cerberus ~/scripts # { { echo test >&3; } | cat > result; } 3>&1
test
root@cerberus ~/scripts # cat result
root@cerberus ~/scripts #

Para ser preciso, acredito que entendo por que "teste" é gerado ao executar a segunda linha, mas não entendo por que nada entra no arquivo de resultado. Meu entendimento do que acontece é o seguinte:

  1. A princípio, fd 3 é configurado como uma duplicata de stdout. Tenho certeza de que isso acontece antes que o pipe seja executado, porque, caso contrário, nenhum dos comandos no pipe teria acesso ao fd 3, o que causaria uma mensagem de erro "bad descriptor".

  2. Um pipeline não é um comando simples, então um subshell é gerado para executá-lo. O subshell herda o ambiente de execução do shell pai, incluindo descritores de arquivo e redirecionamentos. [1]

  3. Cada um dos comandos no pipeline também é executado em seu próprio subshell [2] , novamente herdando o ambiente de execução e os descritores de arquivo. echoA saída de 's é redirecionada para fd 3, que por sua vez foi duplicada de stdoutantes, o que em resumo leva echoa saída de 's a aparecer stdout(a saída vai para fd 3, que vai para fd 1, que é stdout).

  4. Mas não entendo por que echoa saída de 's não entra no arquivo de resultado. Do manual do bash (ênfase minha):

A saída de cada comando no pipeline é conectada por meio de um pipe à entrada do próximo comando. Ou seja, cada comando lê a saída do comando anterior. Essa conexão é executada antes de qualquer redirecionamento especificado pelo comando.

Estou entendendo isso no sentido de que echoa saída de ' deve ser conectada à catentrada de ' antes que o redirecionamento >&3seja configurado ou aplicado, respectivamente. Mas se isso for verdade, o arquivo de resultado existirá (e conterá "teste") depois que o comando for executado. Portanto, meu entendimento está obviamente errado.

Alguém poderia explicar o que estou perdendo?

Atualização, com base na excelente resposta de AB e Gilles abaixo, com mais explicações

A fonte das minhas preocupações é o que escrevi no item 3. acima. Simplesmente não funciona assim; veja também a resposta de Gilles.

AB foi o primeiro a fornecer uma resposta (veja abaixo). No entanto, eu precisava de algum tempo para entendê-lo. Por isso vou explicar algumas passagens para que possam ser compreendidas mais facilmente.

  1. Última parte da linha: 3>&1é feito primeiro: fd 1 apontando para a saída do terminal é duplicado para fd 3. Isso significa que fd 1 e fd 3 agora apontam para a saída do terminal. Eles são idênticos e podem ser usados ​​alternadamente.

  2. Antes de bifurcar, um pipe é criado, normalmente usando a pipe(2)chamada de sistema, nos próximos fds disponíveis: digamos fd 4 e fd 5. O processo de preparação então bifurca em future echo e future cat, executando as seguintes etapas:

    a) O processo de preparação para echo funciona assim:

    fd 5 é duplicado para fd 1 (sobrescrevendo onde fd 1 apontava: a saída do terminal). Isso significa que fd 1 agora é idêntico a fd 5 e que eles podem ser usados ​​alternadamente. Especificamente, fd 1 não aponta mais para a saída do terminal, mas aponta para a extremidade de escrita do pipe.

    Neste estágio (mas veja abaixo), a saída de echoiria para a extremidade de escrita do pipe, porque echoescreve para fd 1, que aponta para essa extremidade de escrita.

    Como não precisamos de dois descritores de arquivo para a mesma coisa, e porque echograva em fd 1 de qualquer maneira, fd 5 é fechado agora.

    Então echoé executado, mas depois de ter configurado o redirecionamento adicional observado por trás dele (ver 3.).

    b) Da mesma forma, o processo de preparação para catduplicatas fd 4 a fd 0, significando que fd 0 não aponta mais para a entrada do terminal, mas aponta para o lado receptor do tubo. Nesse estágio, a entrada para catviria do lado receptor do tubo, porque catlê de fd 0, e fd 0 está conectado a esse lado receptor. Porque não precisamos de dois descritores de arquivo para a mesma coisa, e porque catlê de fd 0 de qualquer maneira, fd 4 é fechado agora. Em seguida, caté executado.

    Enquanto tudo isso acontece, o fd 3 é herdado em todos os lugares.

  3. >&3faz o oposto do marcador 1: ele duplica fd 3 para fd 1. fd 3 foi criado para apontar para a saída do terminal e é herdado pelo subshell que executa o pipe e os subshells adicionais que executam os comandos individuais do pipe.

    Na etapa 2a), fd 1 foi apontado para o lado de escrita do tubo. Mas agora, o redirecionamento >&3sobrescreve fd 1 novamente e o torna igual a fd 3, que por sua vez (ainda) aponta para a saída do terminal. Isso significa que fd 1 não aponta mais para o lado de escrita do pipe, mas para a saída do terminal. Esta é a razão pela qual "test" aparece no terminal quando o pipe é executado (lembre-se que echosempre escreve para fd 1, independentemente de onde fd 1 apontar).

    Além disso, quando fd 1 é "substituído" pelo redirecionamento, sua versão antiga é fechada (porque a chamada de sistema subjacente dup2(2)faz isso). Como sua versão antiga apontava para a extremidade de escrita do tubo, essa extremidade de escrita agora está fechada.

    Por causa disso, a extremidade receptora e, portanto, cat, não receberá nenhum dado. Eles recebem imediatamente uma notificação EOF. Esta é a razão pela qual catnão recebe nada e, consequentemente, o arquivo de resultado permanece vazio ou é truncado.

    [ Nota lateral: eu deveria ter fechado o fd 3 após o redirecionamento (ou seja, deveríamos ter escrito >&3 3>&-em vez de >&3), porqueecho-como mencionado acima- escreve para fd 1 e não sabe nada sobre fd 3. No entanto, essa parte estava faltando no meu exemplo, e eu gostaria de deixar assim para não distrair do problema real). ]

shell pipe
  • 2 2 respostas
  • 185 Views

2 respostas

  • Voted
  1. Best Answer
    A.B
    2021-10-12T05:34:10+08:002021-10-12T05:34:10+08:00

    Isso é por causa do bullet 4 do OP que funciona assim, com o fd herdado ao longo dos vários processos de criação/execução. Eu não estou escrevendo todos os lugares onde o fork/exec acontece. Certamente estou simplificando um pouco (com comandos embutidos...). Links de documentação fornecidos para Linux, mas o mesmo comportamento deve ocorrer em qualquer sistema POSIX ou semelhante a POSIX.

    1. última parte da linha: 3>&1é feito primeiro: fd 1 apontando para o terminal é duplicado como fd 3 (normalmente usando a dup2(2)chamada do sistema).
    2. Antes da bifurcação, um pipe é criado, normalmente usando a pipe(2)chamada do sistema, no próximo fd disponível s disponíveis: digamos 4 e 5. O processo de preparação então bifurca em future echoe future cat. proto-echo dups 5 para 1 ("sobrescrevendo" onde apontava: o terminal), fecha 5 e execs echo, proto-cat dups2() 4 para 0, fecha 4 e execs cat. fd 3 é herdado em todos os lugares.
    3. >&3faz o oposto do marcador 1: duplica o fd 3 (apontando para o terminal) para fd 1. Assim, o lado de escrita do pipe foi substituído e agora está fechado ( dup2(2)diz: "Se o descritor de arquivo newfd foi aberto anteriormente, ele é silenciosamente fechado antes de ser reutilizado."). Nada será gravado no pipe. Terminal recebe teste exibe.
    4. em paralelocat abre e trunca o arquivo de destino resulte inicia a leitura do pipe. Isso aciona um EOF conforme pipe(7)porque o lado da gravação está/foi fechado: cato comando termina.
    5. processo shell principal não tem filho restante: a execução termina

    Resultado:test no terminal e arquivo vazio result.

    • 1
  2. Gilles 'SO- stop being evil'
    2021-10-14T12:36:44+08:002021-10-14T12:36:44+08:00

    1. Inicialmente, fd 3 é configurado como uma duplicata de stdout.

    Isso está correto, como afirmado, mas um pouco estranho, e você parece ter entendido mal o que isso significa. Isso não significa que onde esse redirecionamento está em vigor, escrever em fd 3 é equivalente a escrever em stdout. Isso significa que o fd 3 está conectado a qualquer stdout ao qual esteja conectado no ponto em que o redirecionamento está configurado. Se você estiver executando este código em um terminal, 3>&1conecte o descritor de arquivo 3 ao terminal. E entao…

    3. (...) echoa saída de (…) 's é redirecionada para fd 3, que por sua vez foi duplicada de stdout antes, o que em resumo leva a saída de echo a aparecer em stdout (a saída vai para fd 3, que vai para fd 1, que é stdout ).

    FD 3 é o terminal. O fato de que em algum momento aconteceu também ser fd 1 de algum outro processo é um detalhe histórico irrelevante.

    • 1

relate perguntas

  • Existe uma maneira de fazer ls mostrar arquivos ocultos apenas para determinados diretórios?

  • o que grep -v grep faz

  • Por que canalizar `mysql` para 'tail' altera o formato de saída?

  • Como salvar um caminho com ~ em uma variável?

  • ordem de substituição de processos `te` e `bash`

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve