Citar argumentos de línea de comandos en scripts de shell
Pregunta
El siguiente script de shell toma una lista de argumentos, convierte las rutas de Unix en rutas de WINE/Windows e invoca el ejecutable proporcionado en 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
Sin embargo, hay algún problema con la cita de los argumentos de la línea de comandos.
$ 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'
Tenga en cuenta que:
- La ruta al ejecutable se corta en el primer espacio, aunque esté entre comillas simples.
- El literal " " en la última ruta se transforma en un carácter de tabulación.
Obviamente, el shell no está analizando las citas de la manera que yo pretendía.¿Cómo puedo evitar estos errores?
EDITAR:El " " se está expandiendo a través de dos niveles de direccionamiento indirecto:primero, "$p"
(y/o "$ARGS"
) se está ampliando a Z:\tmp\smtlib3cee8b.smt
;entonces, \t
se está expandiendo al carácter de tabulación.Esto es (aparentemente) equivalente a
Y='y\ty'
Z="z${Y}z"
echo $Z
cuyos rendimientos
zy\tyz
y no
zy yz
ACTUALIZAR: eval "$CMD"
Hace el truco.El "\t
"El problema parece ser culpa de Echo:"Si el primer operando es -n, o si alguno de los operandos contiene un carácter de barra de inverso (''), los resultados están definidos por la implementación". (especificación POSIX de echo
)
Solución
Si quieres tener la asignación a CMD, debes usar
eval $CMD
en lugar de solo $CMD
en la última línea de su guión.Esto debería resolver tu problema con los espacios en las rutas, no sé qué hacer con el problema " ".
Otros consejos
- Las matrices de bash no son portátiles, pero son la única forma sensata de manejar listas de argumentos en Shell.
- El número de argumentos está en ${#}
- Sucederán cosas malas con su script si hay nombres de archivos que comienzan con un guión en el directorio actual
- Si la última línea de su script simplemente ejecuta un programa y no hay trampas al salir, debe ejecutarlo.
Con eso en 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[@]}"
reemplace la última línea de $CMD por solo
vino '$EXEC' $ARGS
Notarás que el error es ''/home/chris/.wine/drive_c/Program' y no '/home/chris/.wine/drive_c/Program'
Las comillas simples no se interpolan correctamente y la cadena se divide por espacios.
Puedes intentar anteponer los espacios con \ así:
/home/chris/.wine/drive_c/Program Files/Microsoft\ Research/Z3-1.3.6/bin/z3.exe
También puedes hacer lo mismo con tu problema : reemplázalo con \ .