Jak můžeme spustit příkaz uložený v proměnné?

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

Zajímalo by mě, proč následující způsoby spuštění výše uvedeného příkazu selžou nebo uspějí?

$ 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 

Komentáře

Odpověď

Toto bylo probráno v řadě otázek na unix.SE, pokusím se shromáždit všechny problémy, se kterými zde mohu přijít. Odkazy na konci.


Proč selhává

Důvodem, proč těmto problémům čelíte, je rozdělení slov a skutečnost, že uvozovky rozšířené z proměnných nepůsobí jako uvozovky, ale jsou to jen obyčejné znaky.

případy uvedené v otázce:

Přiřazení zde přiřadí jeden řetězec ls -l "/tmp/test/my dir"abc:

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

Níže, $abc je rozdělen na mezery a ls získá tři argumenty -l, "/tmp/test/my a dir" (s citací v přední části druhé a další v zadní části třetí). Tato možnost funguje, ale cesta bude nesprávně zpracována:

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

Zde je uvedena expanze, takže je zachována jako jediné slovo. Prostředí se pokusí najít program doslova nazvaný ls -l "/tmp/test/my dir", včetně mezer a uvozovek.

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

A tady, je rozdělena a jako argument pro -c je bráno pouze první výsledné slovo, takže Bash pouze spustí ls v aktuálním adresáři. Ostatní slova jsou argumenty pro bash a používají se k vyplnění $0, $1 atd.

$ bash -c $abc "my dir" 

S bash -c "$abc" a eval "$abc" existuje další krok zpracování shellu, díky němuž fungují uvozovky, ale také způsobí, že budou znovu zpracována všechna rozšíření shellu , takže existuje riziko náhodného spuštění, např. nahrazení příkazu z údajů poskytnutých uživatelem, pokud ne “ jsi velmi opatrný Úplné citování.


Lepší způsoby, jak to udělat

Dva lepší způsoby, jak uložit příkaz, jsou a) místo toho použít funkci, b) použít proměnnou pole ( nebo poziční parametry).

Použití funkce:

Jednoduše deklarovat funkce s příkazem uvnitř a spusťte funkci, jako by to byl příkaz. Expanze v příkazech v rámci funkce jsou zpracovávány pouze při spuštění příkazu, nikoli když je definován, a není třeba citovat jednotlivé příkazy.

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

Použití pole:

Pole umožňují vytvářet víceslovné proměnné, kde jednotlivá slova obsahují bílou prostor. Zde jsou jednotlivá slova uložena jako odlišné prvky pole a rozšíření "${array[@]}" rozšiřuje každý prvek jako samostatná slova prostředí:

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

Syntaxe je mírně příšerná, ale pole také umožňují sestavit příkazový řádek po kuse. Například:

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

nebo udržujte konstantní části příkazového řádku a použijte výplň pole pouze jeho část, například možnosti nebo názvy souborů:

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

Nevýhodou polí je, že nejsou standardní funkcí, takže obyčejné skořápky POSIX (jako dash, výchozí /bin/sh v Debianu / Ubuntu) je nepodporuje (ale viz níže). Bash, ksh a zsh ano, takže je pravděpodobné, že váš systém obsahuje nějaké prostředí podporující pole.

Použití "$@"

Ve skořápkách bez podpory pojmenovaných polí lze stále používat poziční parametry (pseudo-pole "$@") pro uložení argumentů příkazu.

Následující by měly být přenosné skriptové bity, které odpovídají ekvivalentu kódových bitů v předchozí části. Pole je nahrazeno "$@", seznam pozičních parametrů. Nastavení "$@" se provádí pomocí set a uvozovek kolem "$@" jsou důležité (způsobují individuální citaci prvků seznamu).

Nejprve jednoduše uložte příkaz s argumenty do "$@" a spusťte jej:

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

Podmíněné nastavení částí možností příkazového řádku pro příkaz:

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

Pouze použití "$@" pro možnosti a operandy :

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

(Samozřejmě "$@" je obvykle vyplněn argumenty samotného skriptu, takže vy “ Před opětovným použitím "$@" je musím někde uložit.)


Buďte opatrní při eval !

Vzhledem k tomu, že eval zavádí další úroveň zpracování nabídek a rozšiřování, je třeba dávat pozor na vstup uživatele. Například to funguje tak dlouho, dokud uživatel nezadává žádné uvozovky:

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

Pokud ale zadají vstup "$(uname)".txt, váš skript bude šťastně spustí substituci příkazu.

Verze s poli je imunní vůči th protože slova jsou po celou dobu oddělená, neexistuje obsah ani jiné zpracování obsahu filename.

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

Reference

Komentáře

  • eval citaci můžete obejít provedením cmd="ls -l $(printf "%q" "$filename")". není to hezké, ale pokud je uživatel mrtvý nastaven na používání eval, pomůže to. Je to ‚ s také velmi užitečné pro odeslání příkazu i přes podobné věci, jako je ssh foohost "ls -l $(printf "%q" "$filename")" nebo ve sprit této otázky: ssh foohost "$cmd".
  • nesouvisí to přímo, ale naprogramovali jste adresář napevno? V takovém případě se možná budete chtít podívat na alias. Něco jako: $ alias abc='ls -l "/tmp/test/my dir"'

Odpověď

Nejbezpečnější způsob, jak spusťte (netriviální) příkaz eval. Pak můžete napsat příkaz, jako byste to udělali na příkazovém řádku, a ten se provede přesně tak, jako byste jej právě zadali. Musíte však vše uvést.

Jednoduchý případ:

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

ne tak jednoduchý případ:

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

Komentáře

  • Ahoj @HaukeLaging, co je \ ‚ ‚?
  • @jumping_monkey V ' citovaném řetězci nemůžete mít ' . Musíte tedy (1) dokončit / přerušit citování, (2) uniknout z dalšího ', abyste jej získali doslova, (3) zadejte doslovný citát a (4) do v případě přerušení obnovte citovaný řetězec: (1)'(2)\(3)'(4)'
  • Ahoj @HaukeLaging, zajímavé a proč ne abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile' ?
  • @jumping_monkey Nemluvě o tom, že jsem vám právě vysvětlil, proč to nefunguje: ‚ Nemalo by smysl testovat kód před jeho zveřejněním?

Odpověď

Druhý znak uvozovky rozbije příkaz.

Když spustím:

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

Dalo mi to chybu.

Ale když spustím

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

Neexistuje vůbec žádná chyba

V tuto chvíli neexistuje způsob, jak to opravit (pro mě), ale chybě se můžete vyhnout tím, že nebudete mít v názvu adresáře mezeru.

Tato odpověď říká, že k vyřešení tohoto problému lze použít příkaz eval, ale nefunguje to pro mě 🙁

Komentáře

  • Ano, to funguje, pokud ‚ není třeba např. názvy souborů s vloženými mezerami (nebo názvy obsahující znaky glob),

odpověď

Pokud to nefunguje s "", pak byste měli použít ``:

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

Aktualizovat lépe způsob:

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

Komentáře

  • Tím se uloží výsledek příkazu do proměnné. OP (kupodivu) chce uložit samotný příkaz do proměnné. A měli byste opravdu začít používat $( command... ) místo backticků.
  • Děkuji vám za vysvětlení a tipy!

Odpověď

Co třeba jednorázová linka 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 .. 

Komentáře

  • Otázka se týká BASH, nikoli pythonu.

Odpověď

Další jednoduchý trik ke spuštění libovolného (triviálního / netriviálního) příkazu uloženého v abc proměnná je:

$ history -s $abc 

a push UpArrow nebo Ctrl-p jej přenést do příkazového řádku. Na rozdíl od jiných metod tímto způsobem jej můžete v případě potřeby před spuštěním upravit.

Tento příkaz připojí obsah proměnné jako nový záznam do historie Bash a můžete jej vyvolat pomocí UpArrow.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *