Come memorizzare un comando in una variabile in uno script di shell?


113

Vorrei memorizzare un comando da utilizzare in un secondo momento in una variabile (non l'output del comando, ma il comando stesso)

Ho uno script semplice come segue:

command="ls";
echo "Command: $command"; #Output is: Command: ls

b=`$command`;
echo $b; #Output is: public_html REV test... (command worked successfully)

Tuttavia, quando provo qualcosa di un po 'più complicato, non riesce. Ad esempio, se faccio

command="ls | grep -c '^'";

L'output è:

Command: ls | grep -c '^'
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
ls: cannot access '^': No such file or directory

Qualche idea su come posso memorizzare un comando del genere (con pipe / comandi multipli) in una variabile per un uso successivo?


10
Usa una funzione!
gniourf_gniourf

Risposte:


146

Usa eval:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

27
$ (...) è ora consigliato invece di backticks. y = $ (eval $ x) mywiki.wooledge.org/BashFAQ/082
James Broadhead

14
evalè una pratica accettabile solo se ti fidi dei contenuti delle tue variabili. Se stai eseguendo, ad esempio, x="ls $name | wc"(o anche x="ls '$name' | wc"), questo codice è una corsia preferenziale per le vulnerabilità di injection o di escalation dei privilegi se quella variabile può essere impostata da qualcuno con meno privilegi. (Iterare su tutte le sottodirectory /tmp, per esempio? Faresti meglio a fidarti di ogni singolo utente del sistema per non farne uno chiamato $'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/').
Charles Duffy

9
evalè un enorme magnete per i bug che non dovrebbe mai essere raccomandato senza un avvertimento sul rischio di un comportamento di analisi imprevisto (anche senza stringhe dannose, come nell'esempio di @ CharlesDuffy). Ad esempio, prova x='echo $(( 6 * 7 ))'e poi eval $x. Potresti aspettarti che stampi "42", ma probabilmente non lo farà. Puoi spiegare perché non funziona? Puoi spiegare perché ho detto "probabilmente"? Se le risposte a queste domande non sono ovvie per te, non dovresti mai toccarle eval.
Gordon Davisson

1
@Student, prova a eseguire in set -xanticipo per registrare i comandi eseguiti, il che renderà più facile vedere cosa sta succedendo.
Charles Duffy,

1
@Student, consiglierei anche shellcheck.net per segnalare errori comuni (e cattive abitudini che non dovresti prendere).
Gordon Davisson

41

Do Non utilizzare eval! Presenta il rischio maggiore di introdurre l'esecuzione di codice arbitrario.

BashFAQ-50 - Sto cercando di inserire un comando in una variabile, ma i casi complessi falliscono sempre.

Mettetela in un array e ampliare tutte le parole con doppi apici "${arr[@]}"per non lasciare che la IFSscissione le parole a causa della scissione Word .

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

e vedere il contenuto dell'array all'interno. Il declare -pti permette di vedere il contenuto dell'array all'interno di ogni parametro di comando in indici separati. Se uno di questi argomenti contiene spazi, la citazione all'interno durante l'aggiunta all'array impedirà che venga diviso a causa della divisione delle parole.

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

ed eseguire i comandi come

"${cmdArgs[@]}"
23:15:18

(o) usa completamente una bashfunzione per eseguire il comando,

cmd() {
   date '+%H:%M:%S'
}

e chiama semplicemente la funzione

cmd

POSIX sh non ha array, quindi il più vicino possibile è creare un elenco di elementi nei parametri posizionali. Ecco un shmodo POSIX per eseguire un programma di posta

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() {
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"
}

Nota che questo approccio può gestire solo comandi semplici senza reindirizzamenti. Non può gestire reindirizzamenti, pipeline, cicli for / while, istruzioni if, ecc

Un altro caso d'uso comune è durante l'esecuzione curl con più campi di intestazione e payload. Puoi sempre definire argomenti come di seguito e invocare curlil contenuto dell'array espanso

curlArgs=('-H' "keyheader: value" '-H' "2ndkeyheader: 2ndvalue")
curl "${curlArgs[@]}"

Un altro esempio,

payload='{}'
hostURL='http://google.com'
authToken='someToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

ora che le variabili sono definite, utilizzare un array per memorizzare gli argomenti del comando

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

e ora esegui una corretta espansione citata

curl "${curlCMD[@]}"

Questo non funziona per me, l'ho provato Command=('echo aaa | grep a')e "${Command[@]}", sperando che esegua letteralmente il comando echo aaa | grep a. Non è così. Mi chiedo se ci sia un modo sicuro per sostituire eval, ma sembra che ogni soluzione che abbia la stessa forza evalpotrebbe essere pericolosa. Non è vero?
Studente

In breve, come funziona se la stringa originale contiene una barra verticale "|"?
Studente

@Student, se la tua stringa originale contiene una pipe, allora quella stringa deve passare attraverso le parti non sicure del parser bash per essere eseguita come codice. Non utilizzare una stringa in questo caso; usa invece una funzione: Command() { echo aaa | grep a; }- dopodiché puoi semplicemente eseguire Command, o result=$(Command), o simili.
Charles Duffy

1
@Student, a destra; ma ciò fallisce intenzionalmente , perché quello che chiedi di fare è intrinsecamente insicuro .
Charles Duffy

1
@Studente: ho aggiunto una nota all'ultimo per menzionare che non funziona in determinate condizioni
Inian

25
var=$(echo "asdf")
echo $var
# => asdf

Utilizzando questo metodo, il comando viene immediatamente valutato e il valore restituito viene memorizzato.

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

Lo stesso con il backtick

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

L'uso di eval in $(...)non lo renderà valutato in seguito

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

Utilizzando eval, viene valutato quando evalviene utilizzato

stored_date="date" # < storing the command itself
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (wait a few seconds)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ Time changed

Nell'esempio sopra, se devi eseguire un comando con argomenti, inseriscili nella stringa che stai memorizzando

stored_date="date -u"
# ...

Per gli script bash questo è raramente rilevante, ma un'ultima nota. Stai attento con eval. Valuta solo le stringhe che controlli, mai le stringhe provenienti da un utente non attendibile o create da input dell'utente non affidabile.

  • Grazie a @CharlesDuffy per avermi ricordato di citare il comando!

Ciò non risolve il problema originale in cui il comando contiene una barra verticale "|".
Studente

@Nate, nota che eval $stored_datepotrebbe andare bene quando stored_datecontiene solo date, ma eval "$stored_date"è molto più affidabile. Esegui str=$'printf \' * %s\\n\' *'; eval "$str"con e senza le virgolette intorno alla finale "$str"per un esempio. :)
Charles Duffy

@CharlesDuffy Grazie, ho dimenticato di citare. Scommetto che il mio linter si sarebbe lamentato se mi fossi preso la briga di farlo funzionare.
Nate

1

Per bash, memorizza il tuo comando in questo modo:

command="ls | grep -c '^'"

Esegui il tuo comando in questo modo:

echo $command | bash

1
Non sono sicuro, ma forse questo modo di eseguire il comando presenta gli stessi rischi che comporta l'uso di "eval".
Derek Hazell

0

Ho provato diversi metodi:

printexec() {
  printf -- "\033[1;37m$\033[0m"
  printf -- " %q" "$@"
  printf -- "\n"
  eval -- "$@"
  eval -- "$*"
  "$@"
  "$*"
}

Produzione:

$ printexec echo  -e "foo\n" bar
$ echo -e foo\\n bar
foon bar
foon bar
foo
 bar
bash: echo -e foo\n bar: command not found

Come puoi vedere, solo il terzo ha "$@"dato il risultato corretto.


0

Fai attenzione a registrare un ordine con: X=$(Command)

Questo viene ancora eseguito anche prima di essere chiamato. Per verificare e confermare questo, puoi fare:

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed

-1
#!/bin/bash
#Note: this script works only when u use Bash. So, don't remove the first line.

TUNECOUNT=$(ifconfig |grep -c -o tune0) #Some command with "Grep".
echo $TUNECOUNT                         #This will return 0 
                                    #if you don't have tune0 interface.
                                    #Or count of installed tune0 interfaces.

-8

Non è necessario memorizzare i comandi nelle variabili anche se è necessario utilizzarli in seguito. basta eseguirlo come di consueto. Se memorizzi in variabile, avresti bisogno di qualche tipo di evalistruzione o invocare un processo shell non necessario per "eseguire la tua variabile".


1
Il comando che memorizzerò dipenderà dalle opzioni che invio, quindi invece di avere tonnellate di istruzioni condizionali nella maggior parte del mio programma è molto più facile memorizzare il comando di cui ho bisogno per un uso successivo.
Benjamin

1
@ Benjamin, quindi memorizza almeno le opzioni come variabili e non il comando. ad esempiovar='*.txt'; find . -name "$var"
kurumi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.