Bash вставка цитат в строку перед выполнением

Мне удалось отслеживать сложную проблему в init script, над которой я работаю. Я упростил проблему в следующем примере:

> 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 не предоставит этого

Ответы

Ответ 1

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

$cmd

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

  • echo
  • "hello
  • this
  • is
  • a
  • test"

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

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

bash -c "$cmd"

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

eval "$cmd"

вместо

$cmd