Das Festlegen einer Umgebungsvariablen vor einem Befehl in Bash funktioniert nicht für den zweiten Befehl in einer Pipe

351

In einer bestimmten Shell würde ich normalerweise eine Variable oder Variablen festlegen und dann einen Befehl ausführen. Kürzlich habe ich das Konzept kennengelernt, einem Befehl eine Variablendefinition voranzustellen:

FOO=bar somecommand someargs

Das funktioniert ... irgendwie. Es funktioniert nicht, wenn Sie eine LC_ * -Variable ändern (was den Befehl zu beeinflussen scheint, aber nicht seine Argumente, z. B. '[az]' - Zeichenbereiche) oder wenn Sie die Ausgabe folgendermaßen an einen anderen Befehl weiterleiten:

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

Ich kann somecommand2 auch mit "FOO = bar" voranstellen, was funktioniert, aber unerwünschte Duplikate hinzufügt, und es hilft nicht bei Argumenten, die abhängig von der Variablen interpretiert werden (zum Beispiel '[az]').

Was ist also ein guter Weg, dies in einer einzigen Zeile zu tun?

Ich denke etwas in der Größenordnung von:

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

Ich habe viele gute Antworten bekommen! Das Ziel ist es, dies einzeilig zu halten, vorzugsweise ohne "Export" zu verwenden. Die Methode mit einem Aufruf von Bash war insgesamt am besten, obwohl die in Klammern gesetzte Version mit "Export" etwas kompakter war. Interessant ist auch die Methode, die Umleitung anstelle einer Pipe zu verwenden.

MartyMacGyver
quelle
1
(T=$(date) echo $T)wird funktionieren
vp_arth
Im Zusammenhang mit plattformübergreifenden (einschließlich Windows) Skripten oder npm-basierten Projekten (js oder sonst) sollten Sie sich das Cross-Env-Modul ansehen .
Frank Nocke
5
Ich hatte gehofft, eine der Antworten würde auch erklären, warum dies nur funktioniert, dh warum es nicht gleichbedeutend ist mit dem Exportieren der Variablen vor dem Aufruf.
Brecht Machiels
4
Das Warum wird hier erklärt: stackoverflow.com/questions/13998075/…
Brecht Machiels

Antworten:

317
FOO=bar bash -c 'somecommand someargs | somecommand2'
Bis auf weiteres angehalten.
quelle
2
Dies erfüllt meine Kriterien (Einzeiler ohne "Export") ... Ich nehme an, es gibt keine Möglichkeit, dies zu tun, ohne "bash -c" aufzurufen (z. B. kreative Verwendung von Klammern)?
MartyMacGyver
1
@MartyMacGyver: Keine, an die ich denken kann. Es funktioniert auch nicht mit geschweiften Klammern.
Bis auf weiteres angehalten.
7
Beachten Sie, dass Sie, wenn Sie Ihr somecommandals sudo ausführen müssen, sudo das -EFlag übergeben müssen, um durch Variablen zu übergeben. Weil Variablen Schwachstellen verursachen können. stackoverflow.com/a/8633575/1695680
ThorSummoner
11
Beachten Sie, dass diese Methode aufgrund der Anführungszeichen-Hölle äußerst unbefriedigend wird, wenn Ihr Befehl bereits zwei Anführungszeichen enthält. In dieser Situation ist der Export in Unterschalen viel besser.
Pushpendre
Seltsam: Funktioniert unter OSX FOO_X=foox bash -c 'echo $FOO_X'wie erwartet, schlägt jedoch bei bestimmten Variablennamen fehl: DYLD_X=foox bash -c 'echo $DYLD_X'Echos leer. beide arbeiten mit evalanstelle vonbash -c
mwag
209

Wie wäre es mit dem Exportieren der Variablen, aber nur innerhalb der Subshell?:

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

Keith hat einen Punkt, um die Befehle bedingungslos auszuführen, tun Sie dies:

(export FOO=bar; somecommand someargs | somecommand2)
0xC0000022L
quelle
15
Ich würde ;eher verwenden als &&; Es gibt keinen Weg export FOO=bar, der scheitern wird.
Keith Thompson
1
@MartyMacGyver: Führt &&den linken Befehl aus und führt den rechten Befehl nur aus, wenn der linke Befehl erfolgreich war. ;führt beide Befehle bedingungslos aus. Das Windows-Batch ( cmd.exe) -Äquivalent von ;ist &.
Keith Thompson
2
In zsh brauche ich den Export für diese Version nicht: (FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOOErträge FOO=XXX\nFOO=\n.
Rampion
3
@PopePoopinpants: Warum nicht source(aka .) in diesem Fall verwenden? Außerdem sollten die Backticks heutzutage nicht mehr verwendet werden, und dies ist einer der Gründe, warum die Verwendung $(command)waaaaay sicherer ist.
0xC0000022L
3
So einfach und doch so elegant. Und ich mag Ihre Antwort besser als die akzeptierte Antwort, da sie eine Sub-Shell startet, die meiner aktuellen entspricht (die möglicherweise nicht, bashaber etwas anderes sein könnte, z. B. dash), und ich habe keine Probleme, wenn ich Anführungszeichen verwenden muss innerhalb des Befehls args ( someargs).
Mecki
45

Sie können auch verwenden eval:

FOO=bar eval 'somecommand someargs | somecommand2'

Da diese Antwort mit evalnicht jedem zu gefallen scheint, lassen Sie mich etwas klarstellen: Wenn sie wie geschrieben mit den einfachen Anführungszeichen verwendet wird , ist sie absolut sicher. Es ist gut, da es weder einen externen Prozess startet (wie die akzeptierte Antwort) noch die Befehle in einer zusätzlichen Unterschale ausführt (wie die andere Antwort).

Da wir ein paar regelmäßige Ansichten erhalten, ist es wahrscheinlich gut, eine Alternative zu geben eval, die allen gefällt und alle Vorteile (und vielleicht sogar noch mehr!) Dieses schnellen eval„Tricks“ bietet . Verwenden Sie einfach eine Funktion! Definieren Sie eine Funktion mit all Ihren Befehlen:

mypipe() {
    somecommand someargs | somecommand2
}

und führen Sie es mit Ihren Umgebungsvariablen wie folgt aus:

FOO=bar mypipe
gniourf_gniourf
quelle
7
@Alfe: Hast du auch die akzeptierte Antwort abgelehnt? weil es die gleichen "Probleme" aufweist wie eval.
gniourf_gniourf
10
@ Alfe: Leider stimme ich Ihrer Kritik nicht zu. Dieser Befehl ist absolut sicher. Du klingst wirklich wie ein Typ, der einmal gelesen hat, evalist böse, ohne zu verstehen, worum es böse ist eval. Und vielleicht verstehen Sie diese Antwort doch nicht wirklich (und daran ist wirklich nichts auszusetzen). Auf der gleichen Ebene: Würden Sie sagen, dass lsdas schlecht for file in $(ls)ist, weil es schlecht ist? (und ja, Sie haben die akzeptierte Antwort nicht abgelehnt und auch keinen Kommentar hinterlassen). SO ist manchmal so ein seltsamer und absurder Ort.
gniourf_gniourf
7
@Alfe: Wenn ich sage, dass Sie wirklich wie ein Typ klingen, der einmal gelesen hat, evalist böse, ohne zu verstehen, worum es böse ist eval, dann beziehe ich mich auf Ihren Satz: Dieser Antwort fehlen alle Warnungen und Erklärungen, die erforderlich sind, wenn Sie darüber sprechen eval. evalist nicht schlecht oder gefährlich; nicht mehr als bash -c.
gniourf_gniourf
1
Abgesehen von den Stimmen impliziert der Kommentar von @Alfe irgendwie, dass die akzeptierte Antwort irgendwie sicherer ist. Was hilfreicher gewesen wäre, wäre gewesen, wenn Sie beschrieben hätten, was Ihrer Meinung nach bei der Verwendung von unsicher ist eval. In der Antwort, sofern die Argumente in einfachen Anführungszeichen stehen, um vor variabler Erweiterung zu schützen, sehe ich kein Problem mit der Antwort.
Brett Ryan
Ich habe meine Kommentare entfernt, um mein Anliegen auf einen neuen Kommentar zu konzentrieren: evalIst ein Sicherheitsproblem im Allgemeinen (wie, bash -caber weniger offensichtlich), daher sollten die Gefahren in einer Antwort erwähnt werden, in der seine Verwendung vorgeschlagen wird. Unvorsichtige Benutzer können die Antwort ( FOO=bar eval …) nehmen und auf ihre Situation anwenden, so dass Probleme auftreten. Aber für den Antwortenden war es offensichtlich wichtiger herauszufinden, ob ich seine und / oder andere Antworten abgelehnt habe, als etwas zu verbessern. Wie ich bereits schrieb, sollte Fairness nicht das Hauptanliegen sein. Es ist auch unerheblich , nicht schlechter zu sein als jede andere gegebene Antwort.
Alfe
12

Verwenden env .

Zum Beispiel, env FOO=BAR command . Beachten Sie, dass die Umgebungsvariablen nach commandAbschluss der Ausführung wieder hergestellt / unverändert bleiben .

Seien Sie nur vorsichtig, wenn eine Shell-Substitution stattfindet. Wenn Sie also $FOOexplizit auf dieselbe Befehlszeile verweisen möchten , müssen Sie diese möglicherweise maskieren, damit Ihr Shell-Interpreter die Substitution nicht ausführt, bevor sie ausgeführt wird env.

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

Verwenden Sie ein Shell-Skript:

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

> ./myscript
Spencer Rathbun
quelle
13
Du brauchst noch export; Andernfalls handelt $FOOes sich um eine Shell-Variable, nicht um eine Umgebungsvariable, und daher für somecommandoder nicht sichtbar somecommand2.
Keith Thompson
Es würde funktionieren, aber es macht den Zweck eines einzeiligen Befehls zunichte (ich versuche, kreativere Methoden zu erlernen, um Mehrzeiler und / oder Skripte für relativ einfache Fälle zu vermeiden). Und was @Keith sagte, obwohl zumindest der Export auf das Skript beschränkt bleiben würde.
MartyMacGyver