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 / 409893
Accepted
karlosss
karlosss
Asked: 2017-12-10 03:20:09 +0800 CST2017-12-10 03:20:09 +0800 CST 2017-12-10 03:20:09 +0800 CST

Como fazer com que a leitura e gravação do mesmo arquivo no mesmo pipeline sempre "falha"?

  • 772

Digamos que eu tenha o seguinte script:

#!/bin/bash
for i in $(seq 1000)
do
    cp /etc/passwd tmp
    cat tmp | head -1 | head -1 | head -1 > tmp  #this is the key line
    cat tmp
done

Na linha chave, leio e escrevo o mesmo arquivo tmpque às vezes falha.

(Eu li que é por causa das condições de corrida porque os processos no pipeline são executados em paralelo, o que não entendo por que - cada um headprecisa pegar os dados do anterior, não é? Essa NÃO é minha pergunta principal, mas você também pode responder.)

Quando executo o script, ele gera cerca de 200 linhas. Existe alguma maneira de forçar esse script a produzir sempre 0 linhas (para que o redirecionamento de E/S tmpseja sempre preparado primeiro e os dados sejam sempre destruídos)? Para ser claro, quero dizer alterar as configurações do sistema, não este script.

Obrigado por suas idéias.

bash process
  • 2 2 respostas
  • 4866 Views

2 respostas

  • Voted
  1. Gilles 'SO- stop being evil'
    2017-12-10T04:25:49+08:002017-12-10T04:25:49+08:00

    Por que existe uma condição de corrida

    Os dois lados de um tubo são executados em paralelo, não um após o outro. Existe uma maneira muito simples de demonstrar isso: execute

    time sleep 1 | sleep 1
    

    Isso leva um segundo, não dois.

    O shell inicia dois processos filhos e espera que ambos sejam concluídos. Esses dois processos são executados em paralelo: a única razão pela qual um deles sincronizaria com o outro é quando ele precisa esperar pelo outro. O ponto mais comum de sincronização é quando o lado direito bloqueia a espera de dados para ler em sua entrada padrão e é desbloqueado quando o lado esquerdo grava mais dados. O inverso também pode acontecer, quando o lado direito é lento para ler os dados e o lado esquerdo bloqueia em sua operação de escrita até que o lado direito leia mais dados (existe um buffer no próprio pipe, gerenciado pelo kernel, mas tem um tamanho máximo pequeno).

    Para observar um ponto de sincronização, observe os seguintes comandos ( sh -ximprime cada comando à medida que o executa):

    time sh -x -c '{ sleep 1; echo a; } | { cat; }'
    time sh -x -c '{ echo a; sleep 1; } | { cat; }'
    time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
    time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'
    

    Brinque com as variações até se sentir confortável com o que observa.

    Dado o comando composto

    cat tmp | head -1 > tmp
    

    o processo da esquerda faz o seguinte (listei apenas as etapas relevantes para minha explicação):

    1. Execute o programa externo catcom o argumento tmp.
    2. Aberto tmppara leitura.
    3. Embora não tenha chegado ao final do arquivo, leia um trecho do arquivo e grave-o na saída padrão.

    O processo da mão direita faz o seguinte:

    1. Redirecione a saída padrão para tmp, truncando o arquivo no processo.
    2. Execute o programa externo headcom o argumento -1.
    3. Leia uma linha da entrada padrão e grave-a na saída padrão.

    O único ponto de sincronização é que o 3 direito espera que o 3 esquerdo tenha processado uma linha completa. Não há sincronização entre left-2 e right-1, então eles podem acontecer em qualquer ordem. A ordem em que eles acontecem não é previsível: depende da arquitetura da CPU, do shell, do kernel, em quais núcleos os processos são agendados, de quais interrupções a CPU recebe naquele momento, etc.

    Como mudar o comportamento

    Você não pode alterar o comportamento alterando uma configuração do sistema. O computador faz o que você manda. Você disse para truncar tmpe ler tmpem paralelo, então ele faz as duas coisas em paralelo.

    Ok, há uma “configuração do sistema” que você pode alterar: você pode substituir /bin/bashpor um programa diferente que não seja o bash. Espero que seja desnecessário dizer que esta não é uma boa ideia.

    Se você deseja que o truncamento ocorra antes do lado esquerdo do tubo, é necessário colocá-lo fora do pipeline, por exemplo:

    { cat tmp | head -1; } >tmp
    

    ou

    ( exec >tmp; cat tmp | head -1 )
    

    Eu não tenho ideia de por que você iria querer isso. Qual é o sentido de ler um arquivo que você sabe que está vazio?

    Por outro lado, se você deseja que o redirecionamento de saída (incluindo o truncamento) ocorra após cato término da leitura, é necessário armazenar totalmente os dados na memória, por exemplo

    line=$(cat tmp | head -1)
    printf %s "$line" >tmp
    

    ou grave em um arquivo diferente e mova-o para o lugar. Geralmente, essa é a maneira robusta de fazer as coisas em scripts e tem a vantagem de que o arquivo é gravado por completo antes de ficar visível pelo nome original.

    cat tmp | head -1 >new && mv new tmp
    

    A coleção moreutils inclui um programa que faz exatamente isso, chamado sponge.

    cat tmp | head -1 | sponge tmp
    

    Como detectar o problema automaticamente

    Se seu objetivo era pegar scripts mal escritos e descobrir automaticamente onde eles quebram, desculpe, a vida não é tão simples. A análise de tempo de execução não encontrará o problema de forma confiável porque, às vezes cat, termina a leitura antes que o truncamento aconteça. A análise estática pode, em princípio, fazê-lo; o exemplo simplificado em sua pergunta é capturado por Shellcheck , mas pode não detectar um problema semelhante em um script mais complexo.

    • 23
  2. Best Answer
    Peter Cordes
    2017-12-10T06:45:13+08:002017-12-10T06:45:13+08:00

    A resposta de Gilles explica a condição de corrida. Só vou responder a esta parte:

    Existe alguma maneira de forçar esse script a produzir sempre 0 linhas (para que o redirecionamento de E/S para tmp seja sempre preparado primeiro e os dados sejam sempre destruídos)? Para ser claro, quero dizer alterar as configurações do sistema

    IDK se já existe uma ferramenta para isso, mas tenho uma ideia de como poderia ser implementada. (Mas observe que isso não seria sempre 0 linhas, apenas um testador útil que captura corridas simples como essa facilmente e algumas corridas mais complicadas. Veja o comentário de @Gilles .) Não garantiria que um script fosse seguro , mas poderia ser uma ferramenta útil em testes, semelhante ao teste de um programa multi-threaded em CPUs diferentes, incluindo CPUs não-x86 fracamente ordenadas como ARM.

    Você o executaria comoracechecker bash foo.sh

    Use os mesmos recursos de rastreamento/interceptação de chamada do sistema que strace -fe ltrace -fuse para anexar a cada processo filho. (No Linux, esta é a mesma ptracechamada de sistema usada pelo GDB e outros depuradores para definir pontos de interrupção, etapa única e modificar memória/registros de outro processo.)

    Instrumentar as chamadas de sistema opene openat: quando qualquer processo em execução sob esta ferramenta fizer uma chamada open(2)de sistema (ou openat) com O_RDONLY, durma por talvez 1/2 ou 1 segundo. Deixe outras openchamadas do sistema (especialmente aquelas incluindo O_TRUNC) serem executadas sem demora.

    Isso deve permitir que o gravador vença a corrida em quase todas as condições de corrida, a menos que a carga do sistema também seja alta ou seja uma condição de corrida complicada em que o truncamento não aconteceu até depois de alguma outra leitura. Portanto, a variação aleatória de quais open()s (e talvez read()s ou gravações) estão atrasadas aumentaria o poder de detecção dessa ferramenta, mas é claro sem testar por uma quantidade infinita de tempo com um simulador de atraso que eventualmente cobrirá todas as situações possíveis que você pode encontrar em o mundo real, você não pode ter certeza de que seus scripts estão livres de corridas, a menos que você os leia cuidadosamente e prove que não estão.


    Você provavelmente precisaria dele para colocar na lista de permissões (não atrasar open) os arquivos /usr/bine /usr/lib, portanto, a inicialização do processo não demoraria para sempre. (A vinculação dinâmica em tempo de execução tem open()vários arquivos (veja strace -eopen /bin/trueou em /bin/lsalgum momento), embora se o próprio shell pai estiver fazendo o truncamento, tudo bem. Mas ainda será bom para esta ferramenta não tornar os scripts excessivamente lentos).

    Ou talvez coloque na lista de permissões todos os arquivos que o processo de chamada não tem permissão para truncar em primeiro lugar. ou seja, o processo de rastreamento pode fazer uma access(2)chamada de sistema antes de realmente suspender o processo que deseja open()um arquivo.


    racecheckerem si teria que ser escrito em C, não em shell, mas talvez pudesse usar straceo código de como ponto de partida e não desse muito trabalho para implementar.

    Talvez você possa obter a mesma funcionalidade com um sistema de arquivos FUSE . Provavelmente, há um exemplo FUSE de um sistema de arquivos de passagem puro, para que você possa adicionar verificações à open()função que o tornam suspenso para aberturas somente leitura, mas permitem que o truncamento aconteça imediatamente.

    • 2

relate perguntas

  • exportar variáveis ​​​​env programaticamente, via stdout do comando [duplicado]

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

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

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

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

    Matriz JSON para bash variáveis ​​usando jq

    • 4 respostas
  • Marko Smith

    A data pode formatar a hora atual para o fuso horário GMT? [duplicado]

    • 2 respostas
  • Marko Smith

    bash + lê variáveis ​​e valores do arquivo pelo script bash

    • 4 respostas
  • Marko Smith

    Como posso copiar um diretório e renomeá-lo no mesmo comando?

    • 4 respostas
  • Marko Smith

    conexão ssh. Conexão X11 rejeitada devido a autenticação incorreta

    • 3 respostas
  • Marko Smith

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

    • 7 respostas
  • Marko Smith

    comando systemctl não funciona no RHEL 6

    • 3 respostas
  • Marko Smith

    rsync porta 22 e 873 uso

    • 2 respostas
  • Marko Smith

    snap /dev/loop em 100% de utilização -- sem espaço livre

    • 1 respostas
  • Marko Smith

    chave de impressão jq e valor para todos no subobjeto

    • 2 respostas
  • Martin Hope
    EHerman Matriz JSON para bash variáveis ​​usando jq 2017-12-31 14:50:58 +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
    Drux A data pode formatar a hora atual para o fuso horário GMT? [duplicado] 2017-12-26 11:35:07 +0800 CST
  • Martin Hope
    AllisonC Como posso copiar um diretório e renomeá-lo no mesmo comando? 2017-12-22 05:28:06 +0800 CST
  • Martin Hope
    Steve Como as permissões de arquivo funcionam para o usuário "root"? 2017-12-22 02:46:01 +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
  • Martin Hope
    Cbhihe Altere o editor padrão para vim para _ sudo systemctl edit [unit-file] _ 2017-12-03 10:11:38 +0800 CST
  • Martin Hope
    showkey Como baixar o pacote não instalá-lo com o comando apt-get? 2017-12-03 02:15:02 +0800 CST
  • Martin Hope
    youxiao Por que os diretórios /home, /usr, /var, etc. têm o mesmo número de inode (2)? 2017-12-02 05:33:41 +0800 CST
  • Martin Hope
    user223600 gpg — o comando list-keys gera uid [ desconhecido ] depois de importar a chave privada para uma instalação limpa 2017-11-26 18:26:02 +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