Come possiamo eseguire un comando memorizzato in una variabile?

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

Mi chiedevo perché i seguenti modi per eseguire il comando precedente falliscono o riescono?

$ 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 

Commenti

Answer

Questo è stato discusso in una serie di domande su unix.SE, cercherò di raccogliere tutto problemi che posso trovare qui. Riferimenti alla fine.


Perché non riesce

Il motivo per cui devi affrontare questi problemi è suddivisione delle parole e il fatto che le virgolette espanse dalle variabili non agiscono come virgolette, ma sono solo caratteri ordinari.

casi presentati nella domanda:

Lassegnazione qui assegna la singola stringa ls -l "/tmp/test/my dir" a abc:

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

sotto, $abc è suddiviso in spazi bianchi e ls ottiene i tre argomenti -l, "/tmp/test/my e dir" (con una virgoletta allinizio della seconda e unaltra dietro alla terza). Lopzione funziona, ma il percorso viene elaborato in modo errato:

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

Qui lespansione è citata, quindi viene mantenuta come una singola parola. La shell prova per trovare un programma chiamato letteralmente ls -l "/tmp/test/my dir", spazi e virgolette inclusi.

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

E qui, $abc viene suddiviso e solo la prima parola risultante viene considerata come argomento di -c, quindi Bash esegue solo ls nella directory corrente. Le altre parole sono argomenti per bash e vengono utilizzate per riempire $0, $1, ecc.

$ bash -c $abc "my dir" 

Con bash -c "$abc" e eval "$abc", cè un fase di elaborazione della shell, che fa funzionare le virgolette, ma fa anche sì che tutte le espansioni della shell vengano elaborate di nuovo , quindi cè “il rischio di eseguire accidentalmente, ad esempio, una sostituzione di comando dai dati forniti dallutente, a meno che tu” sei molto attento pieno di citazioni.


Modi migliori per farlo

I due modi migliori per memorizzare un comando sono a) utilizzare invece una funzione, b) utilizzare una variabile array ( o i parametri posizionali).

Utilizzando una funzione:

Dichiara semplicemente una funzione con il comando allinterno ed eseguire la funzione come se fosse un comando. Le espansioni nei comandi allinterno della funzione vengono elaborate solo quando il comando viene eseguito, non quando è definito e non è necessario citare i singoli comandi.

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

Utilizzo di un array:

Gli array consentono di creare variabili composte da più parole in cui le singole parole contengono bianco spazio. Qui, le singole parole vengono memorizzate come elementi di array distinti e lespansione "${array[@]}" espande ogni elemento come parole di shell separate:

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

La sintassi è leggermente orribile, ma gli array consentono anche di costruire la riga di comando pezzo per pezzo. Ad esempio:

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

o mantieni costanti parti della riga di comando e usa larray per riempirne solo una parte, come opzioni o nomi di file:

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

Lo svantaggio degli array è che “non sono una funzionalità standard, quindi le semplici shell POSIX (come dash, limpostazione predefinita /bin/sh in Debian / Ubuntu) non li supporta (ma vedi sotto). Bash, ksh e zsh lo fanno, quindi è probabile che il tuo sistema abbia una shell che supporta gli array.

Uso di "$@"

Nelle shell senza supporto per array con nome, si possono ancora usare i parametri posizionali (lo pseudo-array "$@") per contenere gli argomenti di un comando.

I seguenti dovrebbero essere bit di script portatili che fanno lequivalente dei bit di codice nella sezione precedente. Larray è sostituito con "$@", lelenco dei parametri posizionali. Limpostazione di "$@" viene eseguita con set e le virgolette doppie intorno a "$@" sono importanti (questi fanno sì che gli elementi della lista vengano citati individualmente).

Per prima cosa, semplicemente memorizzando un comando con argomenti in "$@" ed eseguendolo:

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

Impostazione condizionale di parti delle opzioni della riga di comando per un comando:

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

Utilizzando solo "$@" per opzioni e operandi :

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

(Ovviamente, "$@" è solitamente riempito con gli argomenti dello script stesso, quindi tu ” Dovrò salvarli da qualche parte prima di riutilizzare "$@".)


Fai attenzione a eval !

Poiché eval introduce un ulteriore livello di elaborazione delle virgolette e delle espansioni, devi prestare attenzione allinput dellutente. Ad esempio, funziona fintanto che lutente non digita virgolette singole:

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

Ma se danno linput "$(uname)".txt, il tuo script felicemente esegue la sostituzione del comando.

Una versione con array è immune a th poiché le parole sono tenute separate per tutto il tempo, non ci sono virgolette o altre elaborazioni per i contenuti di filename.

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

Riferimenti

Commenti

  • puoi aggirare il problema delle citazioni eval eseguendo cmd="ls -l $(printf "%q" "$filename")". non è carino, ma se lutente è deciso a usare un eval, aiuta. ‘ è anche molto utile per inviare il comando attraverso cose simili, come ssh foohost "ls -l $(printf "%q" "$filename")", o nello spirito di questa domanda: ssh foohost "$cmd".
  • Non direttamente correlato, ma hai hard-coded la directory? In tal caso, potresti voler guardare lalias. Qualcosa come: $ alias abc='ls -l "/tmp/test/my dir"'

Rispondi

Il modo più sicuro per eseguire un comando (non banale) è eval. Quindi puoi scrivere il comando come faresti sulla riga di comando e viene eseguito esattamente come se lo avessi appena inserito. Ma devi citare tutto.

Caso semplice:

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

caso non così semplice:

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

Commenti

  • Ciao @HaukeLaging, cosè \ ‘ ‘?
  • @jumping_monkey Non puoi avere un ' allinterno di una stringa ' citata . Quindi devi (1) finire / interrompere la citazione, (2) sfuggire al successivo ' per ottenerlo letteralmente, (3) digitare la citazione letterale e (4) in in caso di interruzione riprendi la stringa tra virgolette: (1)'(2)\(3)'(4)'
  • Ciao @HaukeLaging, interessante e perché non solo abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile' ?
  • @jumping_monkey Per non parlare del fatto che ti ho appena spiegato perché non funziona: ‘ non avrebbe senso testare il codice prima di pubblicarlo?

Answer

La seconda virgoletta interrompe il comando.

Quando eseguo:

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

Mi ha dato un errore.

Ma quando ho eseguito

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

Non ci sono errori

Non cè modo di risolvere questo problema al momento (per me) ma puoi evitare lerrore non avendo spazio nel nome della directory.

Questa risposta diceva che il comando eval può essere utilizzato per risolvere questo problema ma non funziona per me 🙁

Commenti

  • Sì, funziona finché ‘ non è necessario ad es. nomi di file con spazi incorporati (o quelli contenenti caratteri glob).

Risposta

Se questo non funziona con "", dovresti utilizzare ``:

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

Aggiorna meglio way:

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

Commenti

  • Questo memorizza il risultato del comando in una variabile. LOP vuole (stranamente) salvare il comando stesso in una variabile. Oh, e dovresti davvero iniziare a usare $( command... ) invece dei backtick.
  • Grazie mille per i chiarimenti e i suggerimenti!

Risposta

Che ne dici di un one-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 .. 

Commenti

  • La domanda riguarda BASH, non Python.

Risposta

Un altro semplice trucco per eseguire qualsiasi comando (banale / non banale) memorizzato in abc la variabile è:

$ history -s $abc 

e premi UpArrow o Ctrl-p per portarlo nella riga di comando. A differenza di qualsiasi altro metodo in questo modo, puoi modificarlo prima dellesecuzione se necessario.

Questo comando aggiungerà il contenuto della variabile come nuova voce alla cronologia di Bash e potrai richiamarlo UpArrow.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *