Como usar o redirecionamento de saída dinamicamente?

5

Estou tentando adicionar uma opção de depuração a um script meu. Normalmente eu quero esconder qualquer saída, como avisos etc, então eu coloquei >/dev/null 2>&1 para muitos comandos.

Agora, quando eu quiser depurar meu script, precisarei removê-los manualmente ou colocar todos os comandos dentro de um if ... fi testing para uma $DEBUG Variable.

Pensei em colocar >/dev/null 2>&1 dentro de uma variável ( $REDIR ) e escrever command arg1 $REDIR faria o truque. Se eu quiser depurar, precisarei deixar apenas $REDIR empty.

Mas um teste curto na minha concha mostrou que não funcionaria dessa maneira:

~$ echo "bla" >/dev/null 2>&1
~$ REDIR=>/dev/null 2>&1
~$ echo "bla" $REDIR
bla
~$

Usar " ou ' em torno de >/dev/null 2>&1 não funcionou por motivos óbvios.

Então, por que minha ideia não funciona aqui? Eu entendi mal alguma coisa sobre colocar comandos etc. em Variáveis e chamá-los?

    
por Minix 08.06.2015 / 15:45

5 respostas

4

Para tal, eu geralmente defino uma função como run . Isso pode manipular corretamente argumentos com espaços e outros na maioria dos casos.

#!/bin/bash

run() {
        if $DEBUG; then
                v=$(exec 2>&1 && set -x && set -- "$@")
                echo "#${v#*--}"
                "$@"
        else
                "$@" >/dev/null 2>&1
        fi
}

DEBUG=false
run echo "bla"

DEBUG=true
run echo "bla"
run printf "%s . %s . %s\n" bla "more bla" bla

Saída:

$ bash debug.sh 
# echo bla
bla
# printf '%s . %s . %s\n' bla 'more bla' bla
bla . more bla . bla
    
por 08.06.2015 / 18:54
6

Redirecionamento não é um comando, portanto você não pode executá-lo desta maneira. Você pode fazê-lo se usar eval , mas isso está abrindo uma lata de worms.

Um método melhor para fazer o que você está tentando fazer é ter uma função para depuração de saída:

function debugprint {
    if [ ! -z "$debug" ]; then
        echo "$1"
    fi
}

debugprint "$(echo 'bla' 2>&1)"

Isso redirecionará o erro padrão para a saída padrão e, em seguida, chamará debugprint com a saída como um argumento. Agora tudo o que você precisa fazer é definir $debug como algo não vazio quando quiser depurar.

Claro, isso também está abrindo uma lata de worms (diferente) (relacionada à cotação). Você pode querer usar apenas set -x , o que pode ou não fazer o suficiente para suas necessidades de depuração.

    
por 08.06.2015 / 16:07
2

No seu caso, echo está tratando $REDIR como um argumento de string. Você quer algo como:

~$ echo "bla" >/dev/null 2>&1
~$ REDIR='>/dev/null 2>&1'
~$ eval "echo bla $REDIR"
~$

No entanto, a menos que você esteja tentando fazer um hack rápido e sujo, Wouter Verhelst tem a melhor solução (e não é tão longa ou complicada).

    
por 08.06.2015 / 16:13
2

talvez você possa usar uma função para encaminhar a saída e permitir o redirecionamento:

#! /bin/bash

DEBUGMODE="Debug"

function handleStdOut {
    if [[ "$DEBUGMODE" == "Debug" ]]
    then
        echo "Debugging..."
        cat
        echo "Done"
    else
         cat > /dev/null
    fi 
}

echo "bla" | handleStdOut
    
por 08.06.2015 / 16:21
2

Para cada função de shell que escrevo, faço exatamente a mesma coisa.

fn(){ 
    echo some normal stderr debug stuff             >&2     #if $DBG 2>stderr
    dd if="\$DBG/please/report/on/this/file"                #ditto
    echo I DEFINITELY need to handle this           >&3     #always stderr
    ( PATH=; ".some" oops I expect to handle )     2>&4     #always /dev/null
    echo and the regular stuff                              #always unaffected
}   4<>/dev/null 3>&2 2>&"$((${#DBG}?3:4))"

Eu gosto disso por alguns motivos.

  1. Usar a avaliação ${#DBG} para len sempre garante um valor inteiro > = 0 para o teste - independentemente de qual $DBG possa realmente conter . Isso torna a matemática segura mesmo se DBG=IFS=0 , por exemplo.

  2. Cada vez que a função é executada, só tem que fazer a% realopen() on /dev/null a única vez - qualquer outra vez eu redirecionar para /dev/null Eu estou fazendo isso para um descritor estabelecido em #fd>&4 .

  3. Saída de depuração - como eu poderia ativar com set -x - para funções em meus shells interativos é despejada por padrão a menos que eu defina explicitamente a variável de ambiente $DBG para qualquer valor não nulo. / p>

    • Quando $DBG não é nulo, a expansão matemática do redirecionamento apontará para si mesma - ela é avaliada como 2>&3 .
    • Caso contrário, ele é avaliado como 2>&4 e, portanto, vai para o descritor /dev/null aberto.
    • Eu normalmente não preciso ver 20 linhas de rastreamento de execução para uma função que já estou ok e salva em ~/.sh/fn/... , mas a linha de comando na qual ela participa é provavelmente outra questão se eu set -x .
    • Também é útil quando $DBG é nulo / não configurado, mas set -x é ativado porque abre uma saída de teste por comando em $PS4 , que não chega a stderr - mas #fd>&3 ainda pode chegar lá.
  4. Permite a herança de uma maneira sã.

    • Uma função que chama outras pessoas não pode afetar o valor $DBG de qualquer forma que possa permitir que eles iniciem a gravação em stderr por padrão, se já não puderem.
    • Se $DBG não estiver definido ou nulo, todas as funções filho que ele chama perderão mesmo a significância de #fd3 , a menos que ele as chame com child_fn 2>&3 - nesse caso, elas terão a mesma oportunidade que seu pai escreveu para stderr explicitamente.
    • Ele pode também desabilitar $DBG para acalmar essas funções filho mesmo quando $DBG já estiver definido.
    • E pode definir $DBG para que as funções de nível superior chamadas após ativem a saída padrão do stderr.
  5. Eu retenho uma cópia de stderr em #fd>&3 e assim, se a função deve, ela ainda pode gravar stderr explicitamente naquele descritor (exceto como mencionado acima) .

  6. Os descritores se fecham e não afetam os valores atuais do shell para fds 2,3,4, porque eles são associados apenas ao comando composto que envolve a função.

    • Limpar com {3,4}>&- nunca é necessário.
por 08.06.2015 / 20:58