Як зберігати команду у змінній у сценарії оболонки?


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Дозволяє побачити вміст всередині масиву з кожним параметром команди в окремих індексів. Якщо один такий аргумент містить пробіли, цитування всередині під час додавання до масиву запобігає розбиттю через Word-Splitting.

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не має масивів, тому найближчим до вас можна підійти, щоб скласти список елементів у позиційних параметрах. Ось POSIX shспосіб запуску поштової програми

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

Зауважте, що цей підхід може обробляти лише прості команди без перенаправлення. Він не може обробляти перенаправлення, конвеєри, цикли для / while, якщо оператори тощо

Інший поширений випадок використання - це при запуску 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
@ Студент, справа; але це не вдається навмисно , тому що те, що ти просиш зробити, є по суті небезпечним .
Чарльз Даффі

1
@Student: Я додав примітка в останньому кажучи він не працює за певних умов
Inian

25
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

Те саме з backtick

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 Спасибі, я забув про цитування. Б'юсь об заклад, що мій лайнер поскаржився б, якби я намагався його запустити.
Нейт

0

Для 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"
kurumi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.