Como passar 2> / dev / null como uma variável?

13

Eu tenho 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

Se eu tentar encurtar assim:

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

Recebo mensagens de erro:

[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 que um argumento codificado funciona, mas não um argumento como variável?


Edição 2:

Atualmente, obtive sucesso com a sugestão alternativa da segunda resposta:

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

Com base na primeira resposta, devo salientar que o cabeçalho do programa já contém:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
WinEunuuchs2Unix
fonte
Você pode tentar [[ $fCron == true ]] && exec 2>/dev/nullem vez
steeldriver
.. grosso modo, é porque o shell configura redirecionamentos antes de expandir variáveis, eu acho. Ver, por exemplo bash: Use uma variável para armazenar stderr | stdout redirecionamento
steeldriver

Respostas:

19

O motivo pelo qual você não pode causar o redirecionamento ao expandir "$HideErrors"é que símbolos como >não são tratados especialmente após serem produzidos pela expansão de parâmetros . Isso é realmente muito bom, porque esses símbolos aparecem no texto que você pode querer expandir e usar literalmente.

Isso vale se você citar ou não $HideErrors. O resultado da expansão de parâmetros está sujeito à divisão e globbing de palavras quando a expansão não é citada, mas é isso.


Quanto ao que fazer sobre isso, existem várias maneiras de obter o redirecionamento condicional. Para um comando muito simples, pode ser razoável escrever o comando inteiro duas vezes, uma vez em cada ramo de uma construção - caseou . Isso logo se torna oneroso, no entanto, e o comando que você mostrou é certamente um caso em que isso não seria o ideal.ifelse

Das abordagens que permitem que você evite se repetir , há duas que eu recomendo especialmente, porque são bastante limpas e fáceis de acertar. Você deseja usar apenas um desses, não os dois ao mesmo tempo para o mesmo comando e redirecionamento.

Armazene o comando em vez do redirecionamento. Em vez de tentar armazenar o redirecionamento em uma variável e aplicar a expansão de parâmetros, armazene o comando em uma função shell . Em seguida, escreva um caseou if- else, no qual a função é chamada com o redirecionamento em uma ramificação e sem ela na outra.

Se você conceituar seu comando como código que deseja escrever uma vez, mas executado sob várias circunstâncias, uma função é a solução natural. Isto é o que eu costumo fazer. Ele tem o benefício de não exigir um subconjunto nem armazenamento manual e redefinição de estado.

Com o seu 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

Você pode aplicar o espaçamento que quiser, ou if- elsese preferir. Observe que launchusa automaticamente o chamador RobWebAddresse as DownloadNamevariáveis, mesmo que sejam variáveis ​​locais, porque o Bash tem escopo dinâmico , diferentemente da maioria das linguagens de programação com escopo lexical.

Execute o comando em uma subshell e aplique condicionalmente o redirecionamento exec. Foi sobre isso que a steeldriver comentou , mas por dentro ( )para manter o efeito local . Quando o execbuiltin é executado sem argumentos, ele não substitui o shell atual por um novo processo, mas aplica qualquer um de seus redirecionamentos ao shell atual.

(Também é possível acompanhar o que era o erro padrão e restaurá-lo, sem usar um subshell e, portanto, sem sacrificar a capacidade de modificar o ambiente do shell atual. Entretanto, deixarei os detalhes disso para outras respostas.)

Com o seu 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"
)

Após o fechamento ), o erro padrão é efetivamente restaurado para o que era antes, porque está realmente sendo redirecionado apenas no subshell, e não no shell pai. Isso também funciona bem com as variáveis ​​de shell existentes, pois os subshells recebem uma cópia delas. Embora prefira usar uma função shell, admito que esse método possa exigir menos código.

Ambos os métodos funcionam independentemente de qual erro padrão de arquivo ou dispositivo inicia, inclusive no caso de redirecionamentos aplicados a funções de shell que chamam o código que contém o comportamento condicional, bem como no caso (mencionado em sua edição) em que o erro padrão para o script inteiro já foi redirecionado por um ou anterior . Que o caminho foi produzido pela substituição do processo não é problema.exec 2>&fdexec 2> path

Eliah Kagan
fonte
FYI SteelDriver mencionou algo sobre execnão sei se ele está planejando uma resposta sobre isso ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix Espero que essa resposta ainda seja publicada. Embora eu recomende principalmente o uso de uma função, também incluí um método que envolve um redirecionamento exec. Mas, como mencionei entre parênteses, não cobri aplicativos mais sofisticados nos quais o descritor de arquivos antigo é mantido e restaurado sem um subshell. Também não cobri aplicativos menos sofisticados, como manter o redirecionamento se este for o fim do script. Outra resposta, se publicada, pode abranger tanto e talvez mais.
Eliah Kagan 27/08/19
Atualizei minha pergunta com uma existente, execque não deve afetar sua resposta.
WinEunuuchs2Unix 27/08/19
@ WinEunuuchs2Unix Sim, isso não deve ser problema. Eu adicionei um parágrafo sobre isso no final da resposta.
Eliah Kagan 27/08/19
Revelação interessante, lendo sua resposta, RobWebAddressé definitivamente um contexto global. DownloadNamefoi definido como local, mas deve ser um contexto global. Por alguma razão funções filhos herdam definições locais de pais (a saber DownloadNameera visível a DownloadAsHTML ()função chamada pela UpdateOne ()função que definiu como locais Tem sido um dia difícil :(.
WinEunuuchs2Unix
4

Por que um argumento codificado funciona, mas não um argumento como variável?

Como os itens de sintaxe não são interpretados a partir dos valores variáveis ​​expandidos. Ou seja, expansão de variável não é o mesmo que substituir a referência da variável pelo texto da variável na linha de comando. (Coisas como ;, |, &&e citações etc. também não são especiais nos valores das variáveis.)

O que você pode fazer é usar aliases ou usar a variável para conter apenas o destino do redirecionamento.

Os aliases são apenas uma substituição de texto, para que eles possam armazenar itens sintáticos, como operadores e palavras-chave. Em um script, você precisaria shopt expand_aliases, pois, por padrão, eles são desativados em shells não interativos. Então, isso imprime 2(apenas):

#!/bin/bash
shopt -s expand_aliases

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

(E você também pode alias jos=if niin=then soj=fiescrever todas as suas declarações if em finlandês. Tenho certeza de que qualquer pessoa que esteja lendo o script amará você.)

Como alternativa, escreva sempre o redirecionamento, mas controle apenas o destino com uma variável. Você precisará de um destino não operacional para o caso em que não deseja alterar para onde vai a saída, mas /dev/stderrdeve funcionar nesse caso. Na verdade, adicionar 2> /dev/stderrnão é um problema, devido à maneira como o Linux trata os fd's abertos /proc/<pid>/fdcomo independentes do original. Isso afeta o posicionamento da posição de gravação e atrapalha a saída se for para um arquivo regular.

Porém, ele deve funcionar no modo de acréscimo (ou se o stderr for para um tubo ou um 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"

Então, para repetir: 2> /dev/stderrpode quebrar.

ilkkachu
fonte
Hahaha, eu só vou usar ifs finlandeses no trabalho a partir de agora. :>
dessert
Eu gosto de sugestões alternativas. O pensamento expand_aliasesé assustador, porque o seu programa pode ser mantido refém, ~/.bashrceu acho.
WinEunuuchs2Unix 27/08/19
1
@ WinEunuuchs2Unix, sim, expand_aliasesé um pouco assustador. Mas ~/.bashrcnão deve ser um problema, pois ele é lido apenas por shells interativos .profilee os amigos que podem chamá-lo são lidos apenas por shells de login. Os shells não interativos de não login, como scripts, não devem executar nenhum deles. (Mas depois há $BASH_ENV, e, aparentemente, .bashrcé lido, se stdin está conectado a uma tomada de rede Como complicado ele pode ficar ....)
ilkkachu
Bem, eu entendo sua sugestão alternativa perfeitamente e eu vou tentar isso hoje à noite :)
WinEunuuchs2Unix
Honestamente, não tenho certeza de que maneira implementaria isso se fosse necessário. Provavelmente eu armazenaria o comando em uma função ou matriz e, em seguida, ramificaria para decidir se colocaria o redirecionamento lá (usando uma função foi mostrada na outra resposta). Ou esse 2>> "$dst"truque, mas acabei de perceber que não funciona no caso geral, então é melhor ter cuidado com isso.
Ilkkachu
1

Título da pergunta: "Como passar 2> / dev / null como uma variável?" Isso pode ser feito 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$ 

Para reescrevermos 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"

Onde o acesso indireto à variável impede que a expansão aconteça muito cedo no restante da linha de comandos.

Aspas duplas nas variáveis ​​funcionam bem.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 
Joshua
fonte
@EliahKagan: Título da pergunta: "Como passar 2> / dev / null como uma variável?"
Joshua
Ok, não estava funcionando. Eu consertei isso.
Joshua
Agora, o arquivo sempre é nomeado - DownloadNamee o texto literal RobWebAddressé sempre usado para a URL. Você está usando $" "aspas . Eu acho que isso pode não ser intencional e você pode querer o $s dentro do " ", mas você fez assim nos dois lugares, então não tenho certeza. Eu acho que > "$DownloadName"deveria consertar isso. Mas entendo que você pode não gostar disso, já que misturar acidentalmente argumentos e não argumentos evalé uma das razões pelas quais é tão perigoso e amplamente desencorajado a usar evalo comportamento concatenador.
Eliah Kagan 28/08/19
@EliahKagan: Oh. Meu shell preferido para scripts não tem cotação $ "".
Joshua
1
Se você consertar isso, deve funcionar. E eu sempre errei ao pensar que colava citações em textos arbitrários! Ele cria argumentos literais que evalconcatenam antes de avaliar. Mas acho que outra maneira de dizer é que é uma maneira ofuscada de escrever eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"que se assemelha à aparência do código do OP. E, em geral, usar evalpara tarefas que não precisam é ruim . (Nada disso desculpas - nem mesmo explica - wrongness e hostilidade do meu velho resposta.)
Elias Kagan