Параметры в стиле dd для скрипта bash


19

Я хотел бы передать параметры в bash-скрипт в стиле dd. В основном я хочу

./script a=1 b=43

иметь тот же эффект, что и

a=1 b=43 ./script

Я думал, что смогу достичь этого с помощью:

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

Какой хороший способ гарантировать, что evalэто безопасно, то есть, что "$arg"соответствует статическому (без выполнения кода), присвоению переменной?

Или есть лучший способ сделать это? (Я хотел бы сохранить это простым).


Это отмечено тегом bash. Вы хотите решение, совместимое с Posix, или вы принимаете решения bash?
Ричи

То, что говорит тег, это то, что я имею в виду :)
PSkocik

Ну, вы можете просто проанализировать его как шаблон с =разделителем и выполнить присваивание с более тщательно сконструированным eval. Просто для безопасности, для личного пользования, я бы к этому, как ты это сделал.
Орион

Ответы:


16

Вы можете сделать это в bash без eval (и без искусственного выхода):

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

Редактировать: Основываясь на комментарии Стефана Шазеласа, я добавил флаги в объявление, чтобы избежать присвоения назначенной переменной, уже объявленной как массив или целочисленная переменная, что позволит избежать ряда случаев, в которых 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). Я исправил скобки, спасибо.
Ричи

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

frostschutz заслуживает похвалы за большую часть рефакторинга.

Я положил это в исходный файл с глобальной переменной:

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. Затем я использовал это в оболочке CLI Github API, которую я писал, и использовал ту же оболочку для настройки клона github указанного репозитория (начальная загрузка это весело).

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


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

1
на самом деле вы можете избавиться от цикла for и if и использовать вместо него 1 $, так как вы сдвигаетесь и все ...
frostschutz

1
Хех, доказательство того, что мозговой штурм работает. :)
lcd047

1
Сбор урожая утром идеи: вы можете даже избавиться от keyи val, и просто написать eval "${1%%=*}"=\${1#*=}. Но это в значительной степени так далеко, eval "$1"как в @ rici's declare "$arg", очевидно, не сработает. Также остерегайтесь установки таких вещей, как PATHили PS1.
15:30

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, которые было бы слишком опасно устанавливать таким образом. Мне нравится, как это очень коротко и отвечает на вопрос, но я пойду с решением sanitize + eval + prefix, так как оно все еще безопаснее и, следовательно, более универсально (в средах, где вы не хотите, чтобы пользователи вмешивались в эту среду). ). Спасибо и +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|

сдвиг убьет неправильные вещи, если вы не
разорвете

@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используется здесь для обработки вызова new aliasиз контекста varname в кавычках - не для назначения точно. И evalвызывается только в том случае, если предыдущее aliasопределение было успешным, и хотя я знаю, что множество различных реализаций будут принимать множество различных типов значений для имен псевдонимов, я еще не нашел оболочки, которая будет принимать полностью пустую ,

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

В любом случае, если определение псевдонима выполнено успешно, он объявит псевдоним для имени $var. И evalбудет вызывать это только в том aliasслучае , если он также не начинается с числа, иначе evalполучает только нулевой аргумент. Таким образом, если выполняются оба условия, выполняется evalопределение aliasи определение переменной, сохраненной в alias, после чего новый псевдоним быстро удаляется из хеш-таблицы.


aliasВ этом контексте полезно также то, что вы можете распечатать свою работу. при запросеalias напечатает дважды цитируемый оператор safe-for-shell-rexecution .

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.