Dlaczego ustawianie zmiennej przed poleceniem jest legalne w bash?

68

Właśnie spotkałem kilka odpowiedzi, takich jak parsowanie pliku tekstowego z ogranicznikami ... używających konstrukcji:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

gdzie IFSzmienna jest ustawiona przed readpoleceniem.

Czytałem odniesienie do bash, ale nie mogę zrozumieć, dlaczego jest to legalne.

próbowałem

$ x="once upon" y="a time" echo $x $y

z wiersza polecenia bash, ale nic nie zostało powtórzone. Czy ktoś może wskazać mi, gdzie ta składnia jest zdefiniowana w odwołaniu, co pozwala na takie ustawienie zmiennej IFS? Czy to przypadek szczególny, czy mogę zrobić coś podobnego z innymi zmiennymi?

Mike Lippert
źródło

Odpowiedzi:

69

Jest pod gramatyką powłoki, proste polecenia (wyróżnienie dodane):

Proste polecenie jest sekwencją opcjonalnych przypisań zmiennych, po których następują rozdzielone spacjami słowa i przekierowania , i kończy je operator sterujący. Pierwsze słowo określa polecenie do wykonania, i jest przekazywana jako argument zerowy. Pozostałe słowa są przekazywane jako argumenty do wywołanego polecenia.

Możesz przekazać dowolną zmienną. Twój echoprzykład nie działa, ponieważ zmienne są przekazywane do polecenia, a nie ustawione w powłoce. Powłoka rozszerza się $xi $y przed wywołaniem polecenia. Działa to na przykład:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time
derobert
źródło
Dzięki! Zanim zapytałem, zrobiłem dużo googlingu i próbowałem dowiedzieć się, gdzie jest napisane w referencji: bez powodzenia. Staram się poprawić pisanie skryptów bash, a twoja odpowiedź pomaga.
Mike Lippert
1
Hmm, myślę, że muszę znaleźć lepsze odniesienie, moje (patrz link powyżej) nie mówi, że nie ma sekcji gramatyki muszli.
Mike Lippert
1
@MikeLippert Patrz 3.7.4 w tym odnośniku („Środowisko dla każdej prostej komendy lub funkcji można tymczasowo rozszerzyć, poprzedzając ją przypisaniami parametrów”). Myślę, że referencje pochodzą ze starszej wersji bash. Właśnie uruchomiłem man bashsystem ...
derobert
29

Zdefiniowane zmienne stają się zmiennymi środowiskowymi w procesie rozwidlenia.

Jeśli uciekniesz

A="b" echo $A

następnie bash najpierw rozwija $Asię, ""a następnie uruchamia

A="b" echo

Oto poprawny sposób:

x="once upon" y="a time" bash -c 'echo $x $y'

Zwróć uwagę na pojedyncze cytaty, w bash -cprzeciwnym razie masz taki sam problem jak powyżej.

Tak więc twój przykład pętli jest legalny, ponieważ wbudowane polecenie bash „czytaj” szuka IFS w zmiennych środowiskowych i znajduje ,. W związku z tym,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

wydrukuje TEST is and I is test

Wreszcie, jeśli chodzi o składnię, w pętli for oczekiwany jest ciąg znaków. Dlatego musiałem użyć odwróconych zwrotów, aby przekształcić je w polecenie. Jednak podczas gdy pętle oczekują składni poleceń, takich jak IFS=, read xx yy zz.

Mike Fairhurst
źródło
1
Dzięki, nauczyłem się nieco więcej, niż poprosiłem o twoją odpowiedź. Głosowałbym również za Twoją odpowiedzią, ale nie mam jeszcze pozwolenia i oznaczyłem zaakceptowaną odpowiedź na pierwszym.
Mike Lippert
1
Dzięki, właśnie na to liczyłem. A głosowanie jest mniej znaczące niż wysłuchanie twojego uznania!
Mike Fairhurst
Aby wyjaśnić swój komentarz w pierwszym wierszu kodu: Tak, ze Azmienną zmienną bash rozwija się $Ado pustego ciągu, ale aby uniknąć pomyłek, nie użyłbym tego, ""ponieważ kod nie jest równoważny A="b" echo "". Nie będzie argumentu echo.
pabouk
8

man bash

ŚRODOWISKO

[...] Środowisko dla dowolnej prostej komendy lub funkcji można tymczasowo rozszerzyć, poprzedzając je przypisaniami parametrów, jak opisano powyżej w PARAMETRACH. Te instrukcje przypisania wpływają tylko na środowisko widziane przez to polecenie.

Zmienne są rozwijane przed przypisaniem zmiennej. Z oczywistego powodu var=xdziałałoby to również w drugą stronę, ale var=$othervarnie działało . Czyli twoja $xjest potrzebna, zanim będzie dostępna. Ale to nie jest główny problem. Główny problem polega na tym, że wiersz poleceń może być modyfikowany tylko przez środowisko powłoki, ale przypisanie nie staje się częścią środowiska powłoki.

Miksujesz z funkcjami: chcesz zastąpić wiersz poleceń, ale umieść definicję zmiennej w środowisku poleceń. Powłoka musi zastępować wiersz poleceń. Wywołane polecenie musi jawnie wykorzystywać środowisko. To, czy i jak to się robi, zależy od polecenia.

Zaletą tego użycia jest to, że można ustawić środowisko dla podprocesu bez wpływu na środowisko powłoki.

x="once upon" y="a time" bash -c 'echo $x $y'

działa zgodnie z oczekiwaniami, ponieważ w takim przypadku obie funkcje są łączone: zamiana wiersza poleceń nie jest wykonywana przez powłokę wywołującą, ale przez powłokę podprocesu.

Hauke ​​Laging
źródło
1
Jest to nieco bardziej subtelne, ponieważ przykład działa również x="once upon" y="a time" eval 'echo $x $y'wtedy, gdy nie jest zaangażowany żaden podproces, ponieważ evaljest wbudowany. Chyba odpowiedni cytat ze strony podręcznika brzmi The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. Biorąc pod uwagę przykład pytania, musi być tak, ponieważ readjest ono również wbudowane i działa ze zmienionym tymczasowo stanem IFS.
David Ongaro,
4

Komenda podasz jest inna, bo $xi $ysą rozszerzane , zanim w echoseriach dowodzenia, więc ich wartości w bieżącej powłoce są wykorzystywane, a nie wartości, które echobyłyby widoczne w jego otoczeniu, gdyby spojrzeć.

chepner
źródło
Jak cokolwiek w wierszu poleceń można rozwinąć po uruchomieniu polecenia ...?
Hauke ​​Laging
Zadania polecenia do pre- xi ysą dla środowiska, które echodziała w, a nie środowisko, w którym argumenty echosą rozszerzane. Ponieważ IFS=, read xx yy zzcały ciąg jest odczytywany bez readpolecenia przez polecenie. Potem , że ciąg jest podzielony według wartości IFSz odpowiednich elementów przypisanych xx, yyi zz.
chepner
Chciałem tylko zaznaczyć, że sformułowanie „rozwinięte przed uruchomieniem polecenia” nie ma większego sensu, ponieważ po rozpoczęciu polecenia nic już nie jest rozszerzane. Co więcej: czy nie spojrzałeś ani razu na moją odpowiedź, czy jednak uważasz, że potrzebuję wyjaśnienia, co się dzieje ...?
Hauke ​​Laging
1
Nie twierdziłem, że potrzebujesz wyjaśnienia, ani że nic nie można rozszerzyć po uruchomieniu polecenia. Prawdą jest jednak, że bashnajpierw analizuje podaną linię poleceń, rozpoznaje, że istnieją dwa przypisania zmiennych do środowiska nadchodzącej komendy, identyfikuje komendę do uruchomienia ( echo), rozszerza parametry znalezione w argumentach, a następnie uruchamia komendę echoz rozszerzonymi argumentami.
chepner
W przypadku echonie byłem pewien, czy może „zobaczyć” zmienną, ponieważ jest to wbudowane polecenie i dlatego nie działa w podpowłoce, która mogłaby mieć własne środowisko. Ale wypróbowałem to za evalpomocą wbudowanego i rzeczywiście o tym wie. Np. Spróbuj, a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $aktóry pokazuje, że ajest ustawiony tylko wewnątrz, evalmimo że pid jest taki sam, a środowisko nie zmienia się podczas ewaluacji! (Aby uzyskać dostęp /proc, musisz uruchomić to pod Linuksem.) Wygląda na to, że bash robi tutaj dodatkową magię.
David Ongaro,
2

Idę po szerszy obraz „ dlaczego to jest legalne”

Odpowiedź: Aby móc wywoływać lub wywoływać program i do tego wywołania używaj tylko zmiennej z określoną zmienną.

Na przykład: masz parametr połączenia z bazą danych o nazwie „db_connection” i zwykle podajesz „test” jako nazwę testowego połączenia z bazą danych. W rzeczywistości możesz nawet ustawić go jako domyślny, którego nie musisz wtedy jawnie przekazywać. Czasami jednak chcesz pracować z bazą danych ci. Podajesz więc parametr jako „ci”, a następnie wywoływany program używa tego parametru bazy danych jako nazwy bazy danych, która ma być używana dla wszystkich wywołań bazy danych. W przypadku następnego uruchomienia, jeśli nie powtórzysz podejścia i po prostu wywołasz program, zmienna powróci do poprzedniej wartości domyślnej.

Michael Durrant
źródło
0

Możesz także użyć ;. Zostanie to ocenione wcześniej, ponieważ jest to separator poleceń.

x="once upon" y="a time"; echo $x $y
camabeh
źródło
1
Tworzy to dwie zmienne powłoki i nie tworzy zmiennych środowiskowych w danym narzędziu. To jest zupełnie inna sprawa. Istnieje również efekt uboczny, że bieżąca powłoka zawiera teraz nowe zmienne.
Kusalananda