Citer des arguments de ligne de commande dans des scripts shell
Question
Le script shell suivant prend une liste d'arguments, transforme les chemins Unix en chemins WINE/Windows et appelle l'exécutable donné sous 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
Cependant, il y a quelque chose qui ne va pas dans la citation des arguments de ligne de commande.
$ 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'
Noter que:
- Le chemin d'accès à l'exécutable est coupé au premier espace, même s'il est entre guillemets simples.
- Le " " littéral dans le dernier chemin est transformé en caractère de tabulation.
De toute évidence, les citations ne sont pas analysées comme je le souhaitais par le shell.Comment puis-je éviter ces erreurs ?
MODIFIER:Le " " est étendu à travers deux niveaux d'indirection :d'abord, "$p"
(et/ou "$ARGS"
) est en cours d'extension dans Z:\tmp\smtlib3cee8b.smt
;alors, \t
est développé dans le caractère de tabulation.Ceci est (apparemment) équivalent à
Y='y\ty'
Z="z${Y}z"
echo $Z
ce qui donne
zy\tyz
et pas
zy yz
MISE À JOUR: eval "$CMD"
fait l'affaire.Le "\t
" Le problème semble être la faute d'Echo :"Si le premier opérande est -N, ou si l'un des opérandes contienne un caractère bombardement (''), les résultats sont définis par l'implémentation." (Spécification POSIX de echo
)
La solution
Si vous voulez avoir l'affectation à CMD, vous devriez utiliser
eval $CMD
au lieu de juste $CMD
dans la dernière ligne de votre script.Cela devrait résoudre votre problème avec les espaces dans les chemins, je ne sais pas quoi faire avec le problème " ".
Autres conseils
- les tableaux de bash ne sont pas portables mais constituent le seul moyen sensé de gérer les listes d'arguments dans le shell
- Le nombre d'arguments est en ${#}
- De mauvaises choses se produiront avec votre script s'il y a des noms de fichiers commençant par un tiret dans le répertoire courant
- Si la dernière ligne de votre script exécute simplement un programme et qu'il n'y a aucun piège à la sortie, vous devez l'exécuter
Dans cet esprit
#! /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[@]}"
remplacez la dernière ligne de $CMD par juste
vin '$EXEC' $ARGS
Vous remarquerez que l'erreur est ''/home/chris/.wine/drive_c/Program' et non '/home/chris/.wine/drive_c/Program'
Les guillemets simples ne sont pas interpolés correctement et la chaîne est divisée par des espaces.
Vous pouvez essayer de faire précéder les espaces de \ comme ceci :
/home/chris/.wine/drive_c/Program Files/Microsoft\ Research/Z3-1.3.6/bin/z3.exe
Vous pouvez également faire la même chose avec votre problème - remplacez-le par \ .