Цитирование аргументов командной строки в сценариях командной оболочки
Вопрос
Следующий сценарий оболочки принимает список аргументов, преобразует пути Unix в пути WINE / Windows и вызывает данный исполняемый файл в WINE.
#! /bin/sh
if [ "${1+set}" != "set" ]
then
echo "Usage; winewrap EXEC [ARGS...]"
exit 1
fi
EXEC="$1"
shift
ARGS=""
for p in "$@";
do
if [ -e "$p" ]
then
p=$(winepath -w $p)
fi
ARGS="$ARGS '$p'"
done
CMD="wine '$EXEC' $ARGS"
echo $CMD
$CMD
Однако что-то не так с цитированием аргументов командной строки.
$ winewrap '/home/chris/.wine/drive_c/Program Files/Microsoft Research/Z3-1.3.6/bin/z3.exe' -smt /tmp/smtlib3cee8b.smt
Executing: wine '/home/chris/.wine/drive_c/Program Files/Microsoft Research/Z3-1.3.6/bin/z3.exe' '-smt' 'Z: mp\smtlib3cee8b.smt'
wine: cannot find ''/home/chris/.wine/drive_c/Program'
Обратите внимание , что:
- Путь к исполняемому файлу обрывается через первый пробел, даже если он заключен в одинарные кавычки.
- Литерал " " в последнем пути преобразуется в символ табуляции.
Очевидно, что цитаты обрабатываются оболочкой не так, как я предполагал.Как я могу избежать этих ошибок?
Редактировать:"\ t" расширяется за счет двух уровней косвенности:Первый, "$p"
(и/или "$ARGS"
) расширяется на Z:\tmp\smtlib3cee8b.smt
;тогда, \t
расширяется до символа табуляции.Это (по-видимому) эквивалентно
Y='y\ty'
Z="z${Y}z"
echo $Z
что приводит к
zy\tyz
и нет
zy yz
Обновить: eval "$CMD"
делает свое дело.Тот самый "\t
" похоже, в проблеме виновато echo:"Если первый операнд равен -n или если какой-либо из операндов содержит символ обратной косой черты ( '\' ), результаты определяются реализацией". (Спецификация POSIX для echo
)
Решение
Если вы хотите назначить CMD, вы должны использовать
eval $ CMD
вместо просто $ CMD
в последней строке вашего скрипта. Это должно решить вашу проблему с пробелами в путях, я не знаю, что делать с " \ t " проблема.
Другие советы
- массивы bash непереносимы, но это единственный разумный способ обработки списков аргументов в shell
- Количество аргументов указано в ${#}
- Плохие вещи произойдут с вашим скриптом, если в текущем каталоге есть имена файлов, начинающиеся с тире
- Если последняя строка вашего скрипта просто запускает программу, и при выходе нет никаких ловушек, вы должны выполнить ее
Имея это в виду
#! /bin/bash
# push ARRAY arg1 arg2 ...
# adds arg1, arg2, ... to the end of ARRAY
function push() {
local ARRAY_NAME="${1}"
shift
for ARG in "${@}"; do
eval "${ARRAY_NAME}[\${#${ARRAY_NAME}[@]}]=\${ARG}"
done
}
PROG="$(basename -- "${0}")"
if (( ${#} < 1 )); then
# Error messages should state the program name and go to stderr
echo "${PROG}: Usage: winewrap EXEC [ARGS...]" 1>&2
exit 1
fi
EXEC=("${1}")
shift
for p in "${@}"; do
if [ -e "${p}" ]; then
p="$(winepath -w -- "${p}")"
fi
push EXEC "${p}"
done
exec "${EXEC[@]}"
заменить последнюю строку из $ CMD на просто
вино '$ EXEC' $ ARGS
Вы заметите, что ошибка «/home/chris/.wine/drive_c/Program», а не «/home/chris/.wine/drive_c/Program»
Одинарные кавычки неправильно интерполируются, а строка разделяется пробелами.
Вы можете попробовать поставить пробелы перед \ как:
/home/chris/.wine/drive_c/Program Files/Microsoft\ Research/Z3-1.3.6/bin/z3.exe
Вы также можете сделать то же самое с вашей проблемой \ t - замените ее на \\ t.