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 / 476080
Accepted
Alexander Mills
Alexander Mills
Asked: 2018-10-18 09:36:46 +0800 CST2018-10-18 09:36:46 +0800 CST 2018-10-18 09:36:46 +0800 CST

O que impede stdout/stderr de intercalar?

  • 772

Digamos que eu execute alguns processos:

#!/usr/bin/env bash

foo &
bar &
baz &

wait;

Eu corro o script acima assim:

foobarbaz | cat

até onde posso dizer, quando qualquer um dos processos grava em stdout/stderr, sua saída nunca é intercalada - cada linha de stdio parece ser atômica. Como isso funciona? Qual utilitário controla como cada linha é atômica?

shell osx
  • 2 2 respostas
  • 3715 Views

2 respostas

  • Voted
  1. Best Answer
    Gilles 'SO- stop being evil'
    2018-10-18T10:25:22+08:002018-10-18T10:25:22+08:00

    Eles intercalam! Você apenas tentou rajadas de saída curtas, que permanecem sem divisão, mas na prática é difícil garantir que qualquer saída em particular permaneça sem divisão.

    Buffer de saída

    Depende de como os programas armazenam sua saída. A biblioteca stdio que a maioria dos programas usa ao escrever usa buffers para tornar a saída mais eficiente. Em vez de emitir dados assim que o programa chama uma função de biblioteca para gravar em um arquivo, a função armazena esses dados em um buffer e só produz os dados quando o buffer estiver cheio. Isso significa que a saída é feita em lotes. Mais precisamente, existem três modos de saída:

    • Sem buffer: os dados são gravados imediatamente, sem usar um buffer. Isso pode ser lento se o programa escrever sua saída em pequenos pedaços, por exemplo, caractere por caractere. Este é o modo padrão para erro padrão.
    • Fully buffered: os dados só são escritos quando o buffer está cheio. Este é o modo padrão ao gravar em um pipe ou em um arquivo normal, exceto com stderr.
    • Buffer de linha: os dados são gravados após cada nova linha ou quando o buffer está cheio. Este é o modo padrão ao gravar em um terminal, exceto com stderr.

    Os programas podem reprogramar cada arquivo para se comportar de maneira diferente e podem liberar explicitamente o buffer. O buffer é liberado automaticamente quando um programa fecha o arquivo ou sai normalmente.

    Se todos os programas que estão gravando no mesmo pipe usam o modo de buffer de linha ou usam o modo sem buffer e escrevem cada linha com uma única chamada para uma função de saída, e se as linhas são curtas o suficiente para escrever em um único bloco, então a saída será uma intercalação de linhas inteiras. Mas se um dos programas usar o modo totalmente em buffer, ou se as linhas forem muito longas, você verá linhas mistas.

    Aqui está um exemplo onde eu intercalo a saída de dois programas. Eu usei GNU coreutils no Linux; versões diferentes desses utilitários podem se comportar de maneira diferente.

    • yes aaaaescreve aaaapara sempre no que é essencialmente equivalente ao modo de buffer de linha. Na yesverdade, o utilitário grava várias linhas ao mesmo tempo, mas cada vez que emite uma saída, a saída é um número inteiro de linhas.
    • echo bbbb; done | grep bgrava bbbbpara sempre no modo totalmente em buffer. Ele usa um tamanho de buffer de 8192 e cada linha tem 5 bytes de comprimento. Como 5 não divide 8192, os limites entre gravações não estão em um limite de linha em geral.

    Vamos lançá-los juntos.

    $ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
    bbaaaa
    bbbbaaaa
    baaaa
    bbbaaaa
    bbaaaa
    bbbaaaa
    ab
    bbbbaaa
    

    Como você pode ver, sim, às vezes, interrompeu o grep e vice-versa. Apenas cerca de 0,001% das linhas foram interrompidas, mas aconteceu. A saída é aleatória para que o número de interrupções varie, mas eu vi pelo menos algumas interrupções todas as vezes. Haveria uma fração maior de linhas interrompidas se as linhas fossem mais longas, pois a probabilidade de interrupção aumenta à medida que o número de linhas por buffer diminui.

    Existem várias maneiras de ajustar o buffer de saída . Os principais são:

    • Desative o buffer em programas que usam a biblioteca stdio sem alterar suas configurações padrão com o programa stdbuf -o0encontrado no GNU coreutils e alguns outros sistemas como o FreeBSD. Você pode alternar para o buffer de linha com stdbuf -oL.
    • Mude para o buffer de linha direcionando a saída do programa através de um terminal criado apenas para esse propósito com unbuffer. Alguns programas podem se comportar de maneira diferente de outras maneiras, por exemplo, grepusa cores por padrão se sua saída for um terminal.
    • Configure o programa, por exemplo, passando --line-bufferedpara GNU grep.

    Vamos ver o trecho acima novamente, desta vez com buffer de linha em ambos os lados.

    { stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    abbbb
    

    Portanto, desta vez o sim nunca interrompeu o grep, mas o grep às vezes interrompeu o sim. Voltarei ao porquê mais tarde.

    Intercalação de tubos

    Contanto que cada programa produza uma linha de cada vez e as linhas sejam curtas o suficiente, as linhas de saída serão perfeitamente separadas. Mas há um limite de quanto tempo as linhas podem ser para que isso funcione. O próprio pipe tem um buffer de transferência. Quando um programa é enviado para um pipe, os dados são copiados do programa gravador para o buffer de transferência do pipe e, posteriormente, do buffer de transferência do pipe para o programa leitor. (Pelo menos conceitualmente — o kernel às vezes pode otimizar isso para uma única cópia.)

    Se houver mais dados para copiar do que cabe no buffer de transferência do pipe, o kernel copia um bufferful de cada vez. Se vários programas estiverem gravando no mesmo pipe e o primeiro programa que o kernel escolher quiser gravar mais de um bufferful, não há garantia de que o kernel escolherá o mesmo programa novamente na segunda vez. Por exemplo, se P é o tamanho do buffer, fooquer escrever 2* P bytes e barquer escrever 3 bytes, então uma possível intercalação é P bytes de foo, então 3 bytes de bar, e P bytes de foo.

    Voltando ao exemplo yes+grep acima, no meu sistema, yes aaaaacontece de escrever quantas linhas cabem em um buffer de 8192 bytes de uma só vez. Como há 5 bytes para escrever (4 caracteres imprimíveis e a nova linha), isso significa que ele grava 8190 bytes todas as vezes. O tamanho do buffer de pipe é 4096 bytes. Portanto, é possível obter 4096 bytes de yes, depois alguma saída de grep e, em seguida, o restante da gravação de yes (8190 - 4096 = 4094 bytes). 4096 bytes deixa espaço para 819 linhas com aaaaum único arquivo a. Daí uma linha com este lone aseguido por uma escrita de grep, dando uma linha com abbbb.

    Se você quiser ver os detalhes do que está acontecendo, getconf PIPE_BUF .ele informará o tamanho do buffer do pipe em seu sistema e você poderá ver uma lista completa de chamadas de sistema feitas por cada programa com

    strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
    

    Como garantir um entrelaçamento de linha limpo

    Se os comprimentos de linha forem menores que o tamanho do buffer de tubulação, o buffer de linha garante que não haverá nenhuma linha mista na saída.

    Se os comprimentos de linha puderem ser maiores, não há como evitar a mistura arbitrária quando vários programas estão gravando no mesmo pipe. Para garantir a separação, você precisa fazer com que cada programa grave em um pipe diferente e usar um programa para combinar as linhas. Por exemplo , o GNU Parallel faz isso por padrão.

    • 30
  2. Ole Tange
    2018-10-19T23:45:38+08:002018-10-19T23:45:38+08:00

    http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P deu uma olhada nisso:

    O GNU xargs suporta a execução de vários trabalhos em paralelo. -P n onde n é o número de tarefas a serem executadas em paralelo.

    seq 100 | xargs -n1 -P10 echo "$a" | grep 5
    seq 100 | xargs -n1 -P10 echo "$a" > myoutput.txt
    

    Isso funcionará bem para muitas situações, mas tem uma falha enganosa: se $a contiver mais de ~ 1.000 caracteres, o eco pode não ser atômico (pode ser dividido em várias chamadas write()), e há o risco de que duas linhas será misturado.

    $ perl -e 'print "a"x2000, "\n"' > foo
    $ strace -e write bash -c 'read -r foo < foo; echo "$foo"' >/dev/null
    write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 1008) = 1008
    write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 993) = 993
    +++ exited with 0 +++
    

    Obviamente, o mesmo problema surge se houver várias chamadas para echo ou printf:

    slowprint() {
      printf 'Start-%s ' "$1"
      sleep "$1"
      printf '%s-End\n' "$1"
    }
    export -f slowprint
    seq 10 | xargs -n1 -I {} -P4 bash -c "slowprint {}"
    # Compare to no parallelization
    seq 10 | xargs -n1 -I {} bash -c "slowprint {}"
    # Be sure to see the warnings in the next Pitfall!
    

    As saídas dos trabalhos paralelos são misturadas, porque cada trabalho consiste em duas (ou mais) chamadas write() separadas.

    Se você precisar que as saídas não sejam misturadas, é recomendável usar uma ferramenta que garanta que a saída seja serializada (como GNU Parallel).

    • 1

relate perguntas

  • Como funciona este comando? mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc -l 1234 > /tmp/f

  • FreeBSD's sh: funções de lista

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

  • o que grep -v grep faz

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

Sidebar

Stats

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

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

    • 4 respostas
  • Marko Smith

    ssh Não é possível negociar: "nenhuma cifra correspondente encontrada", está rejeitando o cbc

    • 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

    Como descarregar o módulo do kernel 'nvidia-drm'?

    • 13 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
    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
    Wong Jia Hau ssh-add retorna com: "Erro ao conectar ao agente: nenhum arquivo ou diretório" 2018-08-24 23:28:13 +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
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +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