¿Cómo pasar 2> / dev / null como variable?

13

Tengo este código que funciona:

# 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 trato de acortarlo así:

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

Recibo mensajes de error:

[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 ...)

¿Por qué funciona un argumento codificado pero no un argumento como variable?


Edición 2:

Actualmente encontré el éxito con la sugerencia alternativa de la segunda respuesta:

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

Editar 1:

Basado en la primera respuesta, debo señalar que el encabezado del programa ya contiene:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
WinEunuuchs2Unix
fuente
Puedes probar en su [[ $fCron == true ]] && exec 2>/dev/nulllugar
steeldriver
Hablando en términos generales, creo que es porque el shell establece redirecciones antes de expandir las variables. Vea por ejemplo bash: use una variable para almacenar la redirección stderr | stdout
steeldriver

Respuestas:

19

La razón por la que no puede causar la redirección al expandirse "$HideErrors"es que los símbolos como >no son tratados especialmente después de ser producidos por la expansión de parámetros . Esto es realmente muy bueno, porque tales símbolos aparecen en el texto que tal vez desee expandir y usar literalmente.

Esto es válido tanto si cotiza como si no $HideErrors. El resultado de la expansión de parámetros está sujeto a división de palabras y glob cuando la expansión no se cita, pero eso es todo.


En cuanto a qué hacer al respecto, hay numerosas formas de lograr la redirección condicional. Para un comando muy simple, puede ser razonable escribir todo el comando dos veces, una en cada rama de un caseo if- elseconstructo. Sin embargo, esto pronto se vuelve pesado, y el comando que mostró es ciertamente un caso en el que eso no sería ideal.

De los enfoques que le permiten evitar repetirse , hay dos que recomiendo especialmente, porque son bastante limpios y fáciles de entender. Desea usar solo uno de estos, no ambos a la vez para el mismo comando y redirección.

Almacene el comando en lugar de la redirección. En lugar de intentar almacenar la redirección en una variable y aplicar la expansión de parámetros, almacene el comando en una función de shell . Luego escriba un caseo if- else, en el que se llama a la función con la redirección en una rama y sin ella en la otra.

Si conceptualiza su comando como código que desea escribir una vez pero se ejecuta bajo múltiples circunstancias, entonces una función es la solución natural. Esto es lo que suelo hacer. Tiene el beneficio de no requerir ni un subshell ni almacenamiento manual y restablecimiento de estado.

Con su código:

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

Puede aplicar el espacio que desee o if, elseen su lugar, si lo prefiere. Tenga en cuenta que launchusa automáticamente la persona que llama RobWebAddressy las DownloadNamevariables, incluso si son variables locales, porque Bash tiene un alcance dinámico , a diferencia de la mayoría de los lenguajes de programación que tienen un alcance léxico.

Ejecute el comando en una subshell y condicionalmente aplique la redirección a exec. Esto es lo que comentó Steeldriver , pero por dentro ( )para mantener el efecto local . Cuando el execbuiltin se ejecuta sin argumentos, no reemplaza el shell actual con un nuevo proceso, sino que aplica cualquiera de sus redirecciones al shell actual.

(También es posible hacer un seguimiento de qué error estándar era y restaurarlo, sin usar un subshell y, por lo tanto, sin sacrificar la capacidad de modificar el entorno actual del shell. Sin embargo, dejaré los detalles de eso a otras respuestas).

Con su código:

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

Después del cierre ), el error estándar se restaura a lo que era antes, porque solo se redirige en la subshell y no en la shell principal. Esto también funciona bien con las variables de shell existentes, ya que los subshells obtienen una copia de ellos. Aunque prefiero usar una función de shell, admito que este método puede requerir menos código.

Ambos métodos funcionan independientemente de qué error estándar de archivo o dispositivo comience, incluso en el caso de redirecciones aplicadas a funciones de shell que llaman al código que contiene el comportamiento condicional, así como el caso (mencionado en su edición) donde el error estándar para todo el script ya ha sido redirigido por un anterior o . Que el camino fue producido por la sustitución del proceso no es problemaexec 2>&fdexec 2> path

Eliah Kagan
fuente
FYI SteelDriver mencionó algo acerca de execno saber si él está planeando una respuesta en ese ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix Espero que tal respuesta todavía se publique. Aunque principalmente recomiendo usar una función, también incluí un método que involucra una redirección exec. Pero, como mencioné entre paréntesis, no cubrí aplicaciones más sofisticadas en las que el antiguo descriptor de archivo se mantiene y restaura sin una subshell. Tampoco cubrí aplicaciones menos sofisticadas, como simplemente mantener la redirección si este es el final del script. Otra respuesta, si se publica, podría cubrir ambos, y tal vez más.
Eliah Kagan
He actualizado mi pregunta con una existente execque no debería afectar su respuesta en absoluto, creo.
WinEunuuchs2Unix
@ WinEunuuchs2Unix Sí, eso no debería ser un problema. He agregado un párrafo al final de la respuesta.
Eliah Kagan
Una revelación interesante al leer su respuesta, RobWebAddresses definitivamente un contexto global. DownloadNamese definió local pero debería ser contexto global. Por alguna razón, las funciones secundarias heredan las definiciones locales de los padres (a saber, DownloadNameera visible para la DownloadAsHTML ()función llamada por la UpdateOne ()función que la definía como local. Ha sido un día difícil :(
WinEunuuchs2Unix
4

¿Por qué funciona un argumento codificado pero no un argumento como variable?

Porque los elementos de sintaxis no se interpretan a partir de valores variables expandidos. Es decir, la expansión de la variable no es lo mismo que reemplazar la referencia de la variable con el texto de la variable en la línea de comando. (Cosas como ;, |, &&y citas etc. tampoco son especial en los valores de las variables.)

Lo que podría hacer es usar alias, o usar la variable para contener solo el objetivo de la redirección.

Los alias son solo un reemplazo de texto, por lo que pueden contener elementos sintácticos, como operadores y palabras clave. En un script, debería hacerlo shopt expand_aliases, ya que de forma predeterminada están deshabilitados en shells no interactivos. Entonces, esto imprime 2(solo):

#!/bin/bash
shopt -s expand_aliases

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

(Y también podría alias jos=if niin=then soj=fiy luego escribir todas sus declaraciones if en finlandés. Estoy seguro de que cualquiera que lea el guión lo amará).

Alternativamente, escriba la redirección siempre, pero controle solo el objetivo con una variable. Sin embargo/dev/stderr , necesitará un objetivo no operativo para el caso en el que no desea cambiar el destino de la salida, pero debería funcionar en ese caso. En realidad, agregar 2> /dev/stderrno es un no-op debido a la forma en que Linux trata los fd's abiertos /proc/<pid>/fdcomo independientes del original. Esto afecta el posicionamiento de la posición de escritura y desordenará la salida si va a un archivo normal.

Sin embargo, debería funcionar en modo de agregado (o si stderr va a una tubería o a una 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"

Entonces, para repetir: 2> /dev/stderrpuede romperse.

ilkkachu
fuente
Jajaja, solo usaré ifs finlandeses en el trabajo de ahora en adelante. :>
postre
Me gusta la sugerencia alternativa. La idea expand_aliaseses aterradora porque ~/.bashrccreo que tu programa puede ser tomado como rehén .
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix, sí, expand_aliasesda un poco de miedo. Pero ~/.bashrcno debería ser un problema, ya que solo se lee mediante shells interactivos, .profiley los amigos que pueden llamarlo se leen solo mediante shells de inicio de sesión. Los shells no interactivos sin inicio de sesión, como los scripts, no deberían ejecutar ninguno de esos. (Pero luego está $BASH_ENV, y al parecer .bashrcse lee la entrada estándar si se conecta a un enchufe de la red ¿Cómo contorneado se puede poner esto ....)
ilkkachu
Bueno, entiendo perfectamente tu sugerencia alternativa y lo intentaré esta noche :)
WinEunuuchs2Unix
Honestamente, no estoy seguro de qué manera implementaría esto si tuviera que hacerlo. Probablemente almacene el comando en una función o una matriz y luego me ramifique para decidir si poner la redirección allí (el uso de una función se mostró en la otra respuesta). O ese 2>> "$dst"truco, pero me di cuenta de que no funciona en el caso general, así que mejor ten cuidado con eso.
ilkkachu
1

Título de la pregunta: "¿Cómo pasar 2> / dev / null como variable?" Esto realmente se puede hacer usandoeval

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$ 

Entonces podemos reescribir como

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

Donde el acceso variable indirecto evita que la expansión ocurra demasiado pronto en el resto de la línea de comando.

Las comillas dobles en variables funcionan bien.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 
Joshua
fuente
@EliahKagan: Título de la pregunta: "¿Cómo pasar 2> / dev / null como variable?"
Joshua
Ok, no estaba funcionando. Lo he arreglado
Joshua
Ahora el archivo siempre se llama, DownloadNamey el texto literal RobWebAddresssiempre se usa para la URL. Estás usando $" "citas . Creo que esto puede ser involuntario y es posible que desee los $s dentro " ", pero lo hizo de esa manera en ambos lugares, así que no estoy seguro. Creo que solo > "$DownloadName"debería arreglarlo. Pero entiendo que puede que no le guste eso, ya que mezclar accidentalmente argumentos y no argumentos evales una de las razones por las que es tan peligroso y ampliamente desaconsejado usar evalel comportamiento concatenante.
Eliah Kagan
@EliahKagan: Oh. Mi shell preferido para las secuencias de comandos no tiene una cita "".
Joshua
1
Si arreglas eso, debería funcionar. ¡Y siempre me equivoqué al pensar que pegaba citas en textos arbitrarios! Construye argumentos literales que se evalconcatenan antes de evaluar. Pero creo que otra forma de decirlo es que es una forma ofuscada de escribir eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"que se parece a la apariencia del código del OP. Y en general, usar evalpara tareas que no lo necesitan es malo . (Ninguna de estas excusas, ni siquiera explica, la equivocación y hostilidad de mi vieja respuesta.)
Eliah Kagan