Citando argumentos de linha de comando em scripts de shell
Pergunta
O script de shell a seguir pega uma lista de argumentos, transforma caminhos Unix em caminhos WINE/Windows e invoca o executável fornecido em 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
No entanto, há algo errado com a citação de argumentos de linha de comando.
$ 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'
Observe que:
- O caminho para o executável está sendo cortado no primeiro espaço, mesmo que esteja entre aspas simples.
- O literal " " no último caminho está sendo transformado em um caractere de tabulação.
Obviamente, as citações não estão sendo analisadas da maneira pretendida pelo shell.Como posso evitar esses erros?
EDITAR:O " " está sendo expandido através de dois níveis de indireção:primeiro, "$p"
(e/ou "$ARGS"
) está sendo expandido para Z:\tmp\smtlib3cee8b.smt
;então, \t
está sendo expandido para o caractere de tabulação.Isto é (aparentemente) equivalente a
Y='y\ty'
Z="z${Y}z"
echo $Z
que rende
zy\tyz
e não
zy yz
ATUALIZAR: eval "$CMD"
faz o truque.O "\t
"O problema parece ser culpa do echo:"Se o primeiro operando for -n, ou se algum dos operandos contiver um caractere de barragem (''), os resultados serão definidos pela implementação." (Especificação POSIX de echo
)
Solução
Se você quiser ter a atribuição ao CMD, você deve usar
eval $CMD
em vez de apenas $CMD
na última linha do seu script.Isso deve resolver seu problema com espaços nos caminhos, não sei o que fazer com o problema " ".
Outras dicas
- Os arrays do bash não são portáveis, mas a única maneira sensata de lidar com listas de argumentos no shell
- O número de argumentos está em ${#}
- Coisas ruins acontecerão com o seu script se houver nomes de arquivos começando com um travessão no diretório atual
- Se a última linha do seu script apenas executa um programa e não há armadilhas na saída, você deve executá-lo
Com aquilo em mente
#! /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[@]}"
substitua a última linha de $CMD por apenas
vinho '$EXEC' $ARGS
Você notará que o erro é ''/home/chris/.wine/drive_c/Program' e não '/home/chris/.wine/drive_c/Program'
As aspas simples não estão sendo interpoladas corretamente e a string está sendo dividida por espaços.
Você pode tentar preceder os espaços com \ assim:
/home/chris/.wine/drive_c/Program Files/Microsoft\ Research/Z3-1.3.6/bin/z3.exe
Você também pode fazer o mesmo com o seu problema - substituí-lo por \ .