Cum putem rula o comandă stocată într-o variabilă?

$ 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

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

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ă un eval, vă ajută. ‘ este, de asemenea, foarte util pentru trimiterea comenzii deși lucruri similare, cum ar fi ssh 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.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *