Может ли кто-нибудь помочь в этом:
Я хочу выполнить некоторые команды в сценарии оболочки, прочитав их из другого текстового файла примерно так:
command_file.txt содержит:
EXECUTED_OK_$(date)
EXECUTED_OK_$(hostname)
оболочка script.sh
#!/bin/sh
while read command
do
echo $command
done < command_file.txt
Моя проблема в том, что эхо печатает как текст, как в командном файле. Как я могу расширить его и напечатать фактическую дату и имя хоста.
Например Ожидаемый результат должен быть:
EXECUTED_OK_22-02-2010 11:10:10
EXECUTED_OK_localhost
Но я получаю:
EXECUTED_OK_$(date)
EXECUTED_OK_$(hostname)
Поведение, которое вы видите, является желательным и соответствует спецификации. Если бы данные были расширены как код «за вашей спиной», было бы практически невозможно написать код, обрабатывающий ненадежные данные в bash.
(опять же, вы в основном просим, чтобы ваши данные должны выполняться как код, для чего и предназначен eval
. Это также общая плохая идея, и ее лучше избегать, но... ну, это является то, о чем вы просите).
@Charles Duffy Если мы рассмотрим ситуацию, в которой нам нужно создавать динамические команды во время выполнения, это может быть проще, чем запускать отдельные команды и объединять окончательный вывод.
...но если вам нужен эффект eval
, почему вы на самом деле не с использованиемeval
? Это плохая практика, но это потому, что вещь, которую он выполняет (нарушение границы между данными и кодом) является плохой практикой. Вы активно просите об этом (чтобы оценить ваши данные как код, полагая, что его содержимое будет безопасным для выполнения), так что вы можете использовать его, если вы действительно, В самом деле уверены, что это то, что вы хотите.
@Charles Duffy, вот почему я сейчас использую функциональный подход, я подумал, что может быть какой-то обходной путь для этой ситуации, кроме eval
Этот подход с белым списком не позволит выполнять произвольный код, как это делает eval
. Если безопасность не является проблемой, вы должны придерживаться eval
, так как внешние, используемые в этом примере, имеют много угловых случаев, для которых замена не будет работать должным образом.
Программа заменяет определенные внешние ссылки в каждой команде вашего командного файла их выходными данными перед печатью команды:
#!/bin/bash
while read command
do
while read -a external ; do
output=$( "${external[@]}" )
command=$( sed "s/\$(${external})/${output}/g" <<< "$command" )
done <<END
date -R
hostname
END
echo "$command"
done < command_file.txt
Чтобы использовать больше внешних элементов, вам нужно добавить их в документ между <<END
и END
. Я добавил -R
к date
, чтобы продемонстрировать внешний вид с аргументами.
Как было указано, никакая обработка (например, кавычки, замены), кроме разделения слов, не будет выполняться для внешних команд (пробелы можно экранировать (one\ two
) вместо кавычек).
...аналогично, read -a external
не обрабатывает кавычки и т.д. так же, как и синтаксический анализ оболочки, поэтому простое сокращение до "${external[@]}"
на самом деле не исправляет случаи, не указанные в кавычках $anything
, становится неправильным. (Попробуйте touch 'hello world'
в качестве примера; разделение строк без кавычек приведет touch
к созданию двух отдельных файлов, 'hello
и world'
).
@CharlesDuffy Спасибо, это не сработало бы с аргументами. Я изменил свой пример, чтобы использовать массив.
используйте eval
, чтобы расширить переменные и выполнить его повторно.
$ foo='EXECUTED_OK_$(date)'
$ echo $foo
EXECUTED_OK_$(date)
$ eval "echo $foo"
EXECUTED_OK_Tue May 21 12:39:45 PDT 2019
Обновлено: eval
мощный, но может быть опасным; это в основном инъекция кода. Цитирование аргументов — хорошая идея; см. комментарии ниже для получения дополнительных объяснений.
eval "echo $foo"
немного меньше ошибок (или, по крайней мере, немного меньше вводит в заблуждение о том, как это работает). Если вы запускаете eval echo $foo
без кавычек, когда foo='*'
, например, вы заменяете *
списком имен файлов, запускается доeval
, что делает его поведение почти невозможным предсказать без знания этих имен.
(Если у вас есть foo='*'; eval echo $foo
, вам лучше надеяться, что никто не создал файл с touch '$(rm -rf ~)'
; тогда как с foo='*'; eval "echo $foo"
*
расширяется послеeval
, поэтому имена файлов, в которые он расширяется, не могут быть выполнены как код).
Кстати, см. БашПитфоллы #14 обсуждение того, почему цитировать даже в echo "$foo"
— хорошая идея.
Один из вариантов - использовать eval
, предполагая, что вы доверять входной файл
#!/bin/sh
while read command; do
eval "echo $command"
done < command_file.txt
Рассмотрите
envsubst
, если это доступно.