Comment utiliser la redirection de sortie de manière dynamic?

J'essaye d'append une option de debugging à un de mes scripts. Normalement, je veux cacher toute sortie, comme les avertissements, etc, donc je mets >/dev/null 2>&1 derrière beaucoup de commands.

Maintenant, quand je veux déboguer mon script, je devrais les supprimer manuellement, ou mettre toutes les commands à l'intérieur d'un test if ... fi pour une variable $DEBUG .

Je pensais mettre >/dev/null 2>&1 intérieur d'une variable ( $REDIR ) et écrire la command arg1 $REDIR ferait l'affaire. Si je veux déboguer, il me $REDIR de laisser $REDIR vide.

Mais un court test sur ma coquille m'a montré que ça ne marcherait pas comme ça:

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

En utilisant " ou ' around ' >/dev/null 2>&1 ne fonctionnait pas non plus pour des raisons évidentes.

Alors pourquoi mon idée ne fonctionne pas ici? Ai-je mal compris quelque chose à propos de la mise des commands etc. dans les variables et de les appeler?

Dans ce but, je définis habituellement une fonction comme run . Cela peut gérer correctement les args avec des espaces et d'autres dans la plupart des cas.

 #!/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 

Sortie:

 $ bash debug.sh # echo bla bla # printf '%s . %s . %s\n' bla 'more bla' bla bla . more bla . bla 

La redirection n'est pas une command, vous ne pouvez donc pas l'exécuter de cette manière. Vous pouvez le faire si vous utilisez eval , mais cela ouvre une boîte de Pandore.

Une meilleure méthode pour faire ce que vous essayez de faire est d'avoir une fonction pour déboguer la sortie:

 function debugprint { if [ ! -z "$debug" ]; then echo "$1" fi } debugprint "$(echo 'bla' 2>&1)" 

Cela redirecta l'erreur standard vers la sortie standard, puis debugprint avec la sortie comme argument. Maintenant, tout ce que vous devez faire est de mettre $debug sur quelque chose de non vide lorsque vous voulez déboguer.

Bien sûr, cela ouvre aussi une (différente) boîte de vers (liée à la citation). Vous pouvez simplement utiliser set -x place, ce qui peut ou ne peut pas faire assez pour vos besoins de debugging.

Dans votre cas, echo traite $REDIR comme argument de string. Vous voulez quelque chose comme:

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

Cependant, à less que vous essayiez de faire un hack rapide et sale, Wouter Verhelst a la meilleure solution (et ce n'est vraiment pas si long ou compliqué).

peut-être que vous pourriez utiliser une fonction pour diriger la sortie vers et le laisser faire la redirection:

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

Pour chaque fonction shell que j'écris, je fais exactement la même chose.

 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))" 

J'aime ça pour quelques raisons.

  1. L'utilisation de l'évaluation ${#DBG} pour len garantit toujours une valeur entière > = 0 pour le test, indépendamment de ce que $DBG pourrait contenir. Cela rend le calcul sûr même si DBG=IFS=0 , par exemple.

  2. A chaque fois que la fonction s'exécute, elle ne doit faire que l' open() sur /dev/null la seule fois – à tout autre moment je redirige vers /dev/null Je le fais à un descripteur établi dans #fd>&4 .

  3. La sortie de debugging – comme je pourrais l'activer avec set -x – pour les fonctions de mes shells interactifs est vidée par défaut sauf si je fixe explicitement la variable d'environnement $DBG sur une valeur non nulle.

    • Lorsque $DBG n'est pas nul, l'extension mathématique de la redirection pointera sur elle-même – elle sera évaluée à 2>&3 .
    • Mais sinon il évalue à 2>&4 et va donc au descripteur open /dev/null .
    • Je n'ai généralement pas besoin de voir 20 lignes de trace d'exécution pour une fonction que j'ai déjà autorisée et sauvegardée dans ~/.sh/fn/... , mais la command line à laquelle il participe est probablement une autre question tout à fait si j'ai set -x .
    • Il est aussi pratique quand $DBG est null / unset mais set -x est activé car il ouvre une sortie eval par command dans $PS4 qui ne le fait pas à stderr mais #fd>&3 peut toujours y arriver.
  4. Il permet l'inheritance d'une manière saine.

    • Une fonction qui appelle les autres ne peut pas affecter la valeur $DBG d'une manière quelconque qui pourrait leur permettre de commencer à écrire stderr par défaut, si ce n'est déjà possible.
    • Si $DBG n'est pas défini ou null toutes les fonctions enfant, il perd même la #fd3 less qu'il ne les appelle avec child_fn 2>&3 – dans ce cas ils auront la même possibilité que leur parent d'écrire explicitement à stderr.
    • Il peut également annuler $DBG pour calmer ces fonctions enfant même si $DBG est déjà défini.
    • Et il peut définir $DBG pour que les fonctions de niveau supérieur appelées après cela activent la sortie stderr par défaut.
  5. Je conserve une copy de stderr sur #fd>&3 et donc si la fonction doit pouvoir écrire stderr explicitement sur ce descripteur (sauf comme noté ci-dessus) .

  6. Les descripteurs se ferment et n'affectent pas les valeurs de shell actuelles pour fds 2,3,4 car ils ne sont associés qu'à la command compound qui enveloppe la fonction.

    • Nettoyer avec {3,4}>&- n'est jamais nécessaire.