Definir uma variável de ambiente antes de um comando no Bash não está funcionando para o segundo comando em um canal

351

Em um determinado shell, normalmente eu definiria uma variável ou variáveis ​​e depois executaria um comando. Recentemente, aprendi sobre o conceito de anexar uma definição de variável a um comando:

FOO=bar somecommand someargs

Isso funciona ... mais ou menos. Não funciona quando você está alterando uma variável LC_ * (que parece afetar o comando, mas não seus argumentos, por exemplo, intervalos de caracteres '[az]') ou quando canaliza a saída para outro comando da seguinte maneira:

FOO=bar somecommand someargs | somecommand2  # somecommand2 is unaware of FOO

Também posso acrescentar um comando2 com "FOO = bar", o que funciona, mas adiciona duplicação indesejada e não ajuda com argumentos interpretados dependendo da variável (por exemplo, '[az]').

Então, qual é uma boa maneira de fazer isso em uma única linha?

Estou pensando em algo da ordem de:

FOO=bar (somecommand someargs | somecommand2)  # Doesn't actually work

Eu tenho muitas respostas boas! O objetivo é manter essa linha única, de preferência sem usar "exportação". O método usando uma chamada para o Bash foi o melhor em geral, embora a versão entre parênteses com "exportação" fosse um pouco mais compacta. O método de usar o redirecionamento em vez de um canal também é interessante.

MartyMacGyver
fonte
11
(T=$(date) echo $T)funcionará
vp_arth
No contexto de scripts de plataforma cruzada (inclusive janelas) ou projetos baseados em npm (js ou outro), você pode dar uma olhada no módulo entre ambientes .
precisa saber é o seguinte
5
Eu esperava que uma das respostas também explicasse por que isso funciona apenas, ou seja, por que não é equivalente a exportar a variável antes da chamada.
Brecht Machiels
4
O porquê é explicado aqui: stackoverflow.com/questions/13998075/…
Brecht Machiels

Respostas:

317
FOO=bar bash -c 'somecommand someargs | somecommand2'
Pausado até novo aviso.
fonte
2
Isso atende aos meus critérios (one-liner sem a necessidade de "exportação") ... Entendo que não há como fazer isso sem chamar "bash -c" (por exemplo, uso criativo de parênteses)?
MartyMacGyver
11
@MartyMacGyver: Nada que eu possa pensar. Também não funcionará com chaves.
Pausado até novo aviso.
7
Observe que se você precisar executar o seu somecommandcomo sudo, precisará passar o -Esinalizador sudo para passar pelas variáveis. Porque variáveis ​​podem introduzir vulnerabilidades. stackoverflow.com/a/8633575/1695680
ThorSummoner
11
Observe que, se o seu comando já tiver dois níveis de aspas, esse método se tornará extremamente insatisfatório por causa do inferno das aspas. Nessa situação, exportar no subshell é muito melhor.
Pushpendre 22/01
Estranho: no OSX, FOO_X=foox bash -c 'echo $FOO_X'funciona conforme o esperado, mas com nomes específicos de var falha: DYLD_X=foox bash -c 'echo $DYLD_X'echos em branco. tanto trabalho usando evalem vez debash -c
mwag
209

Que tal exportar a variável, mas apenas dentro do subshell ?:

(export FOO=bar && somecommand someargs | somecommand2)

Keith tem razão, para executar incondicionalmente os comandos, faça o seguinte:

(export FOO=bar; somecommand someargs | somecommand2)
0xC0000022L
fonte
15
Eu usaria ;ao invés de &&; não tem como export FOO=barfalhar.
Keith Thompson
11
@MartyMacGyver: &&executa o comando esquerdo, depois executa o comando direito apenas se o comando esquerdo tiver êxito. ;executa os dois comandos incondicionalmente. O lote do Windows ( cmd.exe) equivalente a ;é &.
Keith Thompson
2
No zsh, parece que não preciso de exportação para esta versão: (FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOOyields FOO=XXX\nFOO=\n.
Rampion
3
@PopePoopinpants: por que não usar source(aka .) nesse caso? Além disso, os backticks não devem mais ser usados ​​nos dias de hoje e essa é uma das razões pelas quais o uso $(command)é muito mais seguro.
0xC0000022L
3
Tão simples, mas tão elegante. E eu gosto mais da sua resposta do que da resposta aceita, pois ela iniciará um sub shell igual ao atual (que pode não ser, bashmas pode ser outra coisa, por exemplo dash) e não encontro problemas se precisar usar aspas dentro do comando args ( someargs).
Mecki
45

Você também pode usar eval:

FOO=bar eval 'somecommand someargs | somecommand2'

Como essa resposta com evalparece não agradar a todos, deixe-me esclarecer uma coisa: quando usada como escrita, com aspas simples , é perfeitamente segura. É bom, pois não inicia um processo externo (como a resposta aceita) nem executa os comandos em um subshell extra (como a outra resposta).

Como obtemos algumas visualizações regulares, provavelmente é bom dar uma alternativa para evalisso, para agradar a todos e ter todos os benefícios (e talvez até mais!) Desse rápido eval"truque". Basta usar uma função! Defina uma função com todos os seus comandos:

mypipe() {
    somecommand someargs | somecommand2
}

e execute-o com suas variáveis ​​de ambiente como esta:

FOO=bar mypipe
gniourf_gniourf
fonte
7
@ Alfe: Você também recusou a resposta aceita? porque exibe os mesmos "problemas" que eval.
Gnicf_gniourf
10
@ Alfe: infelizmente não concordo com a sua crítica. Este comando é perfeitamente seguro. Você realmente soa como um cara que uma vez leu o evalmal sem entender o que é o mal eval. E talvez você não esteja realmente entendendo esta resposta depois de tudo (e realmente não há nada de errado com ela). No mesmo nível: você diria que lsé ruim porque for file in $(ls)é ruim? (e sim, você não votou na resposta aceita e também não deixou um comentário). SO é um lugar tão estranho e absurdo às vezes.
Gnicf_gniourf
7
@ Alfe: quando digo que você realmente soa como um cara que leu uma vez que evalé mau, sem entender o que é o mal eval, estou me referindo à sua frase: Esta resposta carece de todos os avisos e explicações necessárias ao se falar eval. evalnão é ruim ou perigoso; não mais que bash -c.
Gnicf_gniourf
11
Votos à parte, o comentário fornecido pelo @Alfe de alguma forma implica que a resposta aceita é de alguma forma mais segura. O que teria sido mais útil teria sido para você descrever o que acredita ser inseguro quanto ao uso eval. Na resposta fornecida, os args foram citados de maneira única, protegendo da expansão variável, portanto, não vejo problema com a resposta.
Brett Ryan
Eu removi meus comentários para concentrar minha preocupação em um novo comentário: evalé uma questão de segurança em geral (como bash -cmenos óbvia); portanto, os perigos devem ser mencionados em uma resposta que propõe seu uso. Usuários descuidados podem pegar a resposta ( FOO=bar eval …) e aplicá-la à sua situação, para que ela cause problemas. Mas, obviamente, era mais importante para o respondente descobrir se eu diminuí o voto dele e / ou outras respostas do que melhorar alguma coisa. Como escrevi antes, a justiça não deve ser a principal preocupação; não ser pior do que qualquer outra resposta dada também é independente.
Alfe
12

Use env.

Por exemplo env FOO=BAR command,. Observe que as variáveis ​​de ambiente serão restauradas / inalteradas novamente quando a commandexecução for concluída.

Apenas tenha cuidado com a substituição de shell, ou seja, se você quiser fazer referência $FOOexplicitamente na mesma linha de comando, pode ser necessário escapá-lo para que seu interpretador de shell não execute a substituição antes de executar env.

$ export FOO=BAR
$ env FOO=FUBAR bash -c 'echo $FOO'
FUBAR
$ echo $FOO
BAR
benjimin
fonte
-5

Use um script de shell:

#!/bin/bash
# myscript
FOO=bar
somecommand someargs | somecommand2

> ./myscript
Spencer Rathbun
fonte
13
Você ainda precisa export; caso contrário $FOO, será uma variável de shell, não uma variável de ambiente e, portanto, não será visível para somecommandou somecommand2.
Keith Thompson
Funcionaria, mas anula o propósito de ter um comando de uma linha (estou tentando aprender maneiras mais criativas de evitar multi-liners e / ou scripts para casos relativamente simples). E o que @Keith disse, embora pelo menos a exportação permaneça no escopo do script.
MartyMacGyver