Na sequência de: comportamento inesperado na substituição de comandos do shell
Eu tenho um comando que pode ter uma lista enorme de argumentos, alguns dos quais podem legitimamente conter espaços (e provavelmente outras coisas)
Escrevi um script que pode gerar esses argumentos para mim, com aspas, mas devo copiar e colar a saída, por exemplo
./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>
Eu tentei simplificar isso simplesmente fazendo
./othercommand $(./somecommand)
e encontrou o comportamento inesperado mencionado na pergunta acima. A questão é: a substituição de comandos pode ser usada com confiabilidade para gerar os argumentos, othercommand
dado que alguns argumentos exigem citação e isso não pode ser alterado?
shell
arguments
command-substitution
user1207217
fonte
fonte
eval
possa ser usado, mas geralmente não é recomendado.xargs
é algo a considerar tambémsomecommand
sofresse uma análise regular do shell:
) ... assumindo que o caractere não estará na saída com segurança .Respostas:
Se a saída for citada corretamente para o shell e você confiar na saída , poderá executá
eval
-lo.Supondo que você tenha um shell que suporte matrizes, seria melhor usar um para armazenar os argumentos obtidos.
Se
./gen_args.sh
produzir saída como'foo bar' '*' asdf
, então poderíamos executareval "args=( $(./gen_args.sh) )"
para preencher uma matriz chamadaargs
com os resultados. Isso seria os três elementosfoo bar
,*
,asdf
.Podemos usar
"${args[@]}"
como de costume para expandir os elementos da matriz individualmente:(Observe que as aspas. Se
"${array[@]}"
expandem para todos os elementos como argumentos distintos, sem modificação. Sem aspas, os elementos da matriz estão sujeitos à divisão de palavras. Veja, por exemplo, a página Matrizes no BashGuide .)No entanto ,
eval
é possível executar com êxito quaisquer substituições de shell, portanto$HOME
, a saída seria expandida para o diretório inicial e uma substituição de comando realmente executaria um comando no shell em execuçãoeval
. Uma saída de"$(date >&2)"
criaria um único elemento vazio da matriz e imprimiria a data atual no stdout. Isso é preocupante segen_args.sh
obtém os dados de alguma fonte não confiável, como outro host na rede, nomes de arquivos criados por outros usuários. A saída pode incluir comandos arbitrários. (Seget_args.sh
ele era malicioso, não precisaria produzir nada, apenas poderia executar os comandos maliciosos diretamente.)Uma alternativa à citação de shell, que é difícil de analisar sem avaliação, seria usar algum outro caractere como separador na saída do seu script. Você precisaria escolher um que não seja necessário nos argumentos reais.
Vamos escolher
#
e ter a saída do scriptfoo bar#*#asdf
. Agora, podemos usar a expansão de comando sem aspas para dividir a saída do comando nos argumentos.Você precisará
IFS
voltar mais tarde se depender da divisão de palavras em algum lugar do script (unset IFS
deve funcionar para torná-lo o padrão) e também usarset +f
se quiser usar globbing mais tarde.Se você não estiver usando o Bash ou algum outro shell que possua matrizes, use os parâmetros posicionais para isso. Substitua
args=( $(...) )
porset -- $(./gen_args.sh)
e use em"$@"
vez"${args[@]}"
disso. (Também aqui você precisa de aspas"$@"
, caso contrário, os parâmetros posicionais estão sujeitos à divisão de palavras.)fonte
${args[@]}
- isso não funcionou para mim de outra forma?"${array[@]}"
como com"$@"
. Ambos precisam ser citados, ou a divisão de palavras quebra os elementos da matriz em partes.O problema é que, uma vez que o
somecommand
script gera as opções paraothercommand
, as opções são realmente apenas texto e à mercê da análise padrão do shell (afetadas pelo que quer$IFS
que seja e pelas opções do shell em vigor, o que você geralmente não faria) estar no controle).Em vez de usar
somecommand
para produzir as opções, seria mais fácil, mais seguro e mais robusto usá-lo para chamarothercommand
. Osomecommand
script seria então um script de invólucro, emothercommand
vez de algum tipo de script auxiliar que você precisaria lembrar de chamar de uma maneira especial como parte da linha de comando dootherscript
. Os scripts de wrapper são uma maneira muito comum de fornecer uma ferramenta que apenas chama outra ferramenta semelhante com outro conjunto de opções (basta verificar comfile
quais comandos/usr/bin
são realmente os wrappers de script de shell).Em
bash
,ksh
ouzsh
, você poderia facilmente um script de wrapper que usa uma matriz para conter as opções individuais da seguinteothercommand
maneira:Em seguida, chame
othercommand
(ainda dentro do script do wrapper):A expansão de
"${options[@]}"
garantiria que cada elemento daoptions
matriz fosse citado individualmente e apresentadoothercommand
como argumentos separados.O usuário do wrapper estaria alheio ao fato de estar realmente chamando
othercommand
, algo que não seria verdadeiro se o script apenas gerasse as opções da linha de comando paraothercommand
saída.Em
/bin/sh
, use$@
para manter as opções:(
set
É o comando usado para definir os parâmetros de posição$1
,$2
,$3
etc. Estes são o que compõe a matriz$@
em um shell padrão POSIX. A primeira--
é para sinalizarset
que não há opções dadas, apenas argumentos. O--
é realmente necessário apenas se o o primeiro valor passa a ser algo que começa com-
).Observe que são as aspas duplas ao redor
$@
e${options[@]}
isso garante que os elementos não sejam divididos individualmente por palavra (e o nome do arquivo seja globbed).fonte
set --
?Se a
somecommand
saída estiver em uma sintaxe confiável e confiável, você poderá usareval
:Mas você deve ter certeza de que a saída possui aspas válidas e tal; caso contrário, você também poderá executar comandos fora do script:
Observe que
echo rm bar baz test.sh
não foi passado para o script (por causa do;
) e foi executado como um comando separado. Eu adicionei ao|
redor$var
para destacar isso.Geralmente, a menos que você possa confiar totalmente na saída de
somecommand
, não é possível usá-la com segurança para criar uma cadeia de comandos.fonte