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


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.