$ ls -l /tmp/test/my\ dir/
total 0
Zastanawiałem się, dlaczego następujące sposoby uruchomienia powyższego polecenia kończą się niepowodzeniem?
$ abc='ls -l "/tmp/test/my dir"'
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
$ bash -c $abc
'my dir'
$ bash -c "$abc"
total 0
$ eval $abc
total 0
$ eval "$abc"
total 0
Odpowiedzi:
Zostało to omówione w wielu pytaniach dotyczących unix.SE, postaram się zebrać wszystkie problemy, które mogę tutaj wymyślić. Referencje na końcu.
Dlaczego zawodzi
Powodem, dla którego napotykasz te problemy, jest dzielenie słów i fakt, że cudzysłowy rozwinięte ze zmiennych nie działają jak cudzysłowy, ale są zwykłymi znakami.
Przypadki przedstawione w pytaniu:
Tutaj
$abc
jest podzielony ils
otrzymuje dwa argumenty"/tmp/test/my
orazdir"
(z cudzysłowami na początku pierwszego i na końcu drugiego):Tutaj rozwinięcie jest cytowane, więc jest przechowywane jako pojedyncze słowo. Powłoka próbuje znaleźć program o nazwie
ls -l "/tmp/test/my dir"
, w tym spacje i cudzysłowy.A tutaj, tylko pierwsze słowo lub
$abc
jest brane jako argument-c
, więc Bash po prostu działals
w bieżącym katalogu. Pozostałe słowa są argumenty bash i są używane do wypełnienia$0
,$1
itpZa pomocą
bash -c "$abc"
ieval "$abc"
istnieje dodatkowy etap przetwarzania powłoki, który sprawia, że cytaty działają, ale powoduje również, że wszystkie rozszerzenia powłoki są przetwarzane ponownie , więc istnieje ryzyko przypadkowego uruchomienia rozszerzenia poleceń z danych dostarczonych przez użytkownika, chyba że jesteś bardzo ostrożnie z cytowaniem.Lepsze sposoby na zrobienie tego
Dwa lepsze sposoby przechowywania polecenia to: a) zamiast tego użyj funkcji, b) użyj zmiennej tablicowej (lub parametrów pozycyjnych).
Korzystanie z funkcji:
Po prostu zadeklaruj funkcję z poleceniem w środku i uruchom funkcję tak, jakby była poleceniem. Rozszerzenia poleceń w funkcji są przetwarzane tylko wtedy, gdy polecenie jest uruchomione, a nie kiedy jest zdefiniowane, i nie trzeba cytować poszczególnych poleceń.
Za pomocą tablicy:
Tablice umożliwiają tworzenie zmiennych składających się z wielu słów, w których pojedyncze słowa zawierają białe znaki. Tutaj poszczególne słowa są przechowywane jako odrębne elementy tablicy, a
"${array[@]}"
rozwinięcie rozwija każdy element jako osobne słowa powłoki:Składnia jest nieco okropna, ale tablice pozwalają również budować wiersz poleceń kawałek po kawałku. Na przykład:
lub utrzymuj stałe części wiersza poleceń i użyj wypełnienia tablicy tylko jego częścią, opcjami lub nazwami plików:
Wadą tablic jest to, że nie są one standardową funkcją, więc zwykłe powłoki POSIX (takie jak
dash
domyślne/bin/sh
w Debian / Ubuntu) nie obsługują ich (ale patrz poniżej). Bash, ksh i zsh jednak, więc prawdopodobnie twój system ma jakąś powłokę, która obsługuje tablice.Za pomocą
"$@"
W powłokach bez obsługi nazwanych tablic można nadal używać parametrów pozycyjnych (pseudo-tablica
"$@"
) do przechowywania argumentów polecenia.Poniżej powinny znajdować się przenośne bity skryptu, które odpowiadają ekwiwalentowi bitów kodu w poprzedniej sekcji. Tablica zostaje zastąpiona
"$@"
listą parametrów pozycyjnych. Ustawianie"$@"
odbywa się za pomocąset
, a podwójne cudzysłowy"$@"
są ważne (powodują, że elementy listy są indywidualnie cytowane).Po pierwsze, po prostu zapisz polecenie z argumentami
"$@"
i uruchom je:Warunkowe ustawienie części opcji wiersza polecenia dla polecenia:
Używanie tylko
"$@"
dla opcji i operandów:(Oczywiście
"$@"
jest zwykle wypełnione argumentami do samego skryptu, więc musisz je gdzieś zapisać przed ponownym wybieraniem"$@"
).Bądź ostrożny z
eval
!Ponieważ
eval
wprowadza dodatkowy poziom przetwarzania ofert i ekspansji, musisz być ostrożny przy wprowadzaniu danych przez użytkownika. Na przykład działa to tak długo, jak użytkownik nie wpisuje żadnych pojedynczych cudzysłowów:Ale jeśli podadzą dane wejściowe
'$(uname)'.txt
, skrypt z przyjemnością uruchomi podstawienie polecenia.Wersja z tablicami jest na to odporna, ponieważ słowa są przechowywane osobno przez cały czas, nie ma cytatu ani innego przetwarzania zawartości
filename
.Referencje
źródło
cmd="ls -l $(printf "%q" "$filename")"
. nie jest ładna, ale jeśli użytkownik nie chce używaćeval
, pomaga. Jest to także bardzo przydatne do wysyłania polecenia choć podobnych rzeczy, takich jakssh foohost "ls -l $(printf "%q" "$filename")"
, lub w rozprza tego pytania:ssh foohost "$cmd"
.$ alias abc='ls -l "/tmp/test/my dir"'
Najbezpieczniejszym sposobem na uruchomienie (nietrywialnego) polecenia jest
eval
. Następnie możesz napisać polecenie tak, jak w wierszu poleceń, i jest ono wykonywane dokładnie tak, jakbyś właśnie je wprowadził. Ale musisz zacytować wszystko.Prosty przypadek:
nie tak prosty przypadek:
źródło
Drugi znak cudzysłowu łamie polecenie.
Kiedy biegnę:
Dał mi błąd.
Ale kiedy biegnę
W ogóle nie ma błędu
W tej chwili nie ma sposobu, aby to naprawić (dla mnie), ale można uniknąć błędu, nie mając spacji w nazwie katalogu.
Ta odpowiedź mówi, że można użyć polecenia eval, aby to naprawić, ale to nie działa dla mnie :(
źródło
Jeśli to nie zadziała
''
, powinieneś użyć``
:Zaktualizuj lepszy sposób:
źródło
$( command... )
zamiast backticks.Co powiesz na jedno-liniowy python3?
źródło