Безопасно ли оценивать $ BASH_COMMAND?

11

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

#!/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, я также хочу, чтобы эта команда была внутри переменной, чтобы я мог, например, записать ее в системный журнал.

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}"

Кажется, с этим хорошо справляются, но мне любопытно, если кто-то еще увидит проблему, которую я пропустил.

Мартин фон Виттих
источник
Это то, что я использовал в течение многих лет в оболочке sudo, я никогда не замечал проблемы.
14:00

Ответы:

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чтобы избежать ненужных подоболочек.

gniourf_gniourf
источник
1
Функция прекрасно работает, но, к сожалению, моя команда содержит перенаправления и оператор управления «excute in background» &. Мне пришлось перенести их в функцию, чтобы заставить ее работать - не очень приятно, но я не думаю, что есть лучший способ. Но не более того 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) Я могу войти в системный журнал.
Мартин фон Виттих
2
«Невозможно напечатать значение переменной без выполнения команды» - хороший пример, я не рассматривал такой случай. Хотя было бы неплохо, если бы bash имел дополнительную переменную, рядом с BASH_COMMANDкоторой содержится расширенная команда, потому что в какой-то момент он должен в любом случае выполнять расширение переменной для команды при ее выполнении :)
Martin von Wittich