parametry w stylu dd do skryptu bash


19

Chciałbym przekazać parametry do skryptu bash w stylu dd. Zasadniczo chcę

./script a=1 b=43

mieć taki sam efekt jak

a=1 b=43 ./script

Myślałem, że mogę to osiągnąć za pomocą:

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

Jaki jest dobry sposób na zapewnienie evalbezpieczeństwa, tzn. Że "$arg"odpowiada statycznemu (bez wykonywania kodu), przypisaniu zmiennej?

Czy jest na to lepszy sposób? (Chciałbym, żeby to było proste).


Oznaczono to bash. Czy potrzebujesz rozwiązania zgodnego z Posix, czy zaakceptujesz rozwiązania bash?
rici

To, co mówi tag, mam na myśli :)
PSkocik

Cóż, możesz po prostu przeanalizować go jako wzór z =separatorem i wykonać zadanie z bardziej starannie skonstruowanym eval. Dla bezpieczeństwa, do prywatnego użytku, zrobiłbym to tak, jak ty to zrobiłeś.
Orion,

Odpowiedzi:


16

Możesz to zrobić w bashu bez eval (i bez sztucznego ucieczki):

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

Edycja: Na podstawie komentarza Stéphane'a Chazelasa dodałem flagi do deklaracji, aby uniknąć przypisania zmiennej już zadeklarowanej jako zmienna tablicowa lub całkowita, co pozwoli uniknąć szeregu przypadków, w których declarewartość części key=valargumentu będzie oceniana . ( +aSpowoduje błąd, jeśli zmienna, która ma zostać ustawiona, jest już zadeklarowana na przykład jako zmienna tablicowa.) Wszystkie te luki dotyczą użycia tej składni do ponownego przypisania istniejących zmiennych (tablica lub liczba całkowita), które zwykle byłyby dobrze znane zmienne powłoki.

W rzeczywistości jest to tylko przypadek klasy ataków iniekcyjnych, które będą w równym stopniu wpływać na evalrozwiązania oparte na rozwiązaniach: naprawdę byłoby znacznie lepiej zezwolić tylko na znane nazwy argumentów niż na ślepo ustawić dowolną zmienną, która pojawiła się w wierszu poleceń. (Zastanów się, co się stanie, jeśli PATHna przykład wiersz polecenia zostanie ustawiony . Lub zresetuje się, PS1aby uwzględnić ocenę, która nastąpi przy następnym wyświetleniu monitu).

Zamiast używać zmiennych bash, wolałbym użyć tablicy asocjacyjnej nazwanych argumentów, która jest łatwiejsza do ustawienia i znacznie bezpieczniejsza. Alternatywnie może ustawić rzeczywiste zmienne bash, ale tylko wtedy, gdy ich nazwy znajdują się w tablicy asocjacyjnej uzasadnionych argumentów.

Jako przykład tego drugiego podejścia:

# 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
Wyrażenie regularne wydaje się mieć błędne nawiasy kwadratowe. Być może to wykorzystać zamiast: ^[[:alpha:]_][[:alnum:]_]*=?
lcd047

1
@ lcd047: foo=to jedyny sposób, aby ustawić foo na pusty ciąg, więc powinno być dozwolone (IMHO). Naprawiłem wsporniki, dzięki.
rici

3
declarejest tak niebezpieczny jak eval(można nawet powiedzieć gorzej, ponieważ nie jest tak oczywiste, że jest tak niebezpieczny). Spróbuj na przykład nazwać to 'DIRSTACK=($(echo rm -rf ~))'argumentem.
Stéphane Chazelas,

1
@PSkocik: +xis „not -x”. -a= tablica indeksowana, -A= tablica asocjacyjna, -i= zmienna całkowita. Zatem: tablica nieindeksowana, tablica asocjacyjna, liczba całkowita.
lcd047,

1
Zauważ, że w następnej wersji bashmoże być konieczne dodanie, +caby wyłączyć zmienne złożone lub +Fwyłączyć zmienne zmienne. Nadal używałbym evaltam, gdzie wiesz, gdzie stoisz.
Stéphane Chazelas,

9

POSIX jeden (zestawy $<prefix>varzamiast $varproblemów uniknąć specjalnymi zmiennymi podoba IFS/ PATH...):

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

Nazywany jako myscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2, przypisuje:

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

6

Rozwiązanie lcd047 refaktoryzowane z DD_OPT_prefiksem na stałe :

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

frostschutz zasługuje na uznanie za większość refaktoryzacji.

Umieszczam to w pliku źródłowym jako zmienną globalną:

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

eval "$DD_OPTS_PARSE" robi całą magię.

Wersja dla funkcji to:

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

W użyciu:

eval "$DD_OPTS_PARSE_LOCAL"

Zrobiłem z tego repo , wraz z testami i README.md. Następnie użyłem tego w opakowującym interfejsie API Github API, które pisałem, i użyłem tego samego opakowania, aby skonfigurować klon github wspomnianego repozytorium (ładowanie jest fajne).

Bezpieczne przekazywanie parametrów dla skryptów bash w jednym wierszu. Cieszyć się. :)


1
ale możesz się pozbyć *=*i przestać zastępować klucz / val tam, gdzie nie ma =. (ponieważ refaktoryzowałeś): P
frostschutz

1
w rzeczywistości możesz pozbyć się pętli for i if i zamiast tego użyć 1 $, ponieważ przesuwasz się i wszystko ...
frostschutz

1
Heh, dowód na to, że burza mózgów działa. :)
lcd047

1
Dla zbierania pomysłów Rano: można dostać nawet pozbyć keyi val, i po prostu pisać eval "${1%%=*}"=\${1#*=}. Ale to tak daleko, jak to możliwe, eval "$1"ponieważ w @ rici declare "$arg"nie będzie oczywiście działać. Uważaj też na ustawianie takich rzeczy jak PATHlub PS1.
lcd047

1
Dziękuję - myślałem, że to była zmienna oceniana. Doceniam twoją cierpliwość do mnie - to dość rażąco oczywiste. W każdym razie nie - poza tym wymyślonym, wygląda dobrze. Wiesz, że możesz rozszerzyć to na dowolną powłokę case. Prawdopodobnie to nie ma znaczenia, ale na wypadek, gdybyś nie wiedział ...
mikeserv

5

Obsługiwana jest klasyczna skorupa Bourne'a, a skorupa Bash i Korn nadal obsługują -kopcję. Gdy ma to zastosowanie, wszelkie „ ddpodobne” opcje polecenia w dowolnym miejscu wiersza poleceń są automatycznie konwertowane na zmienne środowiskowe przekazywane do polecenia:

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

Nieco trudniej jest przekonać, że są to zmienne środowiskowe; uruchomienie tego działa dla mnie:

$ 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
$

Pierwszy env | greppokazuje brak zmiennych środowiskowych z pojedynczą małą literą. Pierwszy bashpokazuje, że do skryptu nie są przekazywane argumenty -c, a środowisko zawiera trzy jednoliterowe zmienne. set +kAnuluje -ki pokazuje, że ta sama komenda ma teraz argumenty przekazywane do niej. ( a=1Traktowano to jak $0scenariusz; można to również udowodnić za pomocą odpowiedniego echa).

Uzyskuje się to, o co pyta pytanie - pisanie ./script.sh a=1 b=2powinno być takie samo jak pisanie a=1 b=2 ./script.sh.

Pamiętaj, że napotkasz problemy, jeśli wypróbujesz takie sztuczki w skrypcie:

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

"$@"Traktuje dosłownie; nie jest ponownie analizowany w celu znalezienia zmiennych w stylu przypisania (zarówno w, jak bashi ksh). Próbowałem:

#!/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

i tylko already_invoked_with_minus_kzmienna środowiskowa jest ustawiona w execskrypcie 'd.


Bardzo miła odpowiedź! Interesujące jest to, że nie zmieni to ŚCIEŻKI, chociaż HOME można zmieniać, więc musi istnieć coś w rodzaju czarnej listy (zawierającej przynajmniej ŚCIEŻKĘ) zmiennych env, która byłaby zbyt niebezpieczna, aby ustawić ją w ten sposób. Uwielbiam sposób, w jaki jest to bardzo krótki i odpowiada na pytanie, ale wybiorę rozwiązanie sanitize + eval + prefix, ponieważ jest ono jeszcze bezpieczniejsze, a tym samym bardziej uniwersalne (w środowiskach, w których użytkownicy nie chcą bałaganić środowiska) ). Dzięki i +1.
PSkocik

2

Moja próba:

#! /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' "$@"

Działa z (prawie) arbitralnymi śmieciami na 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 zabije złe rzeczy, jeśli nie przerwiesz pętli przy pierwszym parametrze innym niż x = y
frostschutz

@frostschutz Dobry punkt, edytowany.
lcd047

Dobra robota w uogólnianiu tego. Myślę, że można to trochę uprościć.
PSkocik

Czy miałeś okazję rzucić okiem na moją edycję?
PSkocik

Proszę spojrzeć na moją edycję. Tak mi się podoba (+ może tylko shiftzamiast shift 1). W przeciwnym razie dzięki!
PSkocik

0

Jakiś czas temu zdecydowałem się aliasna tego rodzaju pracę. Oto moja kolejna odpowiedź:


Czasami jednak możliwe jest rozdzielenie oceny i wykonania takich oświadczeń. Na przykład aliasmożna użyć do wstępnej oceny polecenia. W poniższym przykładzie definicja zmiennej jest zapisywana w aliasie, który można pomyślnie zadeklarować tylko wtedy, gdy $varoceniana zmienna nie zawiera bajtów, które nie pasują do znaków alfanumerycznych ASCII lub _.

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

evalsłuży tutaj do obsługi wywoływania nowego aliasz kontekstu cytowanej nazwy zmiennej - nie do przypisania dokładnie. I evalw ogóle wywoływany jest tylko wtedy, gdy poprzednia aliasdefinicja się powiedzie, i chociaż wiem, że wiele różnych implementacji zaakceptuje wiele różnych rodzajów nazw aliasów, nie znalazłem jeszcze powłoki, która zaakceptuje całkowicie pustą .

Definicja w aliasie ma _$varjednak na celu zapewnienie, że żadne znaczące wartości środowiska nie zostaną zapisane. Nie znam żadnych wartych uwagi wartości środowiskowych zaczynających się od _ i zwykle jest to bezpieczny zakład na deklarację półprywatną.

W każdym razie, jeśli definicja aliasu zakończy się powodzeniem, zadeklaruje alias nazwany na $varwartość. I evalwywoła to tylko, aliasjeśli również nie zaczyna się od liczby - w przeciwnym razie evaldostaje tylko zerowy argument. Jeśli więc oba warunki są spełnione, evalwywoływana jest aliasi zapisywana aliasjest definicja zmiennej , po czym nowy alias jest natychmiast usuwany z tabeli skrótów.


W aliastym kontekście przydatne jest również to, że możesz wydrukować swoją pracę. po zapytaniu aliaswydrukuje podwójnie cytowane oświadczenie o bezpiecznej wymianie powłoki .

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

WYNIK

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.