parametri di tipo dd in uno script bash


19

Vorrei passare i parametri a uno script bash, in stile dd. Fondamentalmente, voglio

./script a=1 b=43

per avere lo stesso effetto di

a=1 b=43 ./script

Ho pensato di poterlo fare con:

for arg in "$@"; do
   eval "$arg";
done

Qual è un buon modo per garantire che evalsia sicuro, ovvero che "$arg"corrisponda a un'assegnazione variabile statica (nessuna esecuzione di codice)?

O c'è un modo migliore per farlo? (Vorrei mantenere questo semplice).


Questo è taggato con bash. Vuoi una soluzione conforme a Posix o accetterai soluzioni bash?
rici,

Quello che dice il tag è quello che voglio dire :)
PSkocik

Bene, potresti semplicemente analizzarlo come un modello con un =separatore e svolgere il compito con una valutazione più attentamente costruita. Solo per sicurezza, per uso privato, lo farei come hai fatto tu.
Orione,

Risposte:


16

Puoi farlo in bash senza eval (e senza fuga artificiale):

for arg in "$@"; do
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; then
    declare +i +a +A "$arg"
  fi
done

Modifica: Sulla base di un commento di Stéphane Chazelas, ho aggiunto flag alla dichiarazione per evitare che la variabile assegnata fosse già dichiarata come variabile array o intera, il che eviterà un numero di casi in cui declarevaluterà la parte di valore key=valdell'argomento. ( +aCiò causerà un errore se, ad esempio, la variabile da impostare è già dichiarata come variabile di matrice.) Tutte queste vulnerabilità riguardano l'uso di questa sintassi per riassegnare variabili esistenti (di matrice o intere), che in genere sarebbero ben note variabili shell.

In realtà, questa è solo un'istanza di una classe di attacchi di iniezione che influenzerà ugualmente le evalsoluzioni basate su: sarebbe davvero molto meglio consentire solo nomi di argomenti noti piuttosto che impostare ciecamente qualunque variabile fosse presente nella riga di comando. (Considera cosa succede se la riga di comando si imposta PATH, ad esempio. O ripristina PS1per includere una valutazione che avverrà alla visualizzazione del prompt successivo.)

Piuttosto che usare le variabili bash, preferirei usare una matrice associativa di argomenti denominati, che è sia più facile da impostare, sia molto più sicura. In alternativa, potrebbe impostare variabili bash effettive, ma solo se i loro nomi si trovano in una matrice associativa di argomenti legittimi.

Come esempio di quest'ultimo approccio:

# Could use this array for default values, too.
declare -A options=([bs]= [if]= [of]=)
for arg in "$@"; do
  # Make sure that it is an assignment.
  # -v is not an option for many bash versions
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= &&
        ${options[${arg%%=*}]+ok} == ok ]]; then
    declare "$arg"
    # or, to put it into the options array
    # options[${arg%%=*}]=${arg#*=}
  fi
done

1
La regex sembra avere le parentesi errate. Forse usare questo invece ^[[:alpha:]_][[:alnum:]_]*=:?
lcd047

1
@ lcd047: foo=è l'unico modo per impostare foo sulla stringa vuota, quindi dovrebbe essere consentito (IMHO). Ho riparato le staffe, grazie.
rici,

3
declareè pericoloso quanto eval(si potrebbe anche dire di peggio in quanto non è così evidente che è altrettanto pericoloso). Prova ad esempio a chiamarlo con 'DIRSTACK=($(echo rm -rf ~))'come argomento.
Stéphane Chazelas,

1
@PSkocik: +xè "non -x". -a= array indicizzato, -A= array associativo, -i= variabile intera. Pertanto: array non indicizzato, array non associativo, non intero.
lcd047

1
Si noti che con la versione successiva di bash, potrebbe essere necessario aggiungere +cper disabilitare le variabili composte o +Fper disabilitare quelle variabili. Userei ancora evaldove sai dove ti trovi.
Stéphane Chazelas,

9

Uno POSIX (imposta $<prefix>varinvece di $varevitare problemi con variabili speciali come IFS/ PATH...):

prefix=my_prefix_
for var do
  case $var in
    (*=*)
       case ${var%%=*} in
         "" | *[!abcdefghijiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]*) ;;
         (*) eval "$prefix${var%%=*}="'${var#*=}'
       esac
  esac
done

Chiamato come myscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2, assegnerebbe:

my_prefix_x <= 1
my_prefix_PATH <= /tmp/evil
my_prefix_1 <= 2

6

La soluzione di lcd047 refactored con un DD_OPT_prefisso hardcoded :

while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
  eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
done

frostschutz merita il merito per la maggior parte del refactoring.

L'ho inserito in un file sorgente con come variabile globale:

DD_OPTS_PARSE=$(cat <<'EOF'
  while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
    eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
  done
EOF
)

eval "$DD_OPTS_PARSE" fa tutta la magia.

Una versione per le funzioni sarebbe:

DD_OPTS_PARSE_LOCAL="${PARSE_AND_REMOVE_DD_OPTS/DD_OPT_/local DD_OPT_}"

In uso:

eval "$DD_OPTS_PARSE_LOCAL"

Ho realizzato un repository da questo, completo di test e README.md. Quindi l'ho usato in un wrapper CLI API Github che stavo scrivendo e ho usato lo stesso wrapper per configurare un clone github di detto repository (il bootstrap è divertente).

Passaggio sicuro dei parametri per gli script bash in una sola riga. Godere. :)


1
ma puoi sbarazzarti di *=*e smettere di sostituire key / val dove non c'è =. (da quando stavi
eseguendo il

1
infatti puoi sbarazzarti del ciclo for e del if e usare invece $ 1 invece, dato che stai cambiando e tutto ...
frostschutz

1
Heh, la prova che il brainstorming funziona. :)
lcd047

1
Raccogliere idee mattutine: puoi anche sbarazzarti di keye val, e semplicemente scrivere eval "${1%%=*}"=\${1#*=}. Ma è abbastanza lontano, eval "$1"dato che in @ rici declare "$arg"non funzionerà, ovviamente. Fai anche attenzione a impostare cose come PATHo PS1.
lcd047

1
Grazie - ho pensato che fosse la variabile valutata. Apprezzo la tua pazienza con me - è abbastanza evidente. Comunque, no - a parte quello immaginato, sembra buono. Sai che potresti estenderlo per lavorare con qualsiasi shell case. Probabilmente non importa, ma nel caso in cui non lo sapessi ...
Mikeserv,

5

Supportata la shell Bourne classica e la shell Bash e Korn ancora supportano, -kun'opzione. Quando è attivo, tutte le ddopzioni di comando " simili" in qualsiasi punto della riga di comando vengono convertite automaticamente in variabili di ambiente passate al comando:

$ set -k
$ echo a=1 b=2 c=3
$ 

È un po 'più difficile convincere che siano variabili d'ambiente; correre questo funziona per me:

$ set -k
$ env | grep '^[a-z]='   # No environment a, b, c
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: 
a=1
b=2
c=3
$ set +k
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: b=2 c=3
$

Il primo env | grepnon mostra variabili d'ambiente con una singola lettera minuscola. Il primo bashmostra che non ci sono argomenti passati allo script eseguito tramite -c, e l'ambiente contiene le tre variabili a lettera singola. Il set +kannulla la -k, e mostra che lo stesso comando contiene ora argomenti passati ad esso. (È a=1stato trattato come $0per la sceneggiatura; puoi dimostrarlo anche con un'eco adeguata.)

Ciò consente di ottenere ciò che pone la domanda: la digitazione ./script.sh a=1 b=2dovrebbe essere uguale alla digitazione a=1 b=2 ./script.sh.

Tieni presente che si verificano problemi se provi trucchi come questo all'interno di uno script:

if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

Il "$@"è trattato alla lettera; non viene nuovamente analizzato per trovare variabili di stile di assegnazione (in entrambi bashe ksh). Provai:

#!/bin/bash

echo "BEFORE"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='
if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

echo "AFTER"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='

unset already_invoked_with_minus_k

e solo la already_invoked_with_minus_kvariabile d'ambiente è impostata nello execscript 'd.


Risposta molto bella! È interessante che questo non cambierà il PERCORSO, sebbene HOME sia modificabile, quindi ci deve essere qualcosa di simile a una lista nera (che contiene almeno PERCORSO) di varchi ambientali che sarebbe troppo pericoloso per impostare in questo modo. Adoro come questo sia ultra-corto e risponda alla domanda, ma andrò con la soluzione sanitize + eval + prefisso in quanto è ancora più sicuro e quindi più universalmente utilizzabile (in ambienti in cui non si desidera che gli utenti interferiscano con l'ambiente ). Grazie e +1.
PSkocik,

2

Il mio tentativo:

#! /usr/bin/env bash
name='^[a-zA-Z][a-zA-Z0-9_]*$'
count=0
for arg in "$@"; do
    case "$arg" in
        *=*)
            key=${arg%%=*}
            val=${arg#*=}

            [[ "$key" =~ $name ]] && { let count++; eval "$key"=\$val; } || break

            # show time
            if [[ "$key" =~ $name ]]; then
                eval "out=\${$key}"
                printf '|%s| <-- |%s|\n' "$key" "$out"
            fi
            ;;
        *)
            break
            ;;
    esac
done
shift $count

# show time again   
printf 'arg: |%s|\n' "$@"

Funziona con immondizia (quasi) arbitraria su RHS:

$ ./assign.sh Foo_Bar33='1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0' '1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33'
|Foo_Bar33| <-- |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0|
arg: |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33|

$ ./assign.sh a=1 b=2 c d=4
|a| <-- |1|
|b| <-- |2|
arg: |c|
arg: |d=4|

shift ucciderà le cose sbagliate se non interrompi il ciclo al primo parametro non-x = y
frostschutz

@frostschutz Buon punto, modificato.
lcd047

Bel lavoro nel generalizzarlo. Penso che possa essere semplificato un po '.
PSkocik,

Hai avuto la possibilità di dare un'occhiata alla mia modifica?
PSkocik,

Dai un'occhiata alla mia modifica. È così che mi piace (+ forse solo shiftinvece di shift 1). Altrimenti grazie!
PSkocik,

0

Qualche tempo fa ho optato aliasper questo tipo di lavoro. Ecco un'altra mia risposta:


Tuttavia, a volte può essere possibile separare la valutazione e l'esecuzione di tali dichiarazioni. Ad esempio, aliaspuò essere utilizzato per pre-valutare un comando. Nell'esempio seguente la definizione della variabile viene salvata in un alias che può essere dichiarato correttamente solo se la $varvariabile che sta valutando non contiene byte che non corrispondono a caratteri alfanumerici ASCII o _.

LC_OLD=$LC_ALL LC_ALL=C
for var do    val=${var#*=} var=${var%%=*}
    alias  "${var##*[!_A-Z0-9a-z]*}=_$var=\$val" &&
    eval   "${var##[0-9]*}" && unalias "$var"
done;       LC_ALL=$LC_OLD

evalviene utilizzato qui per gestire l'invocazione del nuovo aliasda un contesto varname tra virgolette, non esattamente per l'assegnazione. E evalviene chiamato solo se la aliasdefinizione precedente ha esito positivo e mentre so che molte implementazioni diverse accetteranno molti tipi diversi di valori per i nomi di alias, non ho ancora trovato una shell che accetti una completamente vuota .

La definizione all'interno dell'alias è _$var, tuttavia, per garantire che non vengano sovrascritti valori ambientali significativi. Non conosco valori ambientali degni di nota che iniziano con un _ ed è generalmente una scommessa sicura per una dichiarazione semi-privata.

Comunque, se la definizione di alias ha esito positivo, dichiarerà un alias chiamato per $varil valore di. E evallo chiamerò solo aliasse anche non inizia con un numero - altrimenti evalottiene solo un argomento nullo. Pertanto, se vengono soddisfatte entrambe le condizioni, viene evaleffettuata la aliasdefinizione della variabile salvata in alias, dopodiché il nuovo alias viene prontamente rimosso dalla tabella hash.


Anche utile aliasin questo contesto è che puoi stampare il tuo lavoro. aliasstamperà una dichiarazione Safe-for-Shell-Rexecution quotata doppiamente quando richiesto.

sh -c "IFS=\'
    alias q=\"\$*\" q" -- \
    some args which alias \
    will print back at us

PRODUZIONE

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
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.