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


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.