A maioria dessas respostas ocorre no caso específico sobre o qual você está perguntando. Há uma abordagem geral que um amigo e tenho desenvolvido que permite arbitrária citando em caso de necessidade de citação do bash comandos através de várias camadas de expansão shell, por exemplo, através de ssh, su -c
, bash -c
, etc. Há um núcleo primitivo você precisa, aqui no bash nativo:
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
}
Isso faz exatamente o que diz: aspas de cada argumento individualmente (após a expansão do bash, é claro):
$ 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'
Faz o óbvio para uma camada de expansão:
$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2
(Observe que as aspas duplas $(quote_args ...)
são necessárias para transformar o resultado em um único argumento para bash -c
.) E pode ser usado de maneira mais geral para citar corretamente através de várias camadas de expansão:
$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2
O exemplo acima:
- aspas shell cada argumento para o interior
quote_args
individualmente e, em seguida, combina a saída resultante em um único argumento com as aspas duplas internas.
- citações do shell
bash
,-c
e o resultado já uma vez citado a partir do passo 1, e em seguida, combina o resultado em uma única discussão com as aspas duplas exteriores.
- envia essa bagunça como argumento para o exterior
bash -c
.
Essa é a ideia em poucas palavras. Você pode fazer algumas coisas bem complicadas com isso, mas precisa ter cuidado com a ordem da avaliação e com quais substrings são citados. Por exemplo, o seguinte faz as coisas erradas (para alguma definição de "errado"):
$ (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
No primeiro exemplo, o bash se expande imediatamente quote_args cd /; pwd 1>&2
para dois comandos separados quote_args cd /
e pwd 1>&2
, portanto, o CWD permanece /tmp
quando o pwd
comando é executado. O segundo exemplo ilustra um problema semelhante para globbing. De fato, o mesmo problema básico ocorre com todas as expansões do bash. O problema aqui é que uma substituição de comando não é uma chamada de função: está literalmente avaliando um script bash e usando sua saída como parte de outro script bash.
Se você tentar simplesmente escapar dos operadores de shell, falhará porque a cadeia resultante transmitida bash -c
é apenas uma sequência de cadeias citadas individualmente que não são interpretadas como operadores, o que é fácil de ver se você faz eco à cadeia que seria foram passados para o 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'
O problema aqui é que você está citando demais. O que você precisa é que os operadores não sejam cotados como entrada para o anexo bash -c
, o que significa que eles precisam estar fora da $(quote_args ...)
substituição de comando.
Consequentemente, o que você precisa fazer no sentido mais geral é citar shell cada palavra do comando que não pretende ser expandida no momento da substituição do comando separadamente e não aplicar nenhuma citação extra aos operadores do 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
Depois de fazer isso, a sequência inteira é um jogo justo para citar ainda mais os níveis arbitrários de avaliação:
$ 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.
Esses exemplos podem parecer exagerados, considerando que palavras como success
, sbin
e pwd
não precisam ser citadas pelo shell, mas o ponto-chave a ser lembrado ao escrever um script com entrada arbitrária é que você deseja citar tudo o que não tem certeza absoluta de que não é ' Não é necessário citar, porque você nunca sabe quando um usuário lançará um Robert'; rm -rf /
.
Para entender melhor o que está acontecendo debaixo das cobertas, você pode brincar com duas pequenas funções auxiliares:
debug_args() {
for (( I=1; $I <= $#; I++ )); do
echo -n "$I:<${!I}> " 1>&2
done
echo 1>&2
}
debug_args_and_run() {
debug_args "$@"
"$@"
}
que enumerará cada argumento em um comando antes de executá-lo:
$ 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