Pracuję z tym:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
Mam taki skrypt jak poniżej:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
test1
echo "$e"
Który zwraca:
hello
4
Ale jeśli przypiszę wynik funkcji do zmiennej, zmienna globalna e
nie jest modyfikowana:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
ret=$(test1)
echo "$ret"
echo "$e"
Zwroty:
hello
2
Słyszałem o użyciu eval w tym przypadku, więc zrobiłem to w test1
:
eval 'e=4'
Ale ten sam wynik.
Czy możesz mi wyjaśnić, dlaczego nie jest modyfikowany? Jak mogę zapisać echo test1
funkcji w ret
i zmodyfikować również zmienną globalną?
bash
variables
global-variables
eval
harrison4
źródło
źródło
Odpowiedzi:
Kiedy używasz podstawiania poleceń (tj.
$(...)
Konstrukcji), tworzysz podpowłokę. Podpowłoki dziedziczą zmienne ze swoich powłok nadrzędnych, ale działa to tylko w jeden sposób - podpowłoka nie może modyfikować środowiska swojej powłoki nadrzędnej. Twoja zmiennae
jest umieszczona w podpowłoce, ale nie w powłoce nadrzędnej. Istnieją dwa sposoby przekazywania wartości z podpowłoki do jej elementu nadrzędnego. Najpierw możesz wyprowadzić coś na standardowe wyjście, a następnie przechwycić to za pomocą podstawienia polecenia:Daje:
W przypadku wartości liczbowej od 0 do 255 można użyć
return
do przekazania liczby jako statusu wyjścia:Daje:
źródło
setarray() { declare -ag "$1=(a b c)"; }
Podsumowanie
Twój przykład można zmodyfikować w następujący sposób, aby zarchiwizować pożądany efekt:
drukuje zgodnie z życzeniem:
Zwróć uwagę, że to rozwiązanie:
e=1000
też dla.$?
jeśli potrzebujesz$?
Jedyne złe efekty uboczne to:
bash
._
)_capture
prostu wymienić wszystkie wystąpień z3
innym (wyższym) numer.Poniższe (które jest dość długie, przepraszam za to), mam nadzieję, wyjaśnia, jak dostosować ten przepis również do innych skryptów.
Problem
wyjścia
podczas gdy pożądany wynik jest
Przyczyna problemu
Zmienne powłoki (lub ogólnie mówiąc, środowisko) są przekazywane z procesów rodzicielskich do procesów potomnych, ale nie odwrotnie.
Jeśli przechwytujesz dane wyjściowe, zwykle jest to uruchamiane w podpowłoce, więc przekazywanie zmiennych jest trudne.
Niektórzy nawet mówią, że nie da się tego naprawić. To jest złe, ale od dawna znany jest trudny do rozwiązania problem.
Istnieje kilka sposobów najlepszego rozwiązania tego problemu, zależy to od Twoich potrzeb.
Oto przewodnik krok po kroku, jak to zrobić.
Przekazywanie zmiennych do powłoki rodzicielskiej
Istnieje sposób przekazania zmiennych do powłoki rodzicielskiej. Jest to jednak niebezpieczna ścieżka, ponieważ ta używa
eval
. Jeśli zostanie to zrobione niewłaściwie, ryzykujesz wiele złych rzeczy. Ale jeśli zostanie to zrobione poprawnie, jest to całkowicie bezpieczne, pod warunkiem, że nie ma błędubash
.wydruki
Pamiętaj, że działa to również w przypadku niebezpiecznych rzeczy:
wydruki
Wynika to z
printf '%q'
tego, że cytuje wszystko w taki sposób, że można bezpiecznie ponownie użyć go w kontekście powłoki.Ale to jest ból w ...
To nie tylko wygląda brzydko, ale także jest dużo do pisania, więc jest podatne na błędy. Tylko jeden błąd i jesteś skazany, prawda?
Cóż, jesteśmy na poziomie powłoki, więc możesz to poprawić. Pomyśl tylko o interfejsie, który chcesz zobaczyć, a następnie możesz go zaimplementować.
Rozszerz, jak powłoka przetwarza rzeczy
Cofnijmy się o krok i zastanówmy się nad jakimś API, które pozwoli nam w łatwy sposób wyrazić to, co chcemy robić.
Cóż, co chcemy zrobić z
d()
funkcją?Chcemy przechwycić dane wyjściowe do zmiennej. OK, w takim razie zaimplementujmy API właśnie do tego:
Teraz zamiast pisać
możemy pisać
Cóż, wygląda na to, że niewiele się zmieniliśmy, ponieważ ponownie zmienne nie są przekazywane z powrotem
d
do powłoki nadrzędnej i musimy wpisać trochę więcej.Jednak teraz możemy rzucić na niego pełną moc powłoki, ponieważ jest ładnie opakowana w funkcję.
Pomyśl o łatwym do ponownego wykorzystania interfejsie
Po drugie, chcemy być SUCHY (nie powtarzaj się). Więc definitywnie nie chcemy pisać czegoś takiego
x
Tutaj jest nie tylko zbędne, jest podatny na błędy zawsze repeate we właściwym kontekście. A jeśli użyjesz go 1000 razy w skrypcie, a następnie dodasz zmienną? Zdecydowanie nie chcesz zmieniać wszystkich 1000 lokalizacji, do których dochodzi połączenied
.Więc zostaw to
x
daleko, żebyśmy mogli napisać:wyjścia
To już wygląda bardzo dobrze. (Ale nadal jest to,
local -n
które nie działa w oder commonbash
3.x)Unikaj zmian
d()
Ostatnie rozwiązanie ma kilka dużych wad:
d()
musi zostać zmienionyxcapture
Aby przekazać dane wyjściowe, musi użyć pewnych wewnętrznych szczegółów .output
, więc nigdy nie możemy jej przekazać._passback
Czy też możemy się tego pozbyć?
Oczywiście możemy! Jesteśmy w skorupie, więc jest wszystko, czego potrzebujemy, aby to zrobić.
Jeśli przyjrzysz się bliżej wezwaniu
eval
, zobaczysz, że mamy 100% kontrolę w tej lokalizacji. „Wewnątrz”eval
znajdujemy się w podpowłoce, więc możemy robić wszystko, co chcemy, bez obawy, że zrobimy coś złego w powłoce rodzicielskiej.Tak, fajnie, więc dodajmy kolejne opakowanie, teraz bezpośrednio w
eval
:wydruki
Jednak to znowu ma pewną poważną wadę:
!DO NOT USE!
markery są tam, bo jest to bardzo zły stan wyścig w tym, czego nie można zobaczyć w prosty sposób:>(printf ..)
jest praca w tle. Może więc nadal działać, gdy_passback x
jest uruchomiony.sleep 1;
przedprintf
lub_passback
._xcapture a d; echo
następnie odpowiednio wyprowadzax
luba
pierwszy._passback x
należy go częścią_xcapture
, ponieważ utrudnia to ponowne użycie tego przepisu.$(cat)
), ale ponieważ jest to rozwiązanie,!DO NOT USE!
wybrałem najkrótszą trasę.Jednak pokazuje to, że możemy to zrobić bez modyfikacji
d()
(i bezlocal -n
)!Zwróć uwagę, że nie jest to wcale konieczne
_xcapture
, ponieważ mogliśmy wszystko zapisać w plikueval
.Jednak robienie tego zwykle nie jest zbyt czytelne. A jeśli wrócisz do swojego scenariusza za kilka lat, prawdopodobnie będziesz chciał móc go ponownie przeczytać bez większych problemów.
Napraw wyścig
Teraz naprawmy stan wyścigu.
Sztuczka może polegać na tym, aby poczekać, aż
printf
zamknie się STDOUT, a następnie wyprowadzićx
.Istnieje wiele sposobów archiwizacji tego:
Podążanie za ostatnią ścieżką mogłoby wyglądać (zauważ, że robi to
printf
ostatnią, ponieważ działa to lepiej tutaj):wyjścia
Dlaczego jest to poprawne?
_passback x
bezpośrednio rozmawia z STDOUT.>&3
.$("${@:2}" 3<&-; _passback x >&3)
wykończenia po_passback
, kiedy podpowłoki zamyka standardowe wyjście.printf
nie może się to zdarzyć przed_passback
, niezależnie od tego, jak długo_passback
to potrwa.printf
polecenie nie jest wykonywane przed złożeniem całego wiersza polecenia, więc nie możemy zobaczyć artefaktówprintf
, niezależnie od tego , jakprintf
jest zaimplementowane.Dlatego najpierw
_passback
wykonuje, a następnieprintf
.To rozwiązuje wyścig, poświęcając jeden ustalony deskryptor pliku 3. Możesz oczywiście wybrać inny deskryptor pliku w przypadku, gdy FD3 nie jest wolny w twoim skrypcie.
Proszę również zwrócić uwagę na to,
3<&-
co chroni FD3 przed przekazaniem do funkcji.Uczyń to bardziej ogólnym
_capture
zawiera części, do których należąd()
, co jest złe z punktu widzenia możliwości ponownego użycia. Jak to rozwiązać?Cóż, zrób to w desperacki sposób, wprowadzając jeszcze jedną rzecz, dodatkową funkcję, która musi zwrócić właściwe rzeczy, której nazwa pochodzi od pierwotnej funkcji z
_
dołączoną.Ta funkcja jest wywoływana po funkcji rzeczywistej i może wzmacniać rzeczy. W ten sposób można to odczytać jako adnotację, dzięki czemu jest bardzo czytelny:
nadal drukuje
Zezwól na dostęp do kodu zwrotnego
Brakuje tylko jednego bitu:
v=$(fn)
ustawia$?
to, cofn
wróciło. Więc prawdopodobnie też tego chcesz. Wymaga jednak większych poprawek:wydruki
Jest jeszcze wiele do zrobienia
_passback()
można wyeliminowaćpassback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
_capture()
można wyeliminować za pomocącapture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }
Rozwiązanie zanieczyszcza deskryptor pliku (tutaj 3), używając go wewnętrznie. Musisz o tym pamiętać, jeśli zdarzy Ci się zdać FD.
Zauważ, że
bash
4.1 i nowsze muszą{fd}
używać nieużywanego FD.(Być może dodam tutaj rozwiązanie, kiedy się pojawię.)
Zauważ, że dlatego używam oddzielnych funkcji, takich jak
_capture
, ponieważ upchnięcie tego wszystkiego w jedną linię jest możliwe, ale sprawia, że coraz trudniej jest przeczytać i zrozumiećByć może chcesz również przechwycić STDERR wywoływanej funkcji. Lub chcesz nawet przekazywać i przekazywać więcej niż jeden deskryptor pliku zi do zmiennych.
Nie mam jeszcze rozwiązania, jednak tutaj jest sposób na złapanie więcej niż jednego FD , więc prawdopodobnie możemy również w ten sposób przekazać zmienne.
Nie zapomnij również:
To musi wywołać funkcję powłoki, a nie polecenie zewnętrzne.
Ostatnie słowa
To nie jedyne możliwe rozwiązanie. To jeden przykład rozwiązania.
Jak zawsze masz wiele sposobów wyrażania rzeczy w powłoce. Więc nie krępuj się ulepszyć i znaleźć coś lepszego.
Przedstawione tutaj rozwiązanie jest dalekie od doskonałości:
bash
, więc prawdopodobnie trudno jest go przenieść na inne powłoki.Jednak myślę, że jest dość łatwy w użyciu:
źródło
Może możesz użyć pliku, zapisać do pliku wewnątrz funkcji, czytać z pliku po nim. Zmieniłem
e
na tablicę. W tym przykładzie puste miejsca są używane jako separatory podczas odczytu tablicy.Wynik:
źródło
Co robisz, wykonujesz test1
$(test1)
w powłoce podrzędnej (powłoce potomnej), a powłoki potomne nie mogą niczego modyfikować w rodzica .
Możesz go znaleźć w instrukcji basha
Proszę sprawdzić: wyniki są wyświetlane w podpowłoce tutaj
źródło
Miałem podobny problem, gdy chciałem automatycznie usunąć utworzone przez siebie pliki tymczasowe. Rozwiązanie, które wymyśliłem, nie polegało na użyciu podstawiania poleceń, ale raczej na przekazaniu do funkcji nazwy zmiennej, która powinna przyjąć ostateczny wynik. Na przykład
Tak więc w twoim przypadku byłoby to:
Działa i nie ma ograniczeń dotyczących „zwracanej wartości”.
źródło
Dzieje się tak, ponieważ podstawianie poleceń jest wykonywane w podpowłoce, więc podczas gdy podpowłoka dziedziczy zmienne, zmiany w nich są tracone po zakończeniu podpowłoki.
Odniesienie :
źródło
Rozwiązaniem tego problemu, bez konieczności wprowadzania złożonych funkcji i znacznej modyfikacji oryginalnej, jest przechowywanie wartości w pliku tymczasowym i odczyt / zapis w razie potrzeby.
To podejście bardzo mi pomogło, gdy musiałem kpić z funkcji basha wywoływanej wielokrotnie w przypadku testowym nietoperzy.
Na przykład możesz mieć:
Wadą jest to, że możesz potrzebować wielu plików tymczasowych dla różnych zmiennych. Może być również konieczne wydanie
sync
polecenia utrwalenia zawartości dysku między operacjami zapisu i odczytu.źródło
Zawsze możesz użyć aliasu:
źródło