$ ls -l /tmp/test/my\ dir/ total 0
Mă întrebam de ce următoarele moduri de a rula comanda de mai sus nu reușesc sau reușesc?
$ 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
Comentarii
- mywiki.wooledge.org/BashFAQ / 050
- Implicații privind securitatea uitării citării unei variabile în shell-urile bash / POSIX – Dar dacă …?
Răspuns
Acest lucru a fost discutat într-o serie de întrebări pe unix.SE, voi încerca să strâng toate probleme pe care le pot găsi aici. Referințe la final.
De ce nu reușește
Motivul pentru care vă confruntați cu aceste probleme este divizarea cuvintelor și faptul că ghilimelele extinse din variabile nu acționează ca ghilimele, ci sunt doar caractere obișnuite.
cazuri prezentate în întrebarea:
Atribuirea de aici atribuie șirul unic ls -l "/tmp/test/my dir"
la abc
:
$ abc="ls -l "/tmp/test/my dir""
Mai jos, $abc
este împărțit pe spațiul alb și ls
primește cele trei argumente -l
, "/tmp/test/my
și dir"
(cu un ghilimel în fața celui de-al doilea și un altul în spatele celui de-al treilea). Opțiunea funcționează, dar calea este procesată incorect:
$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory
Aici, extinderea este citată, deci este păstrată ca un singur cuvânt. pentru a găsi un program numit literal ls -l "/tmp/test/my dir"
, spații și ghilimele incluse.
$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory
Și aici, $abc
este împărțit și numai primul cuvânt rezultat este luat ca argument pentru -c
, astfel încât Bash execută doar ls
în directorul curent. Celelalte cuvinte sunt argumente pentru bash și sunt utilizate pentru a completa $0
, $1
etc.
$ bash -c $abc "my dir"
Cu bash -c "$abc"
și eval "$abc"
, există un supliment pasul de procesare a shell-ului, care face ca ghilimelele să funcționeze, dar face ca toate expansiunile shell-ului să fie procesate din nou , deci există „riscul de a rula accidental, de exemplu, o înlocuire a comenzii din datele furnizate de utilizator, cu excepția cazului în care” îți pasă foarte mult despre citare.
Moduri mai bune de a face acest lucru
Cele două modalități mai bune de a stoca o comandă sunt a) folosiți o funcție în loc, b) utilizați o variabilă matrice ( sau parametrii poziționali).
Utilizarea unei funcții:
Pur și simplu declarați o funcție cu comanda în interior și rulați funcția ca și cum ar fi o comandă. Extensiile în comenzile din cadrul funcției sunt procesate numai atunci când comanda rulează, nu când este definită și nu este necesar să citiți comenzile individuale.
# define it myls() { ls -l "/tmp/test/my dir" } # run it myls
Utilizarea unui tablou:
Tablourile permit crearea de variabile cu mai multe cuvinte în care cuvintele individuale conțin alb spaţiu. Aici, cuvintele individuale sunt stocate ca elemente matrice distincte, iar extinderea "${array[@]}"
extinde fiecare element ca cuvinte shell separate:
# define the array mycmd=(ls -l "/tmp/test/my dir") # run the command "${mycmd[@]}"
Sintaxa este ușor oribilă, dar matricele vă permit, de asemenea, să construiți linia de comandă bucată cu bucată. De exemplu:
mycmd=(ls) # initial command if [ "$want_detail" = 1 ]; then mycmd+=(-l) # optional flag fi mycmd+=("$targetdir") # the filename "${mycmd[@]}"
sau păstrați părți din linia de comandă constantă și utilizați matricea completați doar o parte din ea, cum ar fi opțiuni sau nume de fișiere:
options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target"
Dezavantajul matricilor este că acestea nu sunt o caracteristică standard, deci shell-uri POSIX simple (cum ar fi dash
, valoarea implicită /bin/sh
în Debian / Ubuntu) nu le susține (dar vezi mai jos). Cu toate acestea, Bash, ksh și zsh există, așa că este posibil ca sistemul dvs. să aibă unele shell care acceptă tablouri.
Utilizarea "$@"
În cochilii fără suport pentru matrice numite, se pot utiliza în continuare parametrii poziționali (pseudo-matricea "$@"
) pentru a păstra argumentele unei comenzi.
Următoarele ar trebui să fie biți de script portabili care fac echivalentul biților de cod din secțiunea anterioară. Matricea este înlocuită cu "$@"
, lista parametrilor poziționali. Setarea "$@"
se face cu set
și ghilimele duble în jurul "$@"
sunt importante (acestea determină ca elementele listei să fie citate individual).
În primul rând, pur și simplu stocați o comandă cu argumente în "$@"
și rulați-o:
set -- ls -l "/tmp/test/my dir" "$@"
Setarea condiționată a părților opțiunilor liniei de comandă pentru o comandă:
set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@"
Folosind numai "$@"
pentru opțiuni și operanzi :
set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@"
(Desigur, "$@"
este de obicei umplut cu argumentele scriptului în sine, deci tu ” Va trebui să le salvez undeva înainte de a re-propune "$@"
.)
Aveți grijă cu eval
!
Deoarece eval
introduce un nivel suplimentar de procesare a ofertelor și extinderii, trebuie să fiți atenți la introducerea utilizatorului. De exemplu, acest lucru funcționează atât timp cât utilizatorul nu introduceți niciun ghilimel:
read -r filename cmd="ls -l "$filename"" eval "$cmd";
Dar dacă dau intrarea "$(uname)".txt
, scriptul dvs. este fericit execută înlocuirea comenzii.
O versiune cu tablouri este imună la th de vreme ce cuvintele sunt păstrate separate tot timpul, nu există nicio ofertă sau altă prelucrare pentru conținutul filename
.
read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}"
Referințe
- Împărțirea cuvintelor în BashGuide
- BashFAQ / 050 sau ” Încerc să pun o comandă într-o variabilă, dar cazurile complexe eșuează întotdeauna! ”
- Întrebarea De ce scriptul meu shell se înabuse de spațiul alb sau alte caractere speciale? , care discută o serie de probleme legate de citarea și spațiul alb, inclusiv stocarea comenzilor.
Comentarii
- poți ocoli chestiunea de evaluare făcând
cmd="ls -l $(printf "%q" "$filename")"
. nu este drăguț, dar dacă utilizatorul este pe cale să folosească uneval
, vă ajută. ‘ este, de asemenea, foarte util pentru trimiterea comenzii deși lucruri similare, cum ar fissh foohost "ls -l $(printf "%q" "$filename")"
, sau în spritul acestei întrebări:ssh foohost "$cmd"
. - Nu are legătură directă, dar ați codificat direct directorul? În acest caz, vă recomandăm să vă uitați la alias. Ceva de genul:
$ alias abc='ls -l "/tmp/test/my dir"'
Răspuns
Cel mai sigur mod de a rulați o comandă (non-banală) este eval
. Apoi puteți scrie comanda așa cum ați face pe linia de comandă și este executată exact ca și când tocmai ați fi introdus-o. Dar trebuie să citați totul.
Caz simplu:
abc="ls -l "/tmp/test/my dir"" eval "$abc"
caz nu atât de simplu:
# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc"
Comentarii
- Bună @HaukeLaging, ce este \ ‘ ‘?
- @jumping_monkey Nu puteți avea un
'
într-un șir citat'
. Astfel, trebuie să (1) finalizați / întrerupeți citatul, (2) să scăpați de următorul'
pentru a obține literalmente, (3) tastați ghilimelul literal și (4) caz de întrerupere reluați șirul citat:(1)'(2)\(3)'(4)'
- Bună @HaukeLaging, interesant și de ce nu doar
abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile'
? - @jumping_monkey Să nu mai vorbim că tocmai v-am explicat de ce nu funcționează: nu ar avea sens să testați codul înainte de a-l posta?
Răspuns
Al doilea semn de ghilimelă rup comanda.
Când rulez:
abc="ls -l "/home/wattana/Desktop"" $abc
Mi-a dat o eroare.
Dar când rulez
abc="ls -l /home/wattana/Desktop" $abc
Nu există nicio eroare
Nu există nicio modalitate de a remedia această problemă la momentul respectiv (pentru mine), dar puteți evita eroarea neavând spațiu în numele directorului.
Acest răspuns a spus că comanda eval poate fi utilizată pentru a remedia acest lucru, dar nu funcționează pentru mine 🙁
Comentarii
- Da, asta funcționează atâta timp cât ‘ nu are nevoie de ex. nume de fișiere cu spații încorporate (sau cele care conțin caractere glob).
Răspuns
Dacă acest lucru nu funcționează cu ""
, atunci ar trebui să utilizați ``
:
abc=`ls -l /tmp/test/my\ dir/`
Actualizați mai bine way:
abc=$(ls -l /tmp/test/my\ dir/)
Comentarii
- Aceasta stochează rezultatul comenzii într-o variabilă. OP vrea (în mod ciudat) să salveze comanda în sine într-o variabilă. Oh, și ar trebui să începeți cu adevărat să utilizați
$( command... )
în loc de backticks. - Vă mulțumesc foarte mult pentru clarificări și sfaturi!
Răspuns
Ce zici de un singur liner 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 ..
Comentarii
- Întrebarea este despre BASH, nu despre python.
Răspuns
Un alt truc simplu pentru a rula orice comandă (trivială / non-trivială) stocată în abc
variabila este:
$ history -s $abc
și împingeți UpArrow
sau Ctrl-p
pentru a-l aduce în linia de comandă. Spre deosebire de orice altă metodă, în acest fel îl puteți edita înainte de execuție, dacă este necesar.
Această comandă va adăuga conținutul variabilei ca intrare nouă în istoricul Bash și îl puteți reaminti prin UpArrow
.