Bash вставляя кавычки в строку перед выполнением - PullRequest
17 голосов
/ 22 мая 2011

Мне удалось отследить сделанную странную проблему в сценарии инициализации, над которым я работаю. Я упростил задачу в следующем примере:

> set -x                           # <--- Make Bash show the commands it runs
> cmd="echo \"hello this is a test\""
+ cmd='echo "hello this is a test"'
> $cmd
+ echo '"hello' this is a 'test"'  # <--- Where have the single quotes come from?
"hello this is a test"

Почему bash вставляет эти дополнительные одинарные кавычки в выполненную команду?

Дополнительные кавычки не вызывают никаких проблем в приведенном выше примере, но они действительно вызывают головную боль.

Для любопытных фактический код проблемы:

cmd="start-stop-daemon --start $DAEMON_OPTS \
    --quiet \
    --oknodo \
    --background \
    --make-pidfile \
    $* \
    --pidfile $CELERYD_PID_FILE
    --exec /bin/su -- -c \"$CELERYD $CELERYD_OPTS\" - $CELERYD_USER"

Который производит это:

start-stop-daemon --start --chdir /home/continuous/ci --quiet --oknodo --make-pidfile --pidfile /var/run/celeryd.pid --exec /bin/su -- -c '"/home/continuous/ci/manage.py' celeryd -f /var/log/celeryd.log -l 'INFO"' - continuous

И, следовательно:

/bin/su: invalid option -- 'f'

Примечание: я использую здесь команду su, так как мне нужно убедиться, что пользовательский virtualenv настроен до запуска celeryd. --chuid не предоставит это

Ответы [ 2 ]

19 голосов
/ 22 мая 2011

Потому что, когда вы пытаетесь выполнить команду с

$cmd

происходит только один уровень расширения. $cmd содержит echo "hello this is a test", который расширен на 6 разделенных пробелами токенов:

  1. echo
  2. "hello
  3. this
  4. is
  5. a
  6. test"

и это то, что показывает вывод set -x: он помещает одинарные кавычки вокруг токенов, которые содержат двойные кавычки, чтобы понять, что представляют собой отдельные токены.

Если вы хотите, чтобы $cmd был развернут в строку, к которой затем снова применяются все правила цитирования bash, попробуйте выполнить команду с помощью:

bash -c "$cmd"

или (как указывает @bitmask в комментариях, и это, вероятно, более эффективно)

eval "$cmd"

вместо

$cmd
2 голосов
/ 14 марта 2018

Используйте Bash массивы для достижения желаемого поведения, не прибегая к очень опасным (см. Ниже) eval и bash -c.

Использование массивов:

declare -a CMD=(echo --test-arg \"Hello\ there\ friend\")
set -x
echo "${CMD[@]}"
"${CMD[@]}"

выводит:

+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello there friend"'
--test-arg "Hello there friend"

Будьте внимательны, чтобы ваш вызов массива был заключен в двойные кавычки;в противном случае Bash пытается выполнить ту же самую «минимальную безопасность», экранируя специальные символы:

declare -a CMD=(echo --test-arg \"Hello\ there\ friend\")
set -x
echo "${CMD[@]}"
${CMD[@]}

выводит:

+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello' there 'friend"'
--test-arg "Hello there friend"

ASIDE: почему eval опасен?

eval безопасен только в том случае, если вы можете гарантировать, что каждый переданный ему ввод не изменит неожиданно способ работы команды в eval.

Пример : AsПолностью надуманный пример, скажем, у нас есть скрипт, который выполняется как часть нашего процесса автоматического развертывания кода.Сценарий сортирует некоторые входные данные (в данном случае три строки текста с жестким кодом) и выводит отсортированный текст в файл, имя которого основано на имени текущего каталога.Подобно исходному вопросу SO, поставленному здесь, мы хотим динамически построить параметр --output=, переданный для сортировки, но мы должны (не так ли) на самом деле полагаться на eval из-за функции автоматического цитирования в Bash "safety".

echo $'3\n2\n1' | eval sort -n --output="$(pwd | sed 's:.*/::')".txt

Запуск этого сценария в каталоге /usr/local/deploy/project1/ приводит к созданию нового файла в /usr/local/deploy/project1/project1.txt.

Так или иначе, если бы пользователь создавал подкаталог проекта с именем owned.txt; touch hahaha.txt; echoСценарий фактически запускает следующую серию команд:

echo $'3\n2\n1'
sort -n --output=owned.txt; touch hahaha.txt; echo .txt

Как видите, это совсем не то, что мы хотим.Но вы можете спросить, в этом надуманном примере, не маловероятно ли, что пользователь может создать каталог проекта owned.txt; touch hahaha.txt; echo, и если они могли, разве мы уже не в беде?

Может быть, нокак насчет сценария, в котором скрипт анализирует не текущее имя каталога, а имя удаленной ветви репозитория git с исходным кодом?Если вы не планируете проявлять предельную осторожность в отношении ограничения или дезинфекции каждого контролируемого пользователем артефакта, чье имя, идентификатор или другие данные используются вашим сценарием, держитесь подальше от eval.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...