Como combinar o agrupamento de comandos bash e o status do pipe

6

Como eu combino o agrupamento de comandos do bash e o status do pipe?

Este é um exemplo de grupo de comando:

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; } 3>&1 | gzip --rsyncable > my_file.tar.gz


Esta é uma leitura de status de tubo de exemplo para acompanhar o acima:

[[ ${PIPESTATUS[*]} =~ [1-9] ]] && rm my_file.tar.gz


Neste exemplo, o grupo de comando mantém o spool de email livre de avisos sobre "Removendo leading /" do tar, sendo entregues via cron porque eles chegam ao stderr (Unices não possui um stdwarn e o tar não possui uma opção silenciosa), enquanto deixa erros reais passarem .

A leitura do status do pipe garante que os arquivos de backup corrompidos sejam imediatamente removidos, para evitar que uma limpeza posterior usando um algoritmo FIFO padrão remova arquivos válidos mais antigos.

Mas este exemplo não funciona. Acima, o status do pipe contém [1 0], ou seja, o código de saída do grep e do gzip, mas não do tar.

Uma tentativa que tentei foi esta:

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; GROUPSTATUS=${PIPESTATUS[*]}; } 3>&1 | gzip --rsyncable > my_file.tar.gz
[[ $GROUPSTATUS =~ [1-9] ]] && rm my_file.tar.gz

Mas GROUPSTATUS está vazio ao sair do grupo.

(Note que ajustando o GROUPSTATUS para qualquer coisa diferente do status do pipe, por exemplo, um literal de algum tipo, pode ser verificado que a variável de fato escapa do escopo do agrupamento de comandos sob circunstâncias normais.)

Eu também tentei se return de dentro do grupo pudesse entregar o código de saída do primeiro componente do pipe para o exterior, mas retornar dentro de um grupo de comandos apenas produz uma mensagem de erro do bash.

    
por user3487 19.01.2011 / 00:38

2 respostas

5

Quando você executa um pipeline, cada elemento separado por pipe é executado em seu próprio processo. Atribuições variáveis só entram em vigor em seu próprio processo. Sob ksh e zsh, o último elemento do pipeline é executado no shell original; sob outras shells como o bash, cada elemento do pipeline é executado em sua própria subshell e o shell original apenas espera que todos eles terminem.

$ bash -c 'GROUPSTATUS=foo; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is foo
$ bash -c 'GROUPSTATUS=foo | :; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is 

No seu caso, uma vez que você só se importa com todos os comandos seguintes, você pode aumentar o fluxo de status.

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2;
  ! ((PIPESTATUS[0])); } 3>&1 |
gzip --rsyncable > my_file.tar.gz;
if ((PIPESTATUS[0] || PIPESTATUS[1])); then rm my_file.tar.gz; fi

Se você deseja obter mais de 8 bits de informação do lado esquerdo de um pipe, pode escrever para outro descritor de arquivo. Aqui está um exemplo de prova de princípio:

{ { tar …; echo $? >&4; } | …; } | { gzip …; echo $? >&4; } \
  4>&1 | ! grep -vxc '0'

Depois de obter os dados na saída padrão, você poderá alimentá-los em uma variável do shell usando a substituição de comandos, ou seja, $(…) . A substituição de comando lê a saída padrão do comando, portanto, se você também quis imprimir as coisas na saída padrão do script, elas precisam passar temporariamente por outro descritor de arquivo. O snippet a seguir usa o fd 3 para coisas que eventualmente vão para o stdout do script e o fd 4 para itens capturados em $statuses .

statuses=$({ { tar -v … >&3; echo tar $? >&4; } | …; } |
           { gzip …; echo gzip $? >&4; } 4>&1) 3>&1

Se você precisa capturar a saída de diferentes comandos em diferentes variáveis, eu acho que não existe um caminho direto mesmo em shells “avançados” como bash, ksh ou zsh. Aqui estão algumas soluções alternativas:

  • Use arquivos temporários.
  • Use um único fluxo de saída, por exemplo, um prefixo em cada linha para indicar sua origem e filtrar no nível superior.
  • Use uma linguagem mais avançada, como Perl ou Python.
por 19.01.2011 / 01:38
1

Uma maneira simples é ignorar o problema, colocar my_folder em uma variável shell e remover a barra inicial:

tar -cf - ${my_folder##/} | gzip --rsyncable > my_file.tar.gz

Caso contrário, você pode verificar $ {PIPESTATUS [0]} dentro do bloco e usar false se não for zero para sinalizar o erro para o processo externo:

{ tar -cf - my_folder 2>&1 1>&3 | 
    grep -v "Removing leading" 1>&2; 
    [ ${PIPESTATUS[0]} -eq 0 ] && true || false; } 3>&1 | 
    gzip --rsyncable > my_file.tar.gz
    
por 19.01.2011 / 01:35

Tags