Comment passer 2> / dev / null comme variable?

13

J'ai ce code qui fonctionne:

# Hide irrelevant errors so chrome doesn't email us in cron
if [[ $fCron == true ]] ; then
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName" 2>/dev/null
else
    # Get silly error messages when running from terminal
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
fi

Si j'essaye de le raccourcir comme ceci:

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
[[ $fCron == true ]] && HideErrors="2>/dev/null"

google-chrome --headless --disable-gpu --dump-dom \
    "$RobWebAddress" > "$DownloadName" "$HideErrors"

Je reçois des messages d'erreur:

[0826/043058.634775:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.672587:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.711640:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
(... SNIP ...)

Pourquoi un argument codé en dur fonctionne-t-il mais pas un argument en tant que variable?


Modifier 2:

Actuellement, j'ai trouvé le succès avec la suggestion alternative de la deuxième réponse:

# Redirect errors when cron is used to /dev/null to reduce emails
ErrorPipe=/dev/stderr
[[ $fCron == true ]] && ErrorPipe=/dev/null

google-chrome --headless --disable-gpu --dump-dom \
                "$RobWebAddress" > "$DownloadName" 2>"$ErrorPipe"

Modifier 1:

Sur la base de la première réponse, je dois souligner que l'en-tête du programme contient déjà:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
WinEunuuchs2Unix
la source
Vous pouvez essayer à la [[ $fCron == true ]] && exec 2>/dev/nullplace
Steeldriver
.. en gros, c'est parce que le shell met en place des redirections avant d'étendre les variables, je pense. Voir par exemple bash: Utilisez une variable pour stocker la redirection stderr | stdout
steeldriver

Réponses:

19

La raison pour laquelle vous ne pouvez pas provoquer de redirection en développant "$HideErrors"est que les symboles comme >ne sont pas traités spécialement après avoir été produits par l' expansion des paramètres . C'est en fait très bon, car ces symboles apparaissent dans du texte que vous voudrez peut-être développer et utiliser littéralement.

Cela vaut que vous citiez ou non $HideErrors. Le résultat de l'expansion des paramètres est sujet à la division et à la globalisation des mots lorsque l'expansion n'est pas citée, mais c'est tout.


Quant à ce qu'il faut faire à ce sujet, il existe de nombreuses façons d'obtenir une redirection conditionnelle. Pour une commande très simple, il peut être raisonnable d'écrire la commande entière deux fois, une fois dans chaque branche d'une construction caseou if- else. Cependant, cela devient vite pénible et la commande que vous avez montrée est certainement un cas où ce ne serait pas idéal.

Parmi les approches qui vous permettent d' éviter de vous répéter , il y en a deux que je recommande particulièrement, car elles sont assez propres et faciles à bien faire. Vous souhaitez utiliser un seul d'entre eux, pas les deux à la fois pour la même commande et la même redirection.

Stockez la commande au lieu de la redirection. Au lieu d'essayer de stocker la redirection dans une variable et d'appliquer une expansion de paramètre, stockez la commande dans une fonction shell . Ensuite, écrivez un caseou if- else, dans lequel la fonction est appelée avec la redirection sur une branche et sans elle sur l'autre.

Si vous conceptualisez votre commande en tant que code que vous souhaitez écrire une fois mais exécuté dans plusieurs circonstances, une fonction est la solution naturelle. C'est ce que je fais d'habitude. Il a l'avantage de ne nécessiter ni sous - shell ni stockage manuel et réinitialisation de l'état.

Avec votre code:

launch() {
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
}

case $fCron in
true)  launch 2>/dev/null;;
*)     launch;; # Get silly error messages when running from terminal
esac

Vous pouvez appliquer l'espacement que vous souhaitez, ou if- à la elseplace si vous préférez. Notez que launchutilise automatiquement l'appelant RobWebAddresset les DownloadNamevariables, même s'il s'agit de variables locales, car Bash a une portée dynamique , contrairement à la plupart des langages de programmation à portée lexicale.

Exécutez la commande dans un sous-shell et appliquez conditionnellement la redirection à exec. C'est ce que Steeldriver a commenté , mais à l' intérieur ( )pour garder l'effet local . Lorsque la fonction execintégrée est exécutée sans arguments, elle ne remplace pas le shell actuel par un nouveau processus, mais applique à la place l'une de ses redirections au shell actuel.

(Il est également possible de garder une trace de l'erreur standard et de la restaurer, sans utiliser de sous-shell et donc sans sacrifier la possibilité de modifier l'environnement du shell actuel. Je laisserai cependant les détails à d'autres réponses.)

Avec votre code:

(
    # Suppress silly error messages unless running from terminal
    case $fCron in true) exec 2>/dev/null;; esac

    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
)

Après la fermeture ), l'erreur standard est en effet restaurée à ce qu'elle était auparavant, car elle n'est vraiment redirigée que dans le sous-shell, et non dans le shell parent. Cela fonctionne également très bien avec les variables shell existantes, car les sous-shell en obtiennent une copie. Bien que je préfère utiliser une fonction shell, j'admets que cette méthode peut nécessiter moins de code.

Les deux méthodes fonctionnent quel que soit le fichier ou l'erreur standard de périphérique qui commence, y compris dans le cas des redirections appliquées aux fonctions shell qui appellent le code qui contient le comportement conditionnel, ainsi que le cas (mentionné dans votre modification) où l'erreur standard pour le script entier a déjà été redirigé par un précédent ou . Que le chemin ait été produit par substitution de processus ne pose aucun problème.exec 2>&fdexec 2> path

Eliah Kagan
la source
FYI SteelDriver a mentionné quelque chose à propos de execJe ne sais pas s'il prévoit une réponse à ce sujet ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix J'espère qu'une telle réponse est toujours affichée. Bien que je recommande principalement d'utiliser une fonction, j'ai également inclus une méthode impliquant une redirection exec. Mais, comme je l'ai mentionné entre parenthèses, je n'ai pas couvert les applications plus sophistiquées où l'ancien descripteur de fichier est conservé et restauré sans sous-shell. Je n'ai pas non plus couvert les applications moins sophistiquées, comme garder simplement la redirection si c'est la fin du script. Une autre réponse, si elle était publiée, pourrait couvrir les deux, et peut-être plus.
Eliah Kagan
J'ai mis à jour ma question avec un existant execqui ne devrait pas du tout avoir un impact sur votre réponse, je pense.
WinEunuuchs2Unix
@ WinEunuuchs2Unix Oui, cela ne devrait poser aucun problème. J'ai ajouté un paragraphe à ce sujet à la fin de la réponse.
Eliah Kagan
Une révélation intéressante à la lecture de votre réponse RobWebAddressest certainement un contexte mondial. DownloadNamea été défini local mais devrait être un contexte global. Pour une raison quelconque, les fonctions enfants héritent des définitions locales des parents (à savoir DownloadName, la DownloadAsHTML ()fonction appelée par la UpdateOne ()fonction qui la définissait comme locale. Ce fut une journée difficile :(
WinEunuuchs2Unix
4

Pourquoi un argument codé en dur fonctionne-t-il mais pas un argument en tant que variable?

Parce que les éléments de syntaxe ne sont pas interprétés à partir des valeurs de variables développées. Autrement dit, l'expansion de variable n'est pas la même chose que de remplacer la référence de variable par le texte de la variable dans la ligne de commande. ( Des choses comme ;, |, &&et des citations , etc. sont pas non plus particulière dans les valeurs des variables.)

Ce que vous pourriez faire, c'est utiliser des alias ou utiliser la variable pour conserver uniquement la cible de la redirection.

Les alias ne sont qu'un remplacement de texte, ils peuvent donc contenir des éléments syntaxiques, comme des opérateurs et des mots clés. Dans un script, vous en auriez besoin shopt expand_aliases, car par défaut, ils sont désactivés dans les shells non interactifs. Donc, cela imprime 2(uniquement):

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2

(Et vous pourriez aussi alias jos=if niin=then soj=fiet ensuite écrire toutes vos instructions if en finnois. Je suis sûr que quiconque lisant le script vous aimera.)

Sinon, écrivez toujours la redirection, mais contrôlez uniquement la cible avec une variable. Vous aurez besoin d'une cible sans opération pour le cas où vous ne voulez pas changer où va la sortie, mais cela /dev/stderrdevrait fonctionner dans ce cas. En fait, l'ajout 2> /dev/stderrn'est pas un no-op en raison de la façon dont Linux traite les fd ouverts /proc/<pid>/fdcomme indépendants de l'original. Cela affecte le positionnement de la position d'écriture et gâchera la sortie si elle va dans un fichier normal.

Cependant, cela devrait fonctionner en mode ajout (ou si stderr va vers un tube ou vers un terminal):

#!/bin/sh
exec 2>/tmp/error.log
dst=/dev/null
ls -l /nosuchfile-1 2>> "$dst"     # this doesn't print
dst=/dev/stderr
ls -l /nosuchfile-2 2>> "$dst"
ls -l /nosuchfile-3 2>> "$dst"

Donc, pour répéter: 2> /dev/stderrpeut casser.

ilkkachu
la source
Hahaha, je n'utiliserai que des ifs finlandais au travail à partir de maintenant. :>
dessert
J'aime la suggestion alternative. L'idée expand_aliasesest effrayante car votre programme peut être pris en otage par ~/.bashrcje pense.
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix, oui, expand_aliasesc'est un peu effrayant. Mais cela ~/.bashrcne devrait pas être un problème car il n'est lu que par des shells interactifs, .profileet les amis qui pourraient l'appeler ne sont lus que par des shells de connexion. Les shells non-interactifs sans connexion comme les scripts ne devraient exécuter aucun de ceux-ci. (Mais alors il y a $BASH_ENV, et .bashrcest apparemment lu si stdin est connecté à une prise réseau. Comment peut-il être compliqué ...)
ilkkachu
Eh bien, je comprends parfaitement votre suggestion alternative et je vais l'essayer ce soir :)
WinEunuuchs2Unix
Honnêtement, je ne sais pas de quelle manière j'implémenterais cela si je le devais. Je stockerais probablement la commande dans une fonction ou un tableau, puis je ferais un branchement pour décider s'il fallait y mettre la redirection (l'utilisation d'une fonction était indiquée dans l'autre réponse). Ou cette 2>> "$dst"astuce, mais je viens de réaliser que cela ne fonctionne pas dans le cas général, alors mieux vaut y faire attention.
ilkkachu
1

Titre de la question: "Comment passer 2> / dev / null comme variable?" Cela peut être fait en utilisanteval

joshua@nova:/tmp$ X=">/dev/null"
joshua@nova:/tmp$ echo $X
>/dev/null
joshua@nova:/tmp$ eval echo $X
joshua@nova:/tmp$ eval echo hi
hi
joshua@nova:/tmp$ eval echo hi $X
joshua@nova:/tmp$ echo hi $X
hi >/dev/null
joshua@nova:/tmp$ 

Nous pouvons donc réécrire

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
local RobWebAddress2
local DownloadName2
[[ $fCron == true ]] && HideErrors="2>/dev/null"
RobWebAddress2='"$RobWebAddress"'
DownloadName2='>"$DownloadName"'

eval google-chrome --headless --disable-gpu --dump-dom \
    $RobWebAddress2 $DownloadName2 "$HideErrors"

Où l'accès variable indirect empêche l'expansion de se produire trop tôt sur le reste de la ligne de commande.

Les guillemets doubles dans les variables fonctionnent très bien.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 
Joshua
la source
@EliahKagan: Titre de la question: "Comment passer 2> / dev / null comme variable?"
Joshua
Ok ça ne marchait pas. Je l'ai corrigé.
Joshua
Maintenant, le fichier est toujours nommé DownloadName- et le texte littéral RobWebAddressest toujours utilisé pour l'URL. Vous utilisez des $" "guillemets . Je pense que cela peut ne pas être intentionnel et vous voudrez peut-être les $s à l'intérieur " ", mais vous l'avez fait de cette façon aux deux endroits, donc je ne suis pas sûr. Je pense que je > "$DownloadName"devrais juste le réparer. Mais je comprends que vous n'aimerez peut-être pas cela, car le mélange accidentel d'arguments et de non-arguments avec evalest l'une des raisons pour lesquelles il est si dangereux et largement déconseillé d'utiliser evalle comportement de concaténation de.
Eliah Kagan
@EliahKagan: Oh. Mon shell préféré pour les scripts n'a pas de guillemet $ "".
Joshua
1
Si vous corrigez cela, cela devrait fonctionner. Et j'ai toujours eu tort de penser qu'il collait des citations sur du texte arbitraire! Il construit des arguments littéraux qui evalconcatène avant d'évaluer. Mais je pense qu'une autre façon de le dire est que c'est une façon obscure d'écrire eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"qui ressemble à l' apparence du code de l'OP. Et en général, l' utilisation evalpour des tâches qui n'en ont pas besoin est mauvaise . (Rien de tout cela n'excuse - ni n'explique même - le tort et l'hostilité de mon ancienne réponse.)
Eliah Kagan