Как сохранить команду в переменной в сценарии оболочки?


113

Я хотел бы сохранить команду для использования в более поздний период в переменной (не вывод команды, а саму команду)

У меня есть простой сценарий:

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

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

Однако когда я пробую что-то посложнее, у меня ничего не получается. Например, если я сделаю

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

Результат:

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

Есть идеи, как сохранить такую ​​команду (с трубами / несколькими командами) в переменной для последующего использования?


10
Используйте функцию!
— gniourf_gniourf

Ответы:


146

Используйте eval:

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

27
$ (...) теперь рекомендуется вместо backticks. y = $ (eval $ x) mywiki.wooledge.org/BashFAQ/082
— Джеймс

14
evalявляется приемлемой практикой, только если вы доверяете содержимому своих переменных. Если вы, скажем, работаете x="ls $name | wc"(или даже x="ls '$name' | wc"), то этот код - быстрый путь к уязвимостям внедрения или повышения привилегий, если эта переменная может быть установлена ​​кем-то с меньшими привилегиями. ( /tmpНапример, перебор всех подкаталогов в ? Лучше доверять каждому пользователю в системе, чтобы он не вызвал ни одного из них $'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/').
— Чарльз Даффи

9
evalявляется огромным магнитом для ошибок, который никогда не следует рекомендовать без предупреждения о риске неожиданного синтаксического анализа (даже без вредоносных строк, как в примере @ CharlesDuffy). Например, попробуйте x='echo $(( 6 * 7 ))'и потом eval $x. Вы могли ожидать, что это напечатает «42», но, вероятно, этого не произойдет. Вы можете объяснить, почему это не работает? Вы можете объяснить, почему я сказал «вероятно»? Если ответы на эти вопросы для вас не очевидны, никогда не трогайте eval.
— Гордон Дэвиссон

1
@Student, попробуйте запустить set -xзаранее, чтобы записать выполненные команды, чтобы было легче увидеть, что происходит.
— Чарльз Даффи

1
@Student Я бы также порекомендовал shellcheck.net, чтобы указывать на типичные ошибки (и вредные привычки, которые вам не следует усваивать ).
— Гордон Дэвиссон

41

Вы не использовать eval! Он имеет большой риск внедрения произвольного выполнения кода.

BashFAQ-50 - Я пытаюсь поместить команду в переменную, но в сложных случаях всегда не получается.

Поместите его в массив и заключите все слова в двойные кавычки, "${arr[@]}"чтобы не допустить IFSразделения слов из-за разделения слов .

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

и посмотреть содержимое массива внутри. Это declare -pпозволяет вам видеть содержимое массива внутри с каждым параметром команды в отдельных индексах. Если один из таких аргументов содержит пробелы, цитирование внутри при добавлении в массив предотвратит его разделение из-за разделения слов.

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

и выполните команды как

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

(или) полностью использовать bashфункцию для запуска команды,

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

и вызовите функцию просто

cmd

POSIX shне имеет массивов, поэтому самое близкое, что вы можете сделать, - это создать список элементов в позиционных параметрах. Вот shспособ POSIX для запуска почтовой программы

# 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" "$@"
}

Обратите внимание, что этот подход может обрабатывать только простые команды без перенаправлений. Он не может обрабатывать перенаправления, конвейеры, циклы for / while, операторы if и т. Д.

Другой распространенный вариант использования - запуск curlс несколькими полями заголовка и полезной нагрузкой. Вы всегда можете определить аргументы, как показано ниже, и вызвать curlсодержимое расширенного массива

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

Другой пример,

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

теперь, когда переменные определены, используйте массив для хранения аргументов вашей команды

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

а теперь сделайте правильное цитируемое расширение

curl "${curlCMD[@]}"

У меня это не работает, я пробовал Command=('echo aaa | grep a')и "${Command[@]}", надеюсь, он запускает буквально команду echo aaa | grep a. Это не так. Интересно, есть ли безопасный способ замены eval, но кажется, что каждое решение, имеющее такую ​​же силу, evalможет быть опасным. Не так ли?
— Студент

Короче говоря, как это работает, если исходная строка содержит черту '|'?
— Студент

@Student, если ваша исходная строка содержит канал, тогда эта строка должна пройти через небезопасные части парсера bash для выполнения как код. Не используйте в этом случае строку; используйте вместо этого функцию: Command() { echo aaa | grep a; }- после чего вы можете просто запустить Command, или result=$(Command), или что-то подобное.
— Чарльз Даффи

1
@Student, верно; но это не удается намеренно , потому что то, что вы просите сделать, по своей сути небезопасно .
— Чарльз Даффи,

1
@Student: Напоследок я добавил примечание, чтобы упомянуть, что он не работает при определенных условиях
— Иниан

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

Используя этот метод, команда немедленно оценивается, и ее возвращаемое значение сохраняется.

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

То же самое с обратной кавычкой

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

Использование eval в $(...)не приведет к его оценке позже

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

Используя eval, он оценивается, когда evalиспользуется

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

В приведенном выше примере, если вам нужно запустить команду с аргументами, поместите их в строку, которую вы сохраняете

stored_date="date -u"
# ...

Для сценариев bash это редко актуально, но последнее замечание. Будьте осторожны с eval. Оценивайте только те строки, которые вы контролируете, никогда строки, исходящие от ненадежного пользователя или созданные на основе ввода ненадежных пользователей.

  • Спасибо @CharlesDuffy за напоминание процитировать команду!

Это не решает исходную проблему, когда команда содержит черту '|'.
— Студент

@Nate, обратите внимание, что eval $stored_dateможет быть достаточно, когда он stored_dateтолько содержит date, но eval "$stored_date"намного надежнее. Запуск str=$'printf \' * %s\\n\' *'; eval "$str"с и без кавычек вокруг окончательного "$str"для примера. :)
— Чарльз Даффи

@CharlesDuffy Спасибо, я забыл про цитирование. Бьюсь об заклад, мой линтер пожаловался бы, если бы я потрудился его запустить.
— Nate

1

Для bash сохраните команду следующим образом:

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

Запустите вашу команду так:

echo $command | bash

1
Не уверен, но, возможно, этот способ запуска команды имеет те же риски, что и использование eval.
— Дерек

0

Я пробовал разные способы:

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

Вывод:

$ 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

Как видите, "$@"правильный результат дал только третий .


0

Будьте внимательны, регистрируя заказ в: X=$(Command)

Этот все еще выполняется Даже до вызова. Чтобы проверить и подтвердить это, вы можете сделать:

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

Нет необходимости хранить команды в переменных, даже если вам понадобится их использовать позже. просто выполните его как обычно. Если вы сохраняете переменную, вам понадобится какой-то evalоператор или вызовите какой-то ненужный процесс оболочки для «выполнения вашей переменной».


1
Команда, которую я сохраню, будет зависеть от параметров, которые я отправляю, поэтому вместо тонны условных операторов в основной части моей программы намного проще сохранить команду, которая мне нужна для дальнейшего использования.
— Бенджамин

1
@Benjamin, тогда по крайней мере сохраните параметры как переменные, а не команду. напримерvar='*.txt'; find . -name "$var"
— куруми
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.