La plupart de ces réponses concernent le cas spécifique dont vous parlez. Il y a une approche générale qu'un ami et moi avons développé qui permet arbitraire de citer dans le cas où vous avez besoin de citer bash commandes à travers plusieurs couches d'expansion de la coquille, par exemple, par le biais de ssh, su -c
, bash -c
, etc. Il y a un noyau vous primitif besoin, ici en bash natif:
quote_args() {
local sq="'"
local dq='"'
local space=""
local arg
for arg; do
echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
space=" "
done
}
Cela fait exactement ce qu'il dit: il cite chaque argument individuellement (après l'expansion bash, bien sûr):
$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'
Cela fait la chose évidente pour une couche d'expansion:
$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2
(Notez que les guillemets autour $(quote_args ...)
sont nécessaires pour transformer le résultat en un seul argument bash -c
.) Et il peut être utilisé plus généralement pour citer correctement à travers plusieurs couches d'expansion:
$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2
L'exemple ci-dessus:
- shell-quote chaque argument à l'intérieur
quote_args
individuellement, puis combine la sortie résultante en un seul argument avec les doubles guillemets intérieurs.
- shell-quotes
bash
, -c
et le résultat déjà cité une fois de l'étape 1, puis combine le résultat en un seul argument avec les doubles guillemets externes.
- envoie ce désordre comme argument à l'extérieur
bash -c
.
C'est l'idée en un mot. Vous pouvez faire des choses assez compliquées avec cela, mais vous devez faire attention à l'ordre d'évaluation et aux sous-chaînes qui sont citées. Par exemple, ce qui suit fait les mauvaises choses (pour une définition de «mauvais»):
$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure
Dans le premier exemple, bash se développe immédiatement quote_args cd /; pwd 1>&2
en deux commandes distinctes quote_args cd /
et pwd 1>&2
, par conséquent, le CWD est toujours /tmp
lorsque la pwd
commande est exécutée. Le deuxième exemple illustre un problème similaire pour la globalisation. En effet, le même problème de base se produit avec toutes les extensions bash. Le problème ici est qu'une substitution de commande n'est pas un appel de fonction: elle évalue littéralement un script bash et utilise sa sortie dans le cadre d'un autre script bash.
Si vous essayez d'échapper simplement aux opérateurs shell, vous échouerez car la chaîne résultante transmise à bash -c
n'est qu'une séquence de chaînes entre guillemets individuels qui ne sont alors pas interprétées comme des opérateurs, ce qui est facile à voir si vous faites écho à la chaîne qui ont été passés à bash:
$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'
Le problème ici est que vous surestimez. Ce dont vous avez besoin, c'est que les opérateurs ne soient pas entre guillemets en entrée du boîtier bash -c
, ce qui signifie qu'ils doivent être en dehors de la $(quote_args ...)
substitution de commande.
Par conséquent, ce que vous devez faire dans le sens le plus général est de citer chaque mot de la commande non destiné à être développé au moment de la substitution de commande séparément, et de n'appliquer aucune citation supplémentaire aux opérateurs du shell:
$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success
Une fois que vous avez fait cela, la chaîne entière est un jeu équitable pour continuer à citer des niveaux d'évaluation arbitraires:
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success
etc.
Ces exemples peuvent sembler surmené étant donné que des mots comme success
, sbin
et pwd
ne doivent pas nécessairement être cité shell, mais le point essentiel à retenir lors de l' écriture d' un script prenant entrée arbitraire est que vous voulez citer tout ce que vous n'êtes pas absolument sûr doesn » t ont besoin de citer, parce que vous ne savez jamais quand un utilisateur va jeter dans un Robert'; rm -rf /
.
Pour mieux comprendre ce qui se passe sous les couvertures, vous pouvez jouer avec deux petites fonctions d'aide:
debug_args() {
for (( I=1; $I <= $#; I++ )); do
echo -n "$I:<${!I}> " 1>&2
done
echo 1>&2
}
debug_args_and_run() {
debug_args "$@"
"$@"
}
qui énumérera chaque argument d'une commande avant de l'exécuter:
$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2