Jak możemy uruchomić polecenie przechowywane w zmiennej?

$ ls -l /tmp/test/my\ dir/ total 0 

Zastanawiałem się, dlaczego poniższe sposoby uruchamiania powyższego polecenia kończą się niepowodzeniem lub sukcesem?

$ 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 

Komentarze

Odpowiedź

Zostało to omówione w wielu pytaniach na unix.SE, spróbuję zebrać wszystkie Problemy, które mogę tu wymyślić. Na końcu referencje.


Dlaczego to się nie udaje

Powodem, dla którego napotykasz te problemy, jest podział na słowa i fakt, że cudzysłowy rozwinięte ze zmiennych nie działają jak cudzysłowy, ale są zwykłymi znakami.

przypadki przedstawione w pytaniu:

Przypisanie tutaj przypisuje pojedynczy ciąg ls -l "/tmp/test/my dir" do abc:

$ abc="ls -l "/tmp/test/my dir"" 

Poniżej $abc jest podzielony na białe znaki, a ls pobiera trzy argumenty -l, "/tmp/test/my i dir" (z cudzysłowem z przodu drugiego i drugim z tyłu trzeciego). Opcja działa, ale ścieżka jest niepoprawnie przetwarzana:

$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory 

Tutaj rozwinięcie jest cytowane, więc jest przechowywane jako pojedyncze słowo. Powłoka próbuje aby znaleźć program nazwany dosłownie ls -l "/tmp/test/my dir", zawierający spacje i cudzysłowy.

$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory 

A tutaj jest dzielone i tylko pierwsze słowo wynikowe jest traktowane jako argument funkcji -c, więc Bash uruchamia po prostu ls w bieżącym katalogu. Pozostałe słowa są argumentami dla basha i są używane do wypełniania $0, $1, itd.

$ bash -c $abc "my dir" 

W przypadku bash -c "$abc" i eval "$abc" jest dodatkowy krok przetwarzania powłoki, który sprawia, że cudzysłowy działają, ale powoduje również ponowne przetworzenie wszystkich rozszerzeń powłoki , więc istnieje ryzyko przypadkowego wykonania np. zamiany polecenia z danych podanych przez użytkownika, chyba że są bardzo ostrożni pełne cytowanie.


Lepsze sposoby na zrobienie tego

Dwa lepsze sposoby przechowywania poleceń to a) użycie funkcji zamiast niej, b) użycie zmiennej tablicowej ( lub parametry pozycyjne).

Użycie funkcji:

Po prostu zadeklaruj funkcję z poleceniem w środku i uruchom funkcję tak, jakby była poleceniem. Rozszerzenia w poleceniach w ramach funkcji są przetwarzane tylko wtedy, gdy polecenie jest uruchomione, a nie gdy jest zdefiniowane, i nie trzeba cytować poszczególnych poleceń.

# define it myls() { ls -l "/tmp/test/my dir" } # run it myls 

Korzystanie z tablicy:

Tablice umożliwiają tworzenie zmiennych wielowyrazowych, w których poszczególne słowa zawierają kolor biały przestrzeń. Tutaj poszczególne słowa są przechowywane jako odrębne elementy tablicy, a rozwinięcie "${array[@]}" rozszerza każdy element jako oddzielne słowa powłoki:

# define the array mycmd=(ls -l "/tmp/test/my dir") # run the command "${mycmd[@]}" 

Składnia jest trochę okropna, ale tablice pozwalają także budować wiersz poleceń kawałek po kawałku. Na przykład:

mycmd=(ls) # initial command if [ "$want_detail" = 1 ]; then mycmd+=(-l) # optional flag fi mycmd+=("$targetdir") # the filename "${mycmd[@]}" 

lub zachowaj stałą część wiersza poleceń i użyj tablicy wypełnij tylko jej część, na przykład opcje lub nazwy plików:

options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target" 

Wadą tablic jest to, że nie są one standardową funkcją, więc zwykłe powłoki POSIX (takie jak dash, domyślna /bin/sh w Debianie / Ubuntu) nie obsługuj ich (ale zobacz poniżej). Jednak Bash, ksh i zsh to robią, więc jest prawdopodobne, że Twój system ma jakąś powłokę obsługującą tablice.

Używanie "$@"

W powłokach bez obsługi nazwanych tablic nadal można używać parametrów pozycyjnych (pseudo-tablica "$@") do przechowywania argumentów polecenia.

Poniżej powinny znajdować się przenośne bity skryptu, które stanowią odpowiednik bitów kodu z poprzedniej sekcji. Tablica jest zastępowana przez "$@", lista parametrów pozycyjnych. Ustawienie "$@" jest wykonywane za pomocą set i podwójnych cudzysłowów około "$@" są ważne (powodują one, że elementy listy są cytowane indywidualnie).

Po pierwsze, po prostu zapisz polecenie z argumentami w "$@" i uruchom je:

set -- ls -l "/tmp/test/my dir" "$@" 

Warunkowe ustawienie części opcji wiersza poleceń dla polecenia:

set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@" 

Tylko użycie "$@" dla opcji i argumentów :

set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@" 

(Oczywiście "$@" jest zwykle wypełnione argumentami skryptu, więc „ Muszę je gdzieś zapisać przed ponownym użyciem "$@".)


Uważaj na eval !

Ponieważ eval wprowadza dodatkowy poziom przetwarzania cytatów i rozwinięć, należy zachować ostrożność przy wprowadzaniu danych przez użytkownika. Na przykład działa to tak długo, jak długo użytkownik nie „t wpisuje żadnych pojedynczych cudzysłowów:

read -r filename cmd="ls -l "$filename"" eval "$cmd"; 

Ale jeśli podają dane wejściowe "$(uname)".txt, twój skrypt szczęśliwie uruchamia podstawianie poleceń.

Wersja z tablicami jest odporna na th w ponieważ słowa są przechowywane oddzielnie przez cały czas, nie ma cytowania ani innego przetwarzania dla treści filename.

read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}" 

Odnośniki

Komentarze

  • możesz obejść cytowanie eval, wykonując cmd="ls -l $(printf "%q" "$filename")". nie jest ładny, ale jeśli użytkownik jest martwy przy używaniu eval, to pomaga. ' jest również bardzo przydatne do wysyłania polecenia, chociaż podobne rzeczy, takie jak ssh foohost "ls -l $(printf "%q" "$filename")", lub w duchu tego pytania: ssh foohost "$cmd".
  • Brak bezpośredniego związku, ale czy zakodowałeś katalog na stałe? W takim przypadku możesz spojrzeć na alias. Na przykład: $ alias abc='ls -l "/tmp/test/my dir"'

Odpowiedź

Najbezpieczniejszy sposób uruchom (nietrywialne) polecenie eval. Następnie możesz napisać polecenie tak, jak w linii poleceń, a zostanie ono wykonane dokładnie tak, jakbyś je właśnie wprowadził. Ale musisz wszystko zacytować.

Prosty przypadek:

abc="ls -l "/tmp/test/my dir"" eval "$abc" 

Nie taki prosty przypadek:

# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc" 

Komentarze

  • Cześć @HaukeLaging, co to jest \ ' '?
  • @jumping_monkey Nie możesz mieć ' w ' -cytowanym ciągu . W związku z tym musisz (1) zakończyć / przerwać cytowanie, (2) uciec przed następnym ', aby uzyskać dosłowne, (3) wpisać dosłowny cytat i (4) w przypadek przerwy wznowić cytowany ciąg: (1)'(2)\(3)'(4)'
  • Cześć @HaukeLaging, interesujące, a dlaczego nie tylko abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile' ?
  • @jumping_monkey Nie mówiąc już o tym, że właśnie wyjaśniłem wam, dlaczego to nie działa: czy nie ' t testowanie kodu przed wysłaniem nie ma sensu?

Odpowiedź

Drugi cudzysłów przerywa polecenie.

Kiedy uruchamiam:

abc="ls -l "/home/wattana/Desktop"" $abc 

Wystąpił błąd.

Ale kiedy uruchamiam

abc="ls -l /home/wattana/Desktop" $abc 

W ogóle nie ma błędu

Nie ma sposobu, aby to naprawić (dla mnie), ale możesz uniknąć błędu, nie mając miejsca w nazwie katalogu.

Ta odpowiedź mówi, że polecenie eval może być użyte, aby to naprawić, ale nie działa dla mnie 🙁

Komentarze

  • Tak, to działa, o ile ' nie ma potrzeby np. nazwy plików z osadzonymi spacjami (lub zawierające znaki glob).

Odpowiedź

Jeśli to nie działa z "", następnie użyj ``:

abc=`ls -l /tmp/test/my\ dir/` 

Aktualizuj lepiej sposób:

abc=$(ls -l /tmp/test/my\ dir/) 

Komentarze

  • To zapisuje wynik polecenia w zmiennej. OP chce (dziwnie) zapisać samo polecenie w zmiennej. Aha, i naprawdę powinieneś zacząć używać $( command... ) zamiast grawisów.
  • Dziękuję bardzo za wyjaśnienia i wskazówki!

Odpowiedź

A co powiesz na jedną linijkę Python3?

bash-4.4# pwd /home bash-4.4# CUSTOM="ls -altr" bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)" total 8 drwxr-xr-x 2 root root 4096 Mar 4 2019 . drwxr-xr-x 1 root root 4096 Nov 27 16:42 .. 

Komentarze

  • Pytanie dotyczy BASH, a nie Pythona.

Odpowiedź

Kolejna prosta sztuczka do uruchomienia dowolnego (trywialnego / nietrywialnego) polecenia przechowywanego w abc zmienną jest:

$ history -s $abc 

i push UpArrow lub Ctrl-p, aby wprowadzić go do wiersza poleceń. W przeciwieństwie do innych metod, w ten sposób możesz ją edytować przed wykonaniem w razie potrzeby.

To polecenie doda zawartość zmiennej jako nowy wpis do historii Bash i możesz ją przywołać przez UpArrow.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *