Establecer una variable de entorno antes de que un comando en Bash no funcione para el segundo comando en una tubería

351

En un shell dado, normalmente establecería una variable o variables y luego ejecutaría un comando. Recientemente aprendí sobre el concepto de anteponer una definición variable a un comando:

FOO=bar somecommand someargs

Esto funciona ... más o menos. No funciona cuando está cambiando una variable LC_ * (que parece afectar el comando, pero no sus argumentos, por ejemplo, '[az]' rangos de caracteres) o cuando canaliza la salida a otro comando de esta manera:

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

También puedo anteponer somecommand2 con "FOO = bar", que funciona, pero que agrega duplicación no deseada, y no ayuda con argumentos que se interpretan dependiendo de la variable (por ejemplo, '[az]').

Entonces, ¿cuál es una buena manera de hacer esto en una sola línea?

Estoy pensando en algo del orden de:

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

¡Tengo muchas buenas respuestas! El objetivo es mantener esto de una línea, preferiblemente sin usar "exportar". El método que utiliza una llamada a Bash fue el mejor en general, aunque la versión entre paréntesis con "exportar" era un poco más compacta. El método de utilizar la redirección en lugar de una tubería también es interesante.

MartyMacGyver
fuente
1
(T=$(date) echo $T)funcionará
vp_arth
En el contexto de scripts multiplataforma (incl. Windows) o proyectos basados ​​en npm (js o de lo contrario), es posible que desee echar un vistazo al módulo cross-env .
Frank Nocke
55
Esperaba que una de las respuestas también explicara por qué esto solo funciona, es decir, por qué no es equivalente a exportar la variable antes de la llamada.
Brecht Machiels
44
El por qué se explica aquí: stackoverflow.com/questions/13998075/…
Brecht Machiels

Respuestas:

317
FOO=bar bash -c 'somecommand someargs | somecommand2'
Pausado hasta nuevo aviso.
fuente
2
Esto satisface mis criterios (una línea sin necesidad de "exportar") ... ¿Supongo que no hay forma de hacerlo sin llamar a "bash -c" (por ejemplo, uso creativo de paréntesis)?
MartyMacGyver 01 de
1
@MartyMacGyver: Ninguno que se me ocurra. Tampoco funcionará con llaves.
Pausado hasta nuevo aviso.
77
Tenga en cuenta que si necesita ejecutar su somecommandcomo sudo, debe pasar sudo a la -Ebandera para pasar a través de las variables. Porque las variables pueden introducir vulnerabilidades. stackoverflow.com/a/8633575/1695680
ThorSummoner
11
Tenga en cuenta que si su comando ya tiene dos niveles de comillas, entonces este método se vuelve extremadamente insatisfactorio debido al infierno de comillas. En esa situación, exportar en subshell es mucho mejor.
Pushpendre
Extraño: en OSX, FOO_X=foox bash -c 'echo $FOO_X'funciona como se esperaba, pero con nombres var específicos falla: DYLD_X=foox bash -c 'echo $DYLD_X'eco en blanco. ambos trabajan usando en evallugar debash -c
mwag
209

¿Qué tal exportar la variable, pero solo dentro de la subshell ?:

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

Keith tiene un punto, para ejecutar incondicionalmente los comandos, haga esto:

(export FOO=bar; somecommand someargs | somecommand2)
0xC0000022L
fuente
15
Yo usaría en ;lugar de &&; no hay forma de export FOO=barque falle
Keith Thompson
1
@MartyMacGyver: &&ejecuta el comando izquierdo, luego ejecuta el comando derecho solo si el comando izquierdo tuvo éxito. ;ejecuta ambos comandos incondicionalmente. El cmd.exeequivalente de Windows por lotes ( ) de ;es &.
Keith Thompson
2
En zsh, parece que no necesito la exportación para esta versión: (FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOOrendimientos FOO=XXX\nFOO=\n.
rampion
3
@PopePoopinpants: ¿por qué no usar source(aka .) en ese caso? Además, los backticks ya no deberían usarse en estos días y esta es una de las razones por las cuales, el uso $(command)es muuuucho más seguro.
0xC0000022L
3
Tan simple, pero tan elegante. Y me gusta su respuesta mejor que la respuesta aceptada, ya que comenzará un sub shell igual al actual (que puede no ser bashpero podría ser otra cosa, por ejemplo dash) y no tengo ningún problema si debo usar comillas dentro del comando args ( someargs).
Mecki
45

También puedes usar eval:

FOO=bar eval 'somecommand someargs | somecommand2'

Como esta respuesta evalno parece complacer a todos, permítanme aclarar algo: cuando se usa tal como está escrita, con comillas simples , es perfectamente segura. Es bueno, ya que no iniciará un proceso externo (como la respuesta aceptada) ni ejecutará los comandos en una subshell adicional (como la otra respuesta).

A medida que obtenemos algunas vistas regulares, probablemente sea bueno dar una alternativa evalque complazca a todos y que tenga todos los beneficios (¡y quizás incluso más!) De este rápido eval"truco". ¡Solo usa una función! Defina una función con todos sus comandos:

mypipe() {
    somecommand someargs | somecommand2
}

y ejecútelo con sus variables de entorno como esta:

FOO=bar mypipe
gniourf_gniourf
fuente
77
@Alfe: ¿También rechazaste la respuesta aceptada? porque exhibe los mismos "problemas" que eval.
gniourf_gniourf
10
@Alfe: desafortunadamente no estoy de acuerdo con tu crítica. Este comando es perfectamente seguro. Realmente suenas como un tipo que una vez leyó evales malvado sin entender de qué se trata el mal eval. Y tal vez no estás realmente entendiendo esta respuesta después de todo (y realmente no tiene nada de malo). En el mismo nivel: ¿diría que lses malo porque for file in $(ls)es malo? (y sí, no rechazó la respuesta aceptada y tampoco dejó un comentario). SO es un lugar tan extraño y absurdo a veces.
gniourf_gniourf
77
@Alfe: cuando digo que realmenteevaleval suenas como un chico que una vez leyó que es malvado sin entender de qué se trata , me refiero a tu oración: esta respuesta carece de todas las advertencias y explicaciones necesarias al hablar eval. evalno es malo ni peligroso; no más bash -c.
gniourf_gniourf
1
Dejando a un lado los votos, el comentario proporcionado @Alfe de alguna manera implica que la respuesta aceptada es de alguna manera más segura. Lo que hubiera sido más útil hubiera sido para usted describir lo que considera inseguro sobre el uso de eval. En la respuesta, siempre que los argumentos se hayan citado solo para protegerlos de la expansión variable, por lo que no veo ningún problema con la respuesta.
Brett Ryan
Eliminé mis comentarios para concentrar mi preocupación en un nuevo comentario: evales un problema de seguridad en general (como bash -cpero menos obvio), por lo que los peligros deben mencionarse en una respuesta que proponga su uso. Los usuarios descuidados pueden tomar la respuesta ( FOO=bar eval …) y aplicarla a su situación para generar problemas. Pero obviamente era más importante para el que respondía averiguar si rechacé su respuesta y / u otras respuestas que mejorar algo. Como escribí antes, la justicia no debería ser la principal preocupación; no ser peor que cualquier otra respuesta dada también es irrelevante.
Alfe
12

Uso env.

Por ejemplo, env FOO=BAR command. Tenga en cuenta que las variables de entorno se restaurarán / modificarán nuevamente cuando commandfinalice la ejecución.

Solo tenga cuidado con la sustitución de shell, es decir, si desea hacer referencia $FOOexplícitamente en la misma línea de comando, es posible que deba escapar para que su intérprete de shell no realice la sustitución antes de que se ejecute env.

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

Use un script de shell:

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

> ./myscript
Spencer Rathbun
fuente
13
Aún lo necesitas export; de lo contrario $FOOserá una variable de shell, no una variable de entorno, y por lo tanto no será visible para somecommando somecommand2.
Keith Thompson
Funcionaría, pero anula el propósito de tener un comando de una línea (estoy tratando de aprender formas más creativas para evitar líneas múltiples y / o scripts para casos relativamente simples). Y lo que dijo @Keith, aunque al menos la exportación se mantendría limitada al script.
MartyMacGyver 01 de