La définition d'une variable d'environnement avant une commande dans Bash ne fonctionne pas pour la deuxième commande dans un canal

351

Dans un shell donné, normalement je définirais une ou plusieurs variables, puis exécuterais une commande. Récemment, j'ai découvert le concept de l'ajout d'une définition de variable à une commande:

FOO=bar somecommand someargs

Cela fonctionne ... en quelque sorte. Cela ne fonctionne pas lorsque vous modifiez une variable LC_ * (qui semble affecter la commande, mais pas ses arguments, par exemple, les plages de caractères '[az]') ou lorsque vous redirigez la sortie vers une autre commande:

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

Je peux également ajouter une commande2 à "FOO = bar", ce qui fonctionne, mais qui ajoute une duplication indésirable, et cela n'aide pas avec les arguments qui sont interprétés en fonction de la variable (par exemple, '[az]').

Alors, quelle est la bonne façon de le faire sur une seule ligne?

Je pense à quelque chose de l'ordre de:

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

J'ai eu plein de bonnes réponses! Le but est de garder cette ligne unique, de préférence sans utiliser "d'export". La méthode utilisant un appel à Bash était globalement la meilleure, bien que la version entre parenthèses avec "export" soit un peu plus compacte. La méthode d'utilisation de la redirection plutôt que d'un tuyau est également intéressante.

MartyMacGyver
la source
1
(T=$(date) echo $T)fonctionnera
vp_arth
Dans le contexte de scripts multiplateformes (y compris windows) ou de projets basés sur npm (js ou autre), vous voudrez peut-être jeter un œil au module cross-env .
Frank Nocke
5
J'espérais que l'une des réponses expliquerait également pourquoi ce seul type de travail fonctionne, c'est-à-dire pourquoi ce n'est pas équivalent à exporter la variable avant l'appel.
Brecht Machiels
4
Le pourquoi est expliqué ici: stackoverflow.com/questions/13998075/…
Brecht Machiels

Réponses:

317
FOO=bar bash -c 'somecommand someargs | somecommand2'
En pause jusqu'à nouvel ordre.
la source
2
Cela répond à mes critères (une ligne sans avoir besoin de "l'exportation") ... Je suppose qu'il n'y a aucun moyen de le faire sans appeler "bash -c" (par exemple, l'utilisation créative des parenthèses)?
MartyMacGyver
1
@MartyMacGyver: Aucun que je puisse penser. Cela ne fonctionnera pas non plus avec des accolades.
pause jusqu'à nouvel ordre.
7
Notez que si vous devez exécuter votre somecommandas sudo, vous devez passer le -Edrapeau sudo pour passer à travers les variables. Parce que les variables peuvent introduire des vulnérabilités. stackoverflow.com/a/8633575/1695680
ThorSummoner
11
Notez que si votre commande a déjà deux niveaux de guillemets, cette méthode devient extrêmement insatisfaisante à cause de l'enfer des guillemets. Dans cette situation, l'exportation en sous-coque est bien meilleure.
Pushpendre
Odd: sous OSX, FOO_X=foox bash -c 'echo $FOO_X'fonctionne comme prévu mais avec des noms var spécifiques, il échoue: DYLD_X=foox bash -c 'echo $DYLD_X'échos en blanc. les deux fonctionnent en utilisant evalau lieu debash -c
mwag
209

Que diriez-vous d'exporter la variable, mais uniquement à l'intérieur du sous-shell?:

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

Keith a un point, pour exécuter inconditionnellement les commandes, procédez comme suit:

(export FOO=bar; somecommand someargs | somecommand2)
0xC0000022L
la source
15
J'utiliserais ;plutôt que &&; il n'y a aucun moyen export FOO=bard'échouer.
Keith Thompson
1
@MartyMacGyver: &&exécute la commande gauche, puis exécute la commande droite uniquement si la commande gauche a réussi. ;exécute les deux commandes sans condition. L' cmd.exeéquivalent Windows batch ( ) de ;est &.
Keith Thompson
2
Dans zsh, je ne semble pas avoir besoin de l'export pour cette version: les (FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOOrendements FOO=XXX\nFOO=\n.
rampion
3
@PopePoopinpants: pourquoi ne pas utiliser source(aka .) dans ce cas? De plus, les backticks ne devraient plus être utilisés de nos jours et c'est l'une des raisons pour lesquelles, l'utilisation $(command)est waaaaay plus sûre.
0xC0000022L
3
Si simple mais si élégant. Et j'aime mieux votre réponse que la réponse acceptée, car elle démarrera un sous-shell égal à mon actuel (qui peut ne pas l'être bashmais pourrait être autre chose, par exemple dash) et je n'ai aucun problème si je dois utiliser des guillemets dans la commande args ( someargs).
Mecki
45

Vous pouvez également utiliser eval:

FOO=bar eval 'somecommand someargs | somecommand2'

Étant donné que cette réponse evalne semble pas plaire à tout le monde, permettez-moi de clarifier une chose: lorsqu'elle est utilisée telle qu'elle est écrite, avec les guillemets simples , elle est parfaitement sûre. C'est bien car il ne lancera pas de processus externe (comme la réponse acceptée) et n'exécutera pas les commandes dans un sous-shell supplémentaire (comme l'autre réponse).

Comme nous obtenons quelques vues régulières, il est probablement bon de donner une alternative à evalcela qui plaira à tout le monde, et a tous les avantages (et peut-être même plus!) De ce rapide eval"truc". Utilisez simplement une fonction! Définissez une fonction avec toutes vos commandes:

mypipe() {
    somecommand someargs | somecommand2
}

et l'exécuter avec vos variables d'environnement comme ceci:

FOO=bar mypipe
gniourf_gniourf
la source
7
@Alfe: Avez-vous également rejeté la réponse acceptée? car il présente les mêmes «problèmes» que eval.
gniourf_gniourf
10
@Alfe: malheureusement, je ne suis pas d'accord avec votre critique. Cette commande est parfaitement sûre. On dirait vraiment un gars qui a déjà lu evalest mal sans comprendre ce qui fait mal eval. Et peut-être que vous ne comprenez pas vraiment cette réponse après tout (et il n'y a vraiment rien de mal à cela). Sur le même plan: diriez-vous que lsc'est mauvais parce que for file in $(ls)c'est mauvais? (et oui, vous n'avez pas dévalorisé la réponse acceptée, et vous n'avez pas laissé de commentaire non plus). SO est parfois un endroit si étrange et absurde.
gniourf_gniourf
7
@Alfe: quand je dis que tuevaleval parles vraiment comme un gars qui a déjà lu est mal sans comprendre de quoi il s'agit , je me réfère à ta phrase: cette réponse manque de tous les avertissements et explications nécessaires quandeval tu parles . evaln'est ni mauvais ni dangereux; pas plus bash -c.
gniourf_gniourf
1
Mis à part les votes, le commentaire fourni @Alfe implique en quelque sorte que la réponse acceptée est en quelque sorte plus sûre. Ce qui aurait été plus utile aurait été que vous décriviez ce que vous croyez dangereux à propos de l'utilisation de eval. Dans la réponse, à condition que les arguments aient été cités, protégeant contre l'expansion variable, je ne vois donc aucun problème avec la réponse.
Brett Ryan
J'ai supprimé mes commentaires pour concentrer ma préoccupation dans un nouveau commentaire: evalest un problème de sécurité en général (comme bash -cmais moins évident), donc les dangers doivent être mentionnés dans une réponse proposant son utilisation. Les utilisateurs imprudents peuvent prendre la réponse ( FOO=bar eval …) et l'appliquer à leur situation afin qu'elle pose problème. Mais il était évidemment plus important pour le répondeur de déterminer si j'avais rétrogradé ses réponses et / ou d'autres réponses que d'améliorer quoi que ce soit. Comme je l'ai écrit auparavant, l'équité ne devrait pas être la principale préoccupation; être pas pire que toute autre réponse donnée est également sans importance.
Alfe
12

Utilisez env.

Par exemple env FOO=BAR command,. Notez que les variables d'environnement seront à nouveau restaurées / inchangées une fois l' commandexécution terminée.

Faites juste attention à ce que la substitution de shell se produise, c'est-à-dire que si vous voulez référencer $FOOexplicitement sur la même ligne de commande, vous devrez peut-être l'échapper afin que votre interpréteur de shell n'effectue pas la substitution avant son exécution env.

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

Utilisez un script shell:

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

> ./myscript
Spencer Rathbun
la source
13
Vous en avez encore besoin export; sinon $FOOsera une variable shell, pas une variable d'environnement, et donc pas visible pour somecommandou somecommand2.
Keith Thompson
Cela fonctionnerait, mais cela va à l'encontre du but d'avoir une commande sur une seule ligne (j'essaie d'apprendre des façons plus créatives d'éviter les multilignes et / ou les scripts pour des cas relativement simples). Et ce que @Keith a dit, bien qu'au moins l'exportation resterait limitée au script.
MartyMacGyver