Параметри стилю dd до сценарію bash


19

Я хотів би передати парами баш-скрипту, стилю dd. В основному, я хочу

./script a=1 b=43

мати такий же ефект, як і

a=1 b=43 ./script

Я думав, що зможу досягти цього за допомогою:

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

Який хороший спосіб забезпечити evalбезпеку, тобто це"$arg" відповідає статичному (без виконання коду) присвоєнню змінної?

Або є кращий спосіб зробити це? (Я хотів би, щоб це було просто).


Це позначено як bash. Ви хочете рішення, сумісне з Posix, чи приймете рішення bash?
rici

Про те, що говорить тег, є те, що я маю на увазі :)
PSkocik

Ну, ви можете просто розібрати його як зразок з =роздільником і виконати завдання більш ретельно побудованим овалом. Просто для безпеки, для приватного користування, я б це робив так, як ви це робили.
orion

Відповіді:


16

Ви можете це зробити в баші без eval (і без штучного втечі):

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

Редагувати: На підставі коментаря Stéphane Chazelas я додав прапори до заяви, щоб уникнути того, щоб присвоєна змінна вже була оголошена як масив чи ціла змінна, що дозволить уникнути ряду випадків, в яких declareбуде оцінено значення значення key=valаргументу. (Виклик +aбуде помилкою, якщо встановлена ​​змінна вже оголошена змінною масиву, наприклад.) Усі ці вразливості стосуються використання цього синтаксису для перепризначення існуючих (масивів чи цілих чисел) змінних, які, як правило, є добре відомими змінні оболонки

Насправді, це лише екземпляр класу ін'єкційних атак, які однаково впливатимуть evalна рішення, засновані на основі: дійсно було б набагато краще дозволити лише відомі імена аргументів, ніж наосліп встановити будь-яку змінну, яка присутня в командному рядку. (Поміркуйте, що станеться, якщо, наприклад, встановити командний рядок PATH. Або скидає налаштуванняPS1 щоб включити деяку оцінку, яка відбудеться під час наступного відображення запиту.)

Замість того, щоб використовувати bash змінні, я вважаю за краще використовувати асоціативний масив з іменованими аргументами, який є і простішим у встановленні, і набагато безпечнішим. Крім того, він може встановлювати фактичні змінні bash, але лише якщо їх назви містяться в асоціативному масиві законних аргументів.

Як приклад останнього підходу:

# 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
Здається, що у регулярному вираженні дужки неправильні. Можливо використовувати замість цього: ^[[:alpha:]_][[:alnum:]_]*=?
lcd047

1
@ lcd047: foo=це єдиний спосіб встановити foo до порожнього рядка, тому його слід дозволити (IMHO). Я зафіксував дужки, дякую.
rici

3
declareприблизно так само небезпечно, як eval(можна навіть сказати гірше, оскільки це не так очевидно, що це так небезпечно). Спробуйте, наприклад, викликати це 'DIRSTACK=($(echo rm -rf ~))'аргументом.
Стефан Шазелас

1
@PSkocik: +xє "ні -x". -a= індексований масив, -A= асоціативний масив, -i= ціла змінна. Таким чином: не індексований масив, не асоціативний масив, не ціле число.
lcd047

1
Зауважте, що з наступною версією bash, можливо, вам потрібно буде додати, +cщоб вимкнути складові змінні або +Fвідключити плаваючі. Я б все одно користувався evalтам, де ти знаєш, де ти стоїш.
Стефан Шазелас

9

POSIX (встановлюється $<prefix>varзамість того, $varщоб уникнути проблем зі спеціальними змінними, такими як IFS/ PATH...):

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

Називається як myscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2, воно призначить:

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

6

Рішення lcd047 відновлено із твердим DD_OPT_префіксом:

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

заморожувати заслуговує на заслугу більшості рефакторингу.

Я поміщую це у вихідний файл із глобальною змінною:

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

eval "$DD_OPTS_PARSE" робить усі чари.

Версія для функцій буде:

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

У вживанні:

eval "$DD_OPTS_PARSE_LOCAL"

Я зробив репост із цього, доповнений тестами та README.md. Потім я використав це в обгортці Github API CLI, про яку я писав, і я застосував ту саму обгортку, щоб створити клон github зазначеного репо (завантаження - це весело).

Безпечне передача параметрів для bash-скриптів лише в одному рядку. Насолоджуйтесь. :)


1
але ви можете позбутися *=*та перестати замінювати ключ / val там, де немає =. (так як ви були рефакторинг): P
frostschutz

1
насправді ви можете позбутися циклу for і if та і використовувати натомість $ 1, оскільки ви
зміщуєте

1
Гей, доказ того, що мозковий штурм працює. :)
lcd047

1
Збір врожаю вранці ідеї: ви можете навіть позбутися keyі val, і просто написати eval "${1%%=*}"=\${1#*=}. Але це майже все, наскільки це йде, eval "$1"як declare "$arg"очевидно , що в @ rici не буде працювати. Також остерігайтеся налаштування таких речей, як PATHабо PS1.
lcd047

1
Дякую - я думав, що це оцінена змінна. Я ціную ваше терпіння зі мною - це досить яскраво очевидно. У всякому разі, ні - крім того, що уявлялося, це добре виглядає. Ви знаєте, хоча ви могли розширити це для роботи в будь-якій оболонці case. Напевно, це не має значення, але про всяк випадок ви не знали ...
mikeserv

5

Класична оболонка Bourne підтримується, а оболонка Bash і Korn все ще підтримується, -kопція. Коли це дійсно, будь-які ddпараметри команди « подібні» в будь-якому місці командного рядка автоматично перетворюються в змінні середовища, передані команді:

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

Трохи важче переконатись, що вони змінні середовища; це працює для мене:

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

Перший env | grepдемонструє відсутність змінних середовища з однієї малої літери. Перший bashпоказує, що аргументу, переданого скрипту, виконаному через -c, немає, а середовище містить три однобуквенні змінні. set +kскасовує-k , і показує , що та ж команда тепер є аргументи , передані йому. (До цього a=1трактували як$0 сценарію до сценарію; ви можете довести це теж відповідним відлунням.)

Цим досягається те, що задається питанням - тим, що вводити текст ./script.sh a=1 b=2 повинно бути таким же, як і введення текстуa=1 b=2 ./script.sh .

Будьте в курсі, що у вас виникли проблеми, якщо ви спробуєте такі хитрощі в сценарії:

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

"$@"Трактується дослівно; не повторно аналізується, щоб знайти змінні стилю призначення (і в bashі в ksh). Я намагався:

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

і already_invoked_with_minus_kв execсценарії 'd встановлюється лише змінна середовища .


Дуже приємна відповідь! Цікаво, що це не змінить PATH, хоча HOME є змінним, тому повинно бути щось на зразок чорного списку (що містить принаймні PATH) env vars, що було б занадто небезпечно встановити таким чином. Мені подобається, як це ультракоротко і відповідає на запитання, але я піду з рішенням sanitize + eval + з префіксом, оскільки це все-таки безпечніше і тим самим більш універсально придатне (у середовищах, коли ви не хочете, щоб користувачі возилися з оточенням ). Дякую та +1
PSkocik

2

Моя спроба:

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

Він працює з (майже) довільним сміттям на 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 знищить неправильні речі, якщо ви не порушите цикл при першому параметрі non-x = y
frostschutz

@frostschutz Добрий момент, відредаговано.
lcd047

Хороша робота з її узагальнення. Я думаю, це можна трохи спростити.
PSkocik

Ви отримали можливість поглянути на мою редакцію?
PSkocik

Погляньте, будь ласка, на мою редакцію. Саме так мені подобається (+ можливо просто shiftзамість shift 1). Інакше дякую!
PSkocik

0

Деякий час тому я влаштувався на aliasтаку роботу. Ось ще одна моя відповідь:


Однак іноді можливе відокремлення оцінки та виконання таких тверджень. Наприклад, aliasможе використовуватися для попередньої оцінки команди. У наступному прикладі визначення змінної зберігається в псевдонімі, який можна успішно оголосити, лише якщо $varзмінна, яку вона оцінює, не містить байтів, що не відповідають буквено-цифровим знакам ASCII або _.

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

evalтут використовується для обробки виклику нового aliasз контекстуваного контексту varname - не для призначення саме. І evalназивається взагалі лише тоді, коли попереднійalias визначення було успішним, і, хоча я знаю, що багато різних реалізацій приймуть безліч різних видів значень для псевдонімів, я ще не знайшов оболонки, яка б прийняла повністю порожню .

Визначення в псевдонімі призначене для _$var , однак, це для того, щоб не було записано жодних значущих значень середовища. Я не знаю жодних примітних значень середовища, що починаються з _, і зазвичай це безпечна ставка для напівприватного декларування.

У будь-якому випадку, якщо визначення псевдоніма буде успішним, він оголосить псевдонім, названий для $var's значення. І evalлише зателефоную, що aliasякщо воно також не починається з числа - інше evalотримує лише нульовий аргумент. Отже, якщо обидві умови виконуються, evalвикликується aliasі визначене значення змінної, збережене у aliasфайлі, після чого новий псевдонім негайно видаляється з хеш-таблиці.


aliasУ цьому контексті також корисно те , що ви можете надрукувати свою роботу. aliasбуде надруковано подвійне цитування заяви про безпечне для оболонки виконання .

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

ВИХІД

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.