Чи безпечно оцінювати $ BASH_COMMAND?


11

Я працюю над сценарієм оболонки, який будує складну команду із змінних, наприклад, як це (з технікою, яку я навчився з FAQ Bash ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

some_complex_command \
  ${SOME_ARG:+--do-something "$SOME_ARG"} \
  ${ANOTHER_ARG:+--with "$ANOTHER_ARG"}

Цей скрипт динамічно додає параметри --do-something "$SOME_ARG"і --with "$ANOTHER_ARG"в some_complex_commandразі визначені ці змінні. Поки це добре працює.

Але тепер я також хочу мати можливість надрукувати або ввести команду під час її запуску, наприклад, коли мій сценарій запускається в режимі налагодження. Отже, коли мій скрипт працює some_complex_command --do-something abc --with def, я також хочу, щоб ця команда була всередині змінної, щоб я міг, наприклад, занести її в syslog.

Bash FAQ демонструє техніку використання DEBUGпастки та $BASH_COMMANDзмінної (наприклад, для налагодження) для цієї мети. Я спробував це з наступним кодом:

#!/bin/bash

ARG="test string"

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"

echo "Command was: ${COMMAND}"

Це працює, але він не розширює змінні в команді:

host ~ # ./test.sh
test string
Command was: echo "$ARG"

Я припускаю , що я повинен використовувати Eval для розширення , echo "$ARG"щоб echo test string(по крайней мере , я не знайшов спосіб , НЕ все evalж). Наступне працює:

eval echo "Command was: ${COMMAND}"

Він дає такий вихід:

host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string

Але я не дуже впевнений, чи можу я так evalбезпечно користуватися. Я безуспішно намагався використати деякі речі:

#!/bin/bash

ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER

echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"

Здається, це добре вдається, але мені цікаво, якщо хтось інший побачить проблему, яку я пропустив.


Це те, що я роками використовую в обгортці судо, я ніколи не помічав жодної проблеми.
w00t

Відповіді:


7

Однією з можливостей є створення функції обгортки, яка одночасно надрукує команду та виконає її наступним чином:

debug() {
    # This function prints (to stdout) its arguments and executes them
    local args=() idx=0 IFS=' ' c
    for c; do printf -v args[idx++] '%q' "$c"; done
    printf "%s\n" "${args[*]}"
    # Execute!
    "$@"
}

Так що у вашому сценарії ви можете:

debug echo "$ARG"

Не потрібно метуватися з пасткою. Недолік полягає в тому, що він додає деякі debugключові слова по всьому коду (але це повинно бути добре, звичайно мати такі речі, як твердження тощо).

Ви навіть можете додати глобальну змінну DEBUGта змінити debugфункцію так:

debug() {
    # This function prints (to stdout) its arguments if DEBUG is non-null
    # and executes them
    if [[ $DEBUG ]]; then
        local args=() idx=0 IFS=' ' c
        for c; do printf -v args[idx++] '%q' "$c"; done
        printf "%s\n" "${args[*]}"
    fi
    # Execute!
    "$@"
}

Тоді ви можете зателефонувати своєму сценарію:

$ DEBUG=yes ./myscript

або

$ DEBUG= ./myscript

або просто

$ ./myscript

залежно від того, хочете ви мати інформацію про налагодження чи ні.

Я використовував великі літери DEBUGзмінної, оскільки до неї слід ставитися як до змінної середовища. DEBUGце тривіальна і загальна назва, тому це може зіткнутися з іншими командами. Може бути , назвати це GNIOURF_DEBUGабо MARTIN_VON_WITTICH_DEBUGабо UNICORN_DEBUGякщо ви любите єдинорогів (і тоді ви , ймовірно , як поні теж).

Примітка. У debugфункції я ретельно відформатував кожен аргумент printf '%q'так, щоб вихід був правильно виведений і цитований, щоб він був повторно використаний дослівно з прямою копією та вставкою. Він також покаже вам, що саме бачив оболонку, оскільки ви зможете з'ясувати кожен аргумент (у випадку пробілів чи інших смішних символів). Ця функція також використовує пряме призначення за допомогою -vперемикача printf, щоб уникнути зайвих підзарядів.


1
Функція чудово працює, але, на жаль, моя команда містить перенаправлення та оператор управління "виправдання у фоновому режимі" &. Мені довелося перевести їх на функцію, щоб змусити її працювати - не дуже приємно, але я не думаю, що є кращий спосіб. Але не більше eval, тому у мене це відбувається для мене, що приємно :)
Мартін фон Віттіч

5

eval "$BASH_COMMAND" виконує команду.

printf '%s\n' "$BASH_COMMAND" друкує точно вказану команду плюс новий рядок.

Якщо команда містить змінні (тобто, якщо це щось на зразок cat "$foo"), то при друкуванні команди виводиться текст змінної. Не можна надрукувати значення змінної без виконання команди - придумай такі команди, як variable=$(some_function) other_variable=$variable.

Найпростіший спосіб отримати слід від виконання сценарію оболонки - встановити параметр xtraceоболонки, запустивши сценарій як bash -x /path/to/scriptабо викликаючи set -xвсередині оболонки. Трасування друкується до стандартної помилки.


1
Я знаю про це xtrace, але це не дає мені дуже багато контролю. Я спробував "set -x; ...; set + x", але: 1) надруковано також команду "set + x" для відключення xtrace 2) Я не можу приєднати вихід, наприклад, із позначкою часу 3) Я можу не записуйте його в syslog.
Мартін фон Віттіч

2
"Неможливо надрукувати значення змінної без виконання команди" - хороший приклад, я не вважав таким випадком. Було б непогано, якщо б у bash була додаткова змінна поруч, BASH_COMMANDяка містить розширену команду, тому що в якийсь момент вона повинна робити розширення змінної команди, так чи інакше, коли її виконує :)
Мартін фон Віттіч,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.