Ustawienie zmiennej środowiskowej przed poleceniem w Bash nie działa dla drugiego polecenia w potoku

351

W danej powłoce normalnie ustawiłbym zmienną lub zmienne, a następnie uruchomiłem polecenie. Ostatnio dowiedziałem się o koncepcji przygotowania definicji zmiennej do polecenia:

FOO=bar somecommand someargs

To działa ... w pewnym sensie. Nie działa, gdy zmieniasz zmienną LC_ * (która wydaje się wpływać na polecenie, ale nie jego argumenty, na przykład zakresy znaków „[az]”) lub gdy przekazujesz dane wyjściowe do innego polecenia w ten sposób:

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

Mogę również dodać somecommand2 z „FOO = bar”, co działa, ale dodaje niechciane powielanie i nie pomaga w argumentach interpretowanych w zależności od zmiennej (na przykład „[az]”).

Więc jaki jest dobry sposób, aby to zrobić na jednej linii?

Myślę o czymś w kolejności:

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

Mam wiele dobrych odpowiedzi! Celem jest utrzymanie tego jednowarstwowego, najlepiej bez użycia „eksportu”. Metoda wykorzystująca wywołanie Basha była ogólnie najlepsza, chociaż wersja w nawiasach z „eksportem” była nieco bardziej zwarta. Interesująca jest również metoda przekierowania zamiast potoku.

MartyMacGyver
źródło
1
(T=$(date) echo $T)zadziała
vp_arth,
W kontekście skryptów wieloplatformowych (w tym Windows) lub projektów opartych na npm (js lub jeszcze), możesz rzucić okiem na moduł cross-env .
Frank Nocke,
5
Miałem nadzieję, że jedna z odpowiedzi wyjaśni również, dlaczego ten jedyny rodzaj działa, tj. Dlaczego nie jest równoważny z eksportowaniem zmiennej przed wywołaniem.
Brecht Machiels
4
Dlaczego wyjaśniono tutaj: stackoverflow.com/questions/13998075/...
Brecht Machiels

Odpowiedzi:

317
FOO=bar bash -c 'somecommand someargs | somecommand2'
Wstrzymano do odwołania.
źródło
2
Spełnia to moje kryteria (jednolinijka bez potrzeby „eksportu”) ... Rozumiem, że nie można tego zrobić bez wywołania „bash -c” (np. Twórcze użycie nawiasów)?
MartyMacGyver
1
@MartyMacGyver: Brak, o którym mogę myśleć. Nie zadziała również z nawiasami klamrowymi.
Wstrzymano do odwołania.
7
Zauważ, że jeśli chcesz uruchomić swoje somecommandsudo, musisz przekazać -Eflagę sudo, aby przejść przez zmienne. Ponieważ zmienne mogą wprowadzać podatności. stackoverflow.com/a/8633575/1695680
ThorSummoner
11
Zauważ, że jeśli twoje polecenie ma już dwa poziomy cytatów, wówczas ta metoda staje się wyjątkowo niezadowalająca z powodu cytowania piekła. W tej sytuacji eksport w podpowłoce jest znacznie lepszy.
Pushpendre
Dziwne: w OSX FOO_X=foox bash -c 'echo $FOO_X'działa zgodnie z oczekiwaniami, ale przy określonych nazwach DYLD_X=foox bash -c 'echo $DYLD_X'zmiennych nie działa: echo puste. oba działają evalzamiastbash -c
mwag
209

Co powiesz na eksportowanie zmiennej, ale tylko w podpowłoce ?:

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

Keith ma rację, aby bezwarunkowo wykonać polecenia, wykonaj następujące czynności:

(export FOO=bar; somecommand someargs | somecommand2)
0xC0000022L
źródło
15
Wolałbym ;raczej niż &&; nie ma mowy, export FOO=barżeby upadł.
Keith Thompson
1
@MartyMacGyver: &&wykonuje lewe polecenie, a następnie wykonuje prawe polecenie tylko wtedy, gdy lewe polecenie się powiodło. ;wykonuje oba polecenia bezwarunkowo. Odpowiednikiem batch ( cmd.exe) systemu Windows ;jest &.
Keith Thompson
2
W Zsh nie potrzebuję eksportu dla tej wersji: (FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOOdaje FOO=XXX\nFOO=\n.
szaleństwo
3
@PopePoopinpants: dlaczego nie użyć source(aka .) w takim przypadku? Ponadto, backtyki nie powinny być już używane w dzisiejszych czasach i jest to jeden z powodów, dla których używanie $(command)jest bezpieczniejsze.
0xC0000022L
3
Taki prosty, ale taki elegancki. I podoba mi się twoja odpowiedź lepiej niż odpowiedź zaakceptowana, ponieważ uruchomi ona sub-powłokę równą mojej obecnej (która może nie być, bashale może być czymś innym, np. dash) I nie mam żadnych problemów, jeśli muszę użyć cudzysłowów w ramach polecenia args ( someargs).
Mecki
45

Możesz także użyć eval:

FOO=bar eval 'somecommand someargs | somecommand2'

Ponieważ odpowiedź na to pytanie evalnie wydaje się wszystkim podobać, pozwólcie, że coś wyjaśnię: w przypadku użycia w formie pisemnej z pojedynczymi cytatami jest całkowicie bezpieczna. Jest dobry, ponieważ nie uruchomi zewnętrznego procesu (jak zaakceptowana odpowiedź) ani nie wykona poleceń w dodatkowej podpowłoce (jak druga odpowiedź).

Ponieważ otrzymujemy kilka regularnych widoków, prawdopodobnie dobrze jest dać alternatywę eval, która zadowoli wszystkich i ma wszystkie zalety (a może nawet więcej!) Tej szybkiej eval„sztuczki”. Po prostu użyj funkcji! Zdefiniuj funkcję za pomocą wszystkich poleceń:

mypipe() {
    somecommand someargs | somecommand2
}

i wykonaj go ze swoimi zmiennymi środowiskowymi, takimi jak to:

FOO=bar mypipe
gniourf_gniourf
źródło
7
@Alfe: Czy głosowałeś również za zaakceptowaną odpowiedzią? ponieważ wykazuje takie same „problemy” jak eval.
gniourf_gniourf
10
@Alfe: niestety nie zgadzam się z twoją krytyką. To polecenie jest całkowicie bezpieczne. Naprawdę brzmisz jak facet, który kiedyś czytał, evaljest złem, nie rozumiejąc, o co mu chodzi eval. A może tak naprawdę nie rozumiesz tej odpowiedzi (i naprawdę nie ma w tym nic złego). Na tym samym poziomie: czy powiedziałbyś, że lsto źle, bo for file in $(ls)jest źle? (i tak, nie głosowałeś zaakceptowanej odpowiedzi i nie zostawiłeś komentarza). SO jest czasem tak dziwnym i absurdalnym miejscem.
gniourf_gniourf
7
@Alfe: kiedy mówię, że naprawdęevaleval brzmisz jak facet, który kiedyś czytał, jest złem, nie rozumiejąc, co jest złego , odnoszę się do twojego zdania: w tej odpowiedzi brakuje wszystkich ostrzeżeń i wyjaśnień koniecznych przy mówieniu eval. evalnie jest zły ani niebezpieczny; nie więcej niż bash -c.
gniourf_gniourf
1
Głosy na bok, komentarz podany @Alfe w jakiś sposób sugeruje, że zaakceptowana odpowiedź jest w jakiś sposób bezpieczniejsza. Bardziej pomocne byłoby opisanie tego, co uważasz za niebezpieczne w użyciu eval. W podanej odpowiedzi argumenty zostały cytowane pojedynczo, chroniąc przed zmienną ekspansją, więc nie widzę problemu z odpowiedzią.
Brett Ryan
Usunąłem swoje komentarze, aby skoncentrować się na jednym komentarzu: evalogólnie rzecz biorąc, jest to kwestia bezpieczeństwa (podobna, bash -cale mniej oczywista), więc o zagrożeniach należy wspomnieć w odpowiedzi proponującej jej użycie. Nieostrożni użytkownicy mogą wziąć odpowiedź ( FOO=bar eval …) i zastosować ją do swojej sytuacji, aby stwarzała problemy. Ale oczywiście dla osoby odpowiadającej ważniejsze było ustalenie, czy głosowałem za jego i / lub innymi odpowiedziami, niż cokolwiek ulepszyć. Jak pisałem wcześniej, uczciwość nie powinna być głównym zmartwieniem; bycie nie gorszym niż jakakolwiek inna odpowiedź również jest nieważne.
Alfe
12

Zastosowanie env.

Na przykład env FOO=BAR command. Pamiętaj, że zmienne środowiskowe zostaną przywrócone / niezmienione po commandzakończeniu wykonywania.

Uważaj tylko na substytucję powłoki, tzn. Jeśli chcesz $FOOjawnie odwoływać się do tego samego wiersza poleceń, być może będziesz musiał uciec przed nią, aby interpreter powłoki nie wykonał podstawienia przed uruchomieniem env.

$ export FOO=BAR
$ env FOO=FUBAR bash -c 'echo $FOO'
FUBAR
$ echo $FOO
BAR
Benjimin
źródło
-5

Użyj skryptu powłoki:

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

> ./myscript
Spencer Rathbun
źródło
13
Nadal potrzebujesz export; w przeciwnym razie $FOObędzie zmienną powłoki, a nie zmienną środowiskową, a zatem nie będzie widoczna dla somecommandlub somecommand2.
Keith Thompson
To działałoby, ale nie spełnia celu posiadania polecenia jednowierszowego (staram się nauczyć bardziej kreatywnych sposobów unikania wielowarstwowości i / lub skryptów w stosunkowo prostych przypadkach). I to, co powiedział @Keith, chociaż przynajmniej eksport pozostanie ograniczony do skryptu.
MartyMacGyver