1

У меня есть сценарий Linux bash, с целью:

COMMAND_TO_EXECUTE="foo "$1" --option1 --option2 "$2
exec $COMMAND_TO_EXECUTE

Проблема: скрипт не работает

Подсказка отладки : если я echo $COMMAND_TO_EXECUTE затем вставляю его в окно терминала, это работает отлично.

Так.. Строка за $COMMAND_TO_EXECUTE допустима в терминале, но недопустима в скрипте. Есть ли что-то, что я должен сделать с $COMMAND_TO_EXECUTE прежде чем пытаться выполнить это?

Усиление информации: команды для выполнения были wget или curl , у меня одинаковые проблемы с обоими. (Я правильно цитировал строки и экранированные символы, такие как &). Как упоминалось ранее ... команда работает нормально, если я эхо, а затем вырезать / вставить его.

Я озадачен и чувствую, что упускаю что-то элементарное, потому что команда работает вырезано / вставлено, но не в сценарии.

ОБНОВЛЕНИЕ: ответ Polyergic ниже работал для меня. bash -c "$COMMAND_TO_EXECUTE" работает правильно.

3 ответа3

3

Краткий ответ: см. BashFAQ # 50: я пытаюсь поместить команду в переменную, но сложные случаи всегда терпят неудачу! ,

Длинный ответ: Когда оболочка анализирует командную строку, она делает такие вещи, как выяснение того, какие части строки находятся в кавычках (или экранированные или какие-либо еще), прежде чем заменить переменные; таким образом, если у вас есть какие-либо кавычки или экранирование внутри значений переменных, к тому времени, когда они подставляются в команду, им уже поздно что-либо делать. Он немного разбирает значения подставляемых переменных: он разбивает их на "слова" на основе пробелов, табуляции и т.д. (Независимо от того, находятся ли они в кавычках) и расширяет подстановочные знаки (опять же, даже если они в кавычках). Кстати, уже слишком поздно, чтобы другие части синтаксиса оболочки, такие как каналы, перенаправления и т.д., Вступили в силу. Чтобы проиллюстрировать это, у меня есть команда printargs которая печатает свои аргументы. Вот что происходит, когда я пытаюсь сохранить сложную команду в переменной:

$ cmd='printargs " * " | cat >outfile &'
$ $cmd
Got 8 arguments:
    '"'
    'file1.txt'
    'file2.txt'
    '"'
    '|'
    'cat'
    '>outfile'
    '&'

Обратите внимание, что кавычки, канал и т.д. Все обрабатываются как обычные символы, а не синтаксис оболочки, но звездочка рассматривается как подстановочный знак и заменяется списком имен файлов.

Есть несколько решений, в зависимости от того, почему вы помещаете команду в переменную в первую очередь:

  • Если вам не нужно помещать команду в переменную, не делайте этого. Команды предназначены для выполнения, поэтому, если нет веской причины не делать этого, просто выполняйте их напрямую.

  • Если вы хотите использовать по существу одну и ту же команду несколько раз и не хотите каждый раз выписывать все целиком (правило программирования "не повторяйте себя"), используйте функцию:

    execute_command() {
        foo "$1" --option1 --option2 "$2"
    }
    

    ... а затем позвонить несколько раз. Обратите внимание, что я помещаю ссылки на переменные в двойные кавычки; Вы должны (почти) всегда делать это, чтобы к ним не применялось разбиение слов и расширение по шаблону.

  • Если вам нужно построить команду динамически, используйте массив:

    post_data=("var1=value1" "var2=value2" ...)
    
    post_args=()
    for post_arg in "${post_data{@]}"; do   # Note that this is the correct idiom for expanding an array in bash
        post_data+=(-d "$post_arg")
    done
    curl "${post_args[@]}" "$url"
    

Обратите внимание, что это работает для сложных аргументов для одной команды, но не будет работать для таких вещей, как конвейеры, перенаправления и фоновые операции (&), потому что они снова анализируются перед заменой переменных.

Напоследок несколько предупреждений:

  • Не используйте eval или bash -c если вы точно не знаете , как работает синтаксический анализ оболочки (и если вы задаете этот вопрос, вы точно не знаете, как работает синтаксический анализ оболочки). Оба эти фактора вызывают дополнительный уровень синтаксического анализа, который, как правило, отлично работает при тестировании, но иногда дает сбой (по непонятным причинам). eval имеет заслуженную репутацию источника действительно странных и тонких ошибок; и bash -c делает то же самое, просто добавив подоболочку, чтобы сделать ее еще более странной.

  • Двойные кавычки для ссылок на переменные для предотвращения неожиданного разделения слов и расширения подстановочных знаков.

  • Вы, вероятно, не хотите использовать exec - он выходит из текущей оболочки (и сценария оболочки) и заменяет ее командой; это, вероятно, не то, что вы хотели.

1

Идиома, которую я использовал, такова:

cmd="some command in a string"
"$cmd"

Обратите внимание, что в отличие от использования exec , вызывающий скрипт будет продолжен.

Некоторая модификация может потребоваться, если ваша команда содержит специальные символы.

Вот еще один вариант, который я использовал (я не помню, почему я так сделал):

cmd="some command in a string"
bash -c "$cmd"

Вот полный пример скрипта, который я использую, чтобы превзойти ожидаемый результат от cronjobs:

#!/bin/sh

# otrap
# Copyright © 2007-2014 Shad Sterling <me@shadsterling.com>
# Released under CC-BY-SA, http://creativecommons.org/licenses/by-sa/4.0/

if [ "" = "$2" ]; then
    echo traps the output of a command and echos the output only if the size doesn\'t match
    echo USAGE: $0 \<size\> [\"test\"] \<command\>
    echo size is the expected output size
    echo \"test\" means echo the output even if the size does match
    echo command is the command to silence unless it\'s output doesn\'t match 
    exit 2
fi;

test=false
size=$1; shift
echo=false
if [ "test" == "$1" ]; then
test=true; shift
echo=true
fi
cmd="$*"

TF=~/tmp/otrap.#$PPID.log
if [ "false" != "$echo" ]; then
    echo file: "$TF"
    echo running: "$cmd"
fi
starttime=`date +%R`
$SHELL -c "$cmd" > $TF 2>&1
ret=$?
endtime=`date +%R`
ST=`\`dirname $0\`/filesize $TF 2>&1`
if [ "$size" != "$ST" ]; then
    echo=true;
fi;
if [ "false" != "$echo" ]; then
    echo " command:" "$cmd"
    echo "   start:" $starttime
    echo "  finish:" $endtime
    echo "returned:" $ret
    echo "    size:" $ST
    echo "expected:" $size
    echo --------------------------------------------
    cat $TF
fi
rm $TF
exit $ret
0

Поскольку вы ввели его, COMMAND-TO-EXECUTE не является допустимым именем переменной оболочки, удалите тире. Это может дать некоторые подсказки:

$ echo $COMMAND-TO-EXECUTE
-TO-EXECUTE

$ COMMAND-TO-EXECUTE=Test
COMMAND-TO-EXECUTE=Test: command not found

$ COMMAND_TO_EXECUTE=Test

$ echo $COMMAND_TO_EXECUTE
Test

$ 

Тогда "foo "$1" --option1 --option2 "$2 выглядит немного странно. Если вы хотите, чтобы COMMAND_TO_EXECUTE содержал " -символы » , замените это на "foo \"$1\" --option1 --option2 $2" - также обратите внимание, что в конце я переместил кавычку. Этим вы "защищаете" 2 доллара, чтобы не вызывать странных эффектов, прежде чем оставить его exec .

Так как вы не дали пример команды для тестирования / отладки, я не могу думать больше ...

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .