Czy jest jakiś sposób na serializację zmiennej powłoki? Załóżmy, że mam zmienną $VAR
i chcę móc zapisać ją w pliku lub czymkolwiek, a następnie odczytać ją później, aby odzyskać tę samą wartość?
Czy istnieje przenośny sposób na zrobienie tego? (Nie sądzę)
Czy istnieje sposób na zrobienie tego w bash lub zsh?
Odpowiedzi:
Ostrzeżenie: korzystając z któregokolwiek z tych rozwiązań, musisz mieć świadomość, że ufasz integralności plików danych, aby były bezpieczne, ponieważ zostaną one wykonane jako kod powłoki w skrypcie. Zabezpieczenie ich jest najważniejsze dla bezpieczeństwa twojego skryptu!
Prosta implementacja wbudowana do szeregowania jednej lub więcej zmiennych
Tak, zarówno w bash, jak i zsh można serializować zawartość zmiennej w sposób łatwy do odczytania za pomocą
typeset
wbudowanego-p
argumentu. Format wyjściowy jest taki, że możesz po prostusource
wyjść, aby odzyskać swoje rzeczy.Możesz odzyskać swoje rzeczy w ten sposób później w skrypcie lub w innym skrypcie:
Będzie to działać w przypadku bash, zsh i ksh, w tym przekazywania danych między różnymi powłokami. Bash przetłumaczy to na swoją wbudowaną
declare
funkcję, podczas gdy zsh implementuje to za pomocą,typeset
ale ponieważ bash ma alias, aby działał tak czy inaczej, ponieważ używamytypeset
tutaj dla kompatybilności z ksh.Bardziej złożone uogólnione wdrożenie za pomocą funkcji
Powyższa implementacja jest naprawdę prosta, ale jeśli często ją wywołujesz, możesz chcieć udostępnić sobie funkcję narzędziową, aby to ułatwić. Dodatkowo, jeśli kiedykolwiek spróbujesz zawrzeć powyższe funkcje niestandardowe, napotkasz problemy ze zmiennym zasięgiem. Ta wersja powinna wyeliminować te problemy.
Uwaga dla wszystkich z nich, w celu utrzymania bash / zsh przekrój kompatybilność będziemy mocowania zarówno przypadki
typeset
, adeclare
więc kod powinien działać w jednej lub obu muszli. Dodaje to trochę bałaganu, który można by wyeliminować, gdybyś robił to tylko dla jednej powłoki.Głównym problemem związanym z używaniem funkcji do tego (lub włączaniem kodu do innych funkcji) jest to, że
typeset
funkcja generuje kod, który po przejściu do skryptu z wnętrza funkcji domyślnie tworzy zmienną lokalną, a nie globalną.Można to naprawić za pomocą jednego z kilku hacków. Pierwszą próbą rozwiązania tego problemu było przeanalizowanie wyniku procesu serializacji w
sed
celu dodania-g
flagi, aby utworzony kod definiował zmienną globalną po ponownym przejściu.Zauważ, że funky
sed
wyrażenie ma pasować tylko do pierwszego wystąpienia „składu” lub „zadeklarować” i dodać-g
jako pierwszy argument. Konieczne jest dopasowanie tylko pierwszego wystąpienia, ponieważ, jak słusznie zauważył Stéphane Chazelas w komentarzach, w przeciwnym razie dopasuje również przypadki, w których szeregowany ciąg zawiera dosłowne znaki nowej linii, a po nim słowo deklaruj lub skład.Oprócz poprawienia mojego początkowego faux pasowania , Stéphane zasugerował również mniej kruchy sposób zhakowania tego, który nie tylko rozwiązuje problemy z parsowaniem łańcuchów, ale może być przydatnym hakiem, aby dodać dodatkową funkcjonalność za pomocą funkcji otoki w celu przedefiniowania działań pobierane przy ponownym pozyskiwaniu danych. Zakłada się, że nie grasz w żadną inną grę za pomocą poleceń deklarowania lub składu, ale tę technikę łatwiej byłoby wdrożyć w sytuacji, w której włączałeś tę funkcję jako część innej własnej lub nie kontrolowałeś zapisywanych danych i tego, czy
-g
dodano flagę. Coś podobnego można również zrobić z aliasami, zobacz implementację Gillesa .Aby wynik był jeszcze bardziej użyteczny, możemy iterować wiele zmiennych przekazywanych do naszych funkcji, zakładając, że każde słowo w tablicy argumentów jest nazwą zmiennej. Wynik staje się mniej więcej taki:
W przypadku obu rozwiązań użycie wyglądałoby tak:
źródło
declare
jestbash
odpowiednikiemksh
„s”typeset
.bash
,zsh
również wsparcie,typeset
więc w tym względzietypeset
jest bardziej przenośne.export -p
jest POSIX, ale nie przyjmuje żadnego argumentu, a jego wynik zależy od powłoki (chociaż jest dobrze określony dla powłok POSIX, więc na przykład, gdy wywoływane jest bash lub kshsh
). Pamiętaj, aby podać swoje zmienne; użycie operatora split + glob tutaj nie ma sensu.-E
można go znaleźć tylko w niektórych BSDsed
. Zmienne wartości mogą zawierać znaki nowego wiersza, więcsed 's/^.../.../'
nie gwarantuje się, że będą działać poprawnie.a=$'foo\ndeclare bar' bash -c 'declare -p a'
do instalacji wyświetli wiersz zaczynający się oddeclare
. Prawdopodobnie lepiej to zrobićdeclare() { builtin declare -g "$@"; }
przed zadzwonieniemsource
(a potemshopt -s expandalias
gdy nie jest interaktywny. Za pomocą funkcji można również ulepszyćdeclare
opakowanie, aby przywracało tylko określone zmienne.Użyj przekierowania, podstawiania poleceń i rozszerzania parametrów. Aby zachować białe znaki i znaki specjalne, potrzebne są podwójne cudzysłowy. Końcowe
x
zapisuje końcowe znaki nowej linii, które w innym przypadku zostałyby usunięte w podstawieniu polecenia.źródło
Serializuj wszystko - POSIX
W dowolnej powłoce POSIX można serializować wszystkie zmienne środowiskowe za pomocą
export -p
. Nie obejmuje to nieeksportowanych zmiennych powłoki. Dane wyjściowe są poprawnie cytowane, dzięki czemu można odczytać je z powrotem w tej samej powłoce i uzyskać dokładnie te same wartości zmiennych. Dane wyjściowe mogą nie być czytelne w innej powłoce, na przykład ksh używa$'…'
składni innej niż POSIX .Serializuj niektóre lub wszystkie - ksh, bash, zsh
Ksh (zarówno pdksh / mksh, jak i ATT ksh), bash i zsh zapewniają lepszą funkcjonalność dzięki
typeset
wbudowanemu.typeset -p
wypisuje wszystkie zdefiniowane zmienne i ich wartości (zsh pomija wartości zmiennych, które zostały ukrytetypeset -H
). Dane wyjściowe zawierają odpowiednią deklarację, dzięki czemu zmienne środowiskowe są eksportowane podczas odczytu (ale jeśli zmienna jest już eksportowana podczas odczytu, nie zostanie wyeksportowana), dzięki czemu tablice są odczytywane jako tablice itp. Tutaj również dane wyjściowe jest poprawnie cytowany, ale gwarantuje się, że będzie czytelny tylko w tej samej powłoce. Możesz przekazać zestaw zmiennych do serializacji w wierszu poleceń; jeśli nie podasz żadnej zmiennej, wszystkie są serializowane.W bash i zsh przywracania nie można wykonać z funkcji, ponieważ
typeset
instrukcje wewnątrz funkcji są ograniczone do tej funkcji. Musisz uruchomić. ./some_vars
w kontekście, w którym chcesz użyć wartości zmiennych, zwracając uwagę, aby zmienne, które były globalne podczas eksportu, zostały ponownie zadeklarowane jako globalne. Jeśli chcesz odczytać wartości w funkcji i wyeksportować je, możesz zadeklarować tymczasowy alias lub funkcję. W Zsh:W bash (który używa
declare
zamiasttypeset
):W ksh
typeset
deklaruje zmienne lokalne w funkcjach zdefiniowanych za pomocąfunction function_name { … }
i zmienne globalne w funkcjach zdefiniowanych za pomocąfunction_name () { … }
.Serializuj niektóre - POSIX
Jeśli chcesz mieć większą kontrolę, możesz ręcznie wyeksportować zawartość zmiennej. Aby wydrukować zawartość zmiennej dokładnie do pliku, użyj
printf
wbudowanego (echo
ma kilka specjalnych przypadków, takich jakecho -n
niektóre powłoki i dodaje nowy wiersz):Możesz to przeczytać ponownie za
$(cat VAR.content)
wyjątkiem tego, że podstawienie polecenia usuwa końcowe znaki nowej linii. Aby uniknąć tego zmarszczki, zadbaj o to, aby wydruk nigdy nie kończył się nową linią.Jeśli chcesz wydrukować wiele zmiennych, możesz zacytować je pojedynczymi cudzysłowami i zastąpić wszystkie osadzone pojedyncze cudzysłowy
'\''
. Tę formę cytowania można odczytać z powrotem w dowolnej powłoce w stylu Bourne / POSIX. Poniższy fragment kodu działa w dowolnej powłoce POSIX. Działa tylko w przypadku zmiennych łańcuchowych (i zmiennych numerycznych w powłokach, które je mają, chociaż będą odczytywane jako ciągi znaków), nie próbuje zajmować się zmiennymi tablicowymi w powłokach, które je zawierają.Oto inne podejście, które nie rozwidla podprocesu, ale jest trudniejsze w manipulowaniu łańcuchem.
Zauważ, że w powłokach, które dopuszczają zmienne tylko do odczytu, pojawi się błąd, jeśli spróbujesz odczytać zmienną tylko do odczytu.
źródło
$PWD
i$_
- proszę zobaczyć własne komentarze poniżej.typeset
aliasutypeset -g
?Wiele dzięki @ stéphane-chazelas, który wskazał wszystkie problemy z moimi poprzednimi próbami, wydaje się, że teraz działa to na serializację tablicy na standardowe wyjście lub na zmienną.
Ta technika nie analizuje danych wejściowych w powłoce (w przeciwieństwie do
declare -a
/declare -p
), a zatem jest bezpieczna przed złośliwym wstawianiem metaznaków w serializowanym tekście.Uwaga: znaki nowej linii nie są poprzedzane znakami ucieczki, ponieważ
read
usuwa\<newlines>
parę znaków, dlatego-d ...
należy ją przekazać do odczytu, a następnie znaki nowej linii bez zmian są zachowywane.Wszystko to jest zarządzane w
unserialise
funkcji.Wykorzystywane są dwie magiczne postacie, separator pól i separator rekordów (dzięki czemu wiele tablic może być szeregowanych do tego samego strumienia).
Znaki te można zdefiniować jako
FS
iRS
żaden z nich nie może być zdefiniowany jakonewline
znak, ponieważ znak nowej linii jest usuwany przezread
.Znak ucieczki musi być
\
odwrotnym ukośnikiem, ponieważ używa się go,read
aby uniknąć rozpoznania postaci jakoIFS
znaku.serialise
będzie serialise"$@"
na standardowe wyjście,serialise_to
będzie serialise do varable w nazwie$1
i odserializować za pomocą:
lub
na przykład
(bez końcowego znaku nowej linii)
przeczytaj to:
lub
Bash
read
respektuje znak zmiany znaczenia\
(chyba że podasz flagę -r), aby usunąć specjalne znaczenie znaków, takie jak separacja pól wejściowych lub rozdzielanie linii.Jeśli chcesz serializować tablicę zamiast zwykłej listy argumentów, po prostu przekaż tablicę jako listę argumentów:
Możesz używać
unserialise
w pętli tak, jak byś to zrobił,read
ponieważ jest to po prostu zawinięty odczyt - ale pamiętaj, że strumień nie jest oddzielony znakiem nowej linii:źródło
bash
izsh
uczynić je jako$'\xxx'
. Spróbuj zbash -c $'printf "%q\n" "\t"'
lubbash -c $'printf "%q\n" "\u0378"'
$IFS
niezmodyfikowania, a teraz nie przywraca poprawnie pustych elementów tablicy. W rzeczywistości rozsądniej byłoby użyć innej wartości IFS i użyć,-d ''
aby uniknąć konieczności ucieczki od nowej linii. Na przykład użyj:
jako separatora pól i unikaj tego oraz ukośnika odwrotnego i użyjIFS=: read -ad '' array
do importowania.read
. backslash-newline forread
jest sposobem na kontynuację linii logicznej do innej linii fizycznej. Edycja: ah Widzę, że wspominałeś już o problemie z nową linią.Możesz użyć
base64
:źródło
Innym sposobem na to jest zapewnienie obsługi wszystkich
'
takich cytatów:Lub z
export
:Pierwsza i druga opcja działają w dowolnej powłoce POSIX, przy założeniu, że wartość zmiennej nie zawiera łańcucha:
Trzecia opcja powinna działać dla dowolnej powłoki POSIX, ale może próbować zdefiniować inne zmienne, takie jak
_
lubPWD
. Prawda jest jednak taka, że jedyne zmienne, które może próbować zdefiniować, są ustawiane i utrzymywane przez samą powłokę - a więc nawet jeśli importujeszexport
wartość dla dowolnej z nich -$PWD
na przykład - powłoka po prostu zresetuje je do i tak poprawna wartość natychmiast - spróbuj zrobićPWD=any_value
i przekonaj się sam.A ponieważ - przynajmniej w GNU
bash
- wyniki debugowania są automatycznie bezpiecznie cytowane w celu ponownego wprowadzenia do powłoki, działa to niezależnie od liczby'
ciężkich cytatów w"$VAR"
:$VAR
można później ustawić na zapisaną wartość w dowolnym skrypcie, w którym poprawna jest następująca ścieżka:źródło
$$
jest PID działającej powłoki, czy źle napisałeś cytat i\$
czy coś takiego? Można zastosować podstawowe podejście do używania dokumentu tutaj, ale jest to trudny, a nie jednowarstwowy materiał: cokolwiek wybierzesz jako znacznik końcowy, musisz wybrać coś, co nie pojawia się w ciągu.$VAR
zawiera%
. Trzecie polecenie nie zawsze działa z wartościami zawierającymi wiele wierszy (nawet po dodaniu oczywiście brakujących podwójnych cudzysłowów).env
. Nadal jestem ciekawy, co masz na myśli mówiąc o wielu liniach -sed
usuwa każdą linię do momentu napotkaniaVAR=
do ostatniej - więc wszystkie linie$VAR
są przekazywane. Czy możesz podać przykład, który go łamie?VAR
) nie ulega zmianiePWD
lub_
czy może innym, że niektóre powłoki zdefiniowania. Druga metoda wymaga bash; format wyjściowy z-v
nie jest ustandaryzowany (żadne z dash, ksh93, mksh i zsh nie działają).Prawie takie same, ale nieco inne:
Ze skryptu:
Ten czas powyżej jest testowany.
źródło
'
,*
, itd.echo "$LVALUE=\"$RVALUE\""
ma również zachowywać nowe wiersze, a wynik w pliku cfg_ powinien wyglądać następująco: MY_VAR1 = "Line1 \ nLine 2" Tak więc, kiedy eval MY_VAR1 będzie zawierał również nowe wiersze. Oczywiście możesz mieć problemy, jeśli przechowywana wartość zawiera"
char. Ale można to również załatwić.