Jak używać eval w skryptach Linux Bash
Opublikowany: 2022-08-22 Ze wszystkich poleceń Bash, biedny stary eval
ma prawdopodobnie najgorszą reputację. Uzasadnione, czy po prostu zła prasa? Omawiamy użycie i niebezpieczeństwa tego najmniej lubianego polecenia Linuksa.
Musimy porozmawiać o ewaluacji
Używany niedbale, eval
może prowadzić do nieprzewidywalnych zachowań, a nawet niepewności systemu. Sądząc po dźwiękach, prawdopodobnie nie powinniśmy go używać, prawda? No nie do końca.
Można powiedzieć coś podobnego o samochodach. W niewłaściwych rękach są śmiercionośną bronią. Ludzie używają ich w najazdach na taran i jako pojazdy do ucieczki. Czy wszyscy powinniśmy przestać używać samochodów? Nie, oczywiście nie. Ale muszą być używane właściwie i przez ludzi, którzy wiedzą, jak je prowadzić.
Zwykłym przymiotnikiem stosowanym do eval
jest „zło”. Ale wszystko sprowadza się do tego, jak jest używany. Polecenie eval
zestawia wartości z jednej lub więcej zmiennych. Tworzy ciąg poleceń. Następnie wykonuje to polecenie. Jest to przydatne, gdy musisz poradzić sobie z sytuacjami, w których treść polecenia jest wyprowadzana dynamicznie podczas wykonywania skryptu.
Problemy pojawiają się, gdy skrypt jest pisany z użyciem eval
na łańcuchu, który został odebrany spoza skryptu. Może być wpisany przez użytkownika, wysłany przez API, oznaczony w żądaniu HTTPS lub w dowolnym innym miejscu poza skryptem.
Jeśli ciąg, na którym ma działać eval
, nie został wyprowadzony lokalnie i programowo, istnieje ryzyko, że ciąg może zawierać osadzone złośliwe instrukcje lub inne źle sformułowane dane wejściowe. Oczywiście nie chcesz, aby eval
wykonywał złośliwe polecenia. Aby być bezpiecznym, nie używaj eval
z zewnętrznie generowanymi ciągami lub danymi wejściowymi użytkownika.
Pierwsze kroki z oceną
Polecenie eval
jest wbudowanym poleceniem powłoki Bash. Jeśli Bash jest obecny, eval
będzie obecny.
eval
łączy swoje parametry w jeden ciąg. Użyje jednej spacji do oddzielenia połączonych elementów. Ocenia argumenty, a następnie przekazuje cały ciąg do wykonania powłoki.
Stwórzmy zmienną o nazwie wordcount
.
wordcount="wc -w raw-notes.md"
Zmienna łańcuchowa zawiera polecenie do zliczania słów w pliku o nazwie „raw-notes.md”.
Możemy użyć eval
do wykonania tego polecenia, przekazując mu wartość zmiennej.
eval " $wordcount "
Polecenie jest wykonywane w bieżącej powłoce, a nie w podpowłoce. Możemy to łatwo pokazać. Mamy krótki plik tekstowy o nazwie „variables.txt”. Zawiera te dwie linie.
pierwszy=Jak to zrobić drugi=Geek
Użyjemy cat
do wysłania tych linii do okna terminala. Następnie użyjemy eval
do oceny polecenia cat
, aby wykonać instrukcje zawarte w pliku tekstowym. To ustawi dla nas zmienne.
cat zmienne.txt eval "$(cat zmienne.txt)" echo $pierwsza $druga
Używając echo
do drukowania wartości zmiennych, możemy zobaczyć, że polecenie eval
działa w bieżącej powłoce, a nie w podpowłoce.
Proces w podpowłoce nie może zmienić środowiska powłoki rodzica. Ponieważ eval działa w bieżącej powłoce, zmienne ustawione przez eval
mogą być używane z powłoki, która uruchomiła polecenie eval
.
Zauważ, że jeśli użyjesz eval
w skrypcie, powłoka, która zostanie zmieniona przez eval
, jest podpowłoką, w której działa skrypt, a nie powłoką, która go uruchomiła.
POWIĄZANE: Jak korzystać z poleceń cat i tac systemu Linux
Używanie zmiennych w ciągu poleceń
W ciągach poleceń możemy uwzględnić inne zmienne. Ustawimy dwie zmienne do przechowywania liczb całkowitych.
liczba1=10 liczba2=7
Stworzymy zmienną do przechowywania polecenia expr
, które zwróci sumę dwóch liczb. Oznacza to, że musimy uzyskać dostęp do wartości dwóch zmiennych całkowitych w poleceniu. Zwróć uwagę na backticki wokół expr
.
add="`wyrażenie $numer1 + $numer2`"
Stworzymy kolejne polecenie, aby pokazać nam wynik expr
.
pokaż="echo"
Zauważ, że nie musimy umieszczać spacji na końcu ciągu echo
ani na początku ciągu expr
. eval
się tym zajmuje.
A do wykonania całego polecenia używamy:
eval $pokaż $dodaj
Wartości zmiennych wewnątrz łańcucha expr
są podstawiane do łańcucha przez eval
, zanim zostaną przekazane do powłoki do wykonania.
POWIĄZANE: Jak pracować ze zmiennymi w Bash
Uzyskiwanie dostępu do zmiennych wewnątrz zmiennych
Możesz przypisać wartość do zmiennej, a następnie przypisać nazwę tej zmiennej do innej zmiennej. Używając eval
, możesz uzyskać dostęp do wartości przechowywanej w pierwszej zmiennej, począwszy od jej nazwy, która jest wartością przechowywaną w drugiej zmiennej. Przykład pomoże ci to rozwikłać.
Skopiuj ten skrypt do edytora i zapisz go jako plik o nazwie „assign.sh”.
#!/kosz/bash title="Jak to zrobić" strona internetowa=tytuł polecenie="echo" eval $polecenie \${$strona internetowa}
Musimy uczynić go wykonywalnym poleceniem chmod
.
chmod +x przypisz.sh
Musisz to zrobić dla wszystkich skryptów, które skopiujesz z tego artykułu. Po prostu użyj odpowiedniej nazwy skryptu w każdym przypadku.
Kiedy uruchamiamy nasz skrypt, widzimy tekst z title
zmiennej, mimo że polecenie eval
używa zmiennej webpage
.
./przypisz.sh
Uciekły znak dolara „ $
” i nawiasy klamrowe „ {}
” powodują, że eval sprawdza wartość przechowywaną wewnątrz zmiennej, której nazwa jest przechowywana w zmiennej webpage
.
Korzystanie ze zmiennych tworzonych dynamicznie
Możemy użyć eval
do dynamicznego tworzenia zmiennych. Ten skrypt nazywa się „loop.sh”.
#!/kosz/bash suma=0 label="Zapętlanie zakończone. Razem:" dla n w {1..10} robić oszacowanie x$n=$n echo "Pętla" $x$n ((ogółem+=$x$n)) Gotowe echo $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10 echo $etykieta $łącznie
Tworzy zmienną o nazwie total
, która przechowuje sumę wartości tworzonych przez nas zmiennych. Następnie tworzy zmienną łańcuchową o nazwie label
. To jest prosty ciąg tekstu.
Zapętlimy się 10 razy i utworzymy 10 zmiennych o nazwach od x1
do x10
. Instrukcja eval
w treści pętli zawiera „x” i pobiera wartość licznika pętli $n
, aby utworzyć nazwę zmiennej. Jednocześnie ustawia nową zmienną na wartość licznika pętli $n
.
Drukuje nową zmienną w oknie terminala, a następnie zwiększa total
zmienną o wartość nowej zmiennej.
Poza pętlą wypisywanych jest jeszcze 10 nowych zmiennych, wszystkie w jednym wierszu. Zauważ, że możemy odwoływać się do zmiennych również ich prawdziwymi nazwami, bez używania obliczonej lub pochodnej wersji ich nazw.
Na koniec wypisujemy wartość zmiennej total
.
./pętla.sh
POWIĄZANE: Primer: Bash Loops: na, chwilę i do
Używanie eval z tablicami
Wyobraź sobie scenariusz, w którym masz skrypt, który działa długo i wykonuje dla Ciebie pewne przetwarzanie. Zapisuje do pliku dziennika o nazwie utworzonej na podstawie znacznika czasu. Czasami uruchamia nowy plik dziennika. Po zakończeniu skryptu, jeśli nie wystąpiły żadne błędy, usuwa utworzone pliki dziennika.
Nie chcesz po prostu rm *.log
, chcesz tylko usunąć pliki dziennika, które utworzył. Ten skrypt symuluje tę funkcjonalność. To jest „clear-logs.sh”.
#!/kosz/bash zadeklaruj -a logfiles liczba plików=0 rm_string="echo" function create_logfile() { ((++liczba plików)) filename=$(data +"%Y-%m-%d_%H-%M-%S").log logfiles[$filecount]=$nazwapliku echo $filecount "Utworzono" ${logfiles[$filecount]} } # treść skryptu. Tutaj odbywa się pewne przetwarzanie, które # okresowo generuje plik dziennika. Zasymulujemy to utwórz_logfile spać 3 utwórz_logfile spać 3 utwórz_logfile spać 3 utwórz_logfile # czy są jakieś pliki do usunięcia? for ((plik=1; plik<=$liczbaplików; plik++)) robić # usuń plik dziennika eval $rm_string ${logfiles[$plik]} "usunięty..." pliki dziennika[$plik]="" Gotowe
Skrypt deklaruje tablicę zwaną logfiles
. Będzie to zawierało nazwy plików dziennika, które są tworzone przez skrypt. Deklaruje zmienną o nazwie filecount
. To będzie przechowywać liczbę plików dziennika, które zostały utworzone.
Deklaruje również ciąg o nazwie rm_string
. W prawdziwym skrypcie zawierałoby to polecenie rm
, ale używamy echo
, aby zademonstrować zasadę w niedestrukcyjny sposób.
Funkcja create_logfile()
nazwę każdego pliku dziennika i miejsce, w którym zostanie on otwarty. Tworzymy tylko nazwę pliku i udajemy, że została utworzona w systemie plików.
Funkcja zwiększa zmienną filecount
. Jego wartość początkowa to zero, więc pierwsza tworzona przez nas nazwa pliku jest przechowywana na pozycji jeden w tablicy. Odbywa się to celowo, jak zobaczymy później.
Nazwa pliku jest tworzona za pomocą polecenia date
i rozszerzenia „.log”. Nazwa jest przechowywana w tablicy w pozycji wskazanej przez filecount
. Nazwa jest wypisywana w oknie terminala. W prawdziwym skrypcie utworzyłbyś również rzeczywisty plik.
Ciało skryptu jest symulowane za pomocą polecenia sleep
. Tworzy pierwszy plik dziennika, czeka trzy sekundy, a następnie tworzy kolejny. Tworzy cztery pliki dziennika, rozmieszczone w odstępach, aby sygnatury czasowe w ich nazwach plików były różne.
Wreszcie istnieje pętla, która usuwa pliki dziennika. Plik licznika pętli jest ustawiony na jeden. Liczy się do wartości filecount
, która przechowuje liczbę utworzonych plików.
Jeśli filecount
jest nadal ustawiona na zero — ponieważ nie zostały utworzone żadne pliki dziennika — treść pętli nigdy nie zostanie wykonana, ponieważ jeden jest nie mniejszy lub równy zero. Dlatego zmienna filecount
była ustawiona na zero, kiedy została zadeklarowana i dlaczego została zwiększona przed utworzeniem pierwszego pliku.
Wewnątrz pętli używamy eval
z naszym nieniszczącym rm_string
i nazwą pliku, który jest pobierany z tablicy. Następnie ustawiamy element tablicy na pusty ciąg.
Oto, co widzimy, gdy uruchamiamy skrypt.
./clear-logs.sh
Nie wszystko jest złe
Bardzo złośliwa eval
zdecydowanie ma swoje zastosowanie. Jak większość narzędzi, używany lekkomyślnie, jest niebezpieczny i to na wiele sposobów.
Jeśli upewnisz się, że ciągi, na których działa, są tworzone wewnętrznie i nie są przechwytywane od ludzi, interfejsów API lub takich rzeczy, jak żądania HTTPS, unikniesz głównych pułapek.
POWIĄZANE: Jak wyświetlić datę i godzinę w terminalu Linux (i używać jej w skryptach Bash)