$ 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
- mywiki.wooledge.org/BashFAQ / 050
- Bezpečnostní důsledky zapomenutí citovat proměnnou v skořápkách bash / POSIX – Ale co když …?
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"
až 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
- Rozdělení slov v BashGuide
- BashFAQ / 050 nebo “ Snažím se vložit příkaz v proměnné, ale složité případy vždy selžou! “
- Otázka Proč ano můj škrticí skript sytič na mezery nebo jiné speciální znaky? , který pojednává o řadě problémů souvisejících s citací a mezerami, včetně ukládání příkazů.
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 jessh 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
.