A regra geral no script de shell é que as variáveis sempre devem ser citadas, a menos que haja uma razão convincente para não fazê-lo. Para obter mais detalhes do que você provavelmente deseja saber, dê uma olhada nesta grande seção de perguntas e respostas: Implicações de segurança de esquecer de citar uma variável nos shells bash / POSIX .
Considere, no entanto, uma função como a seguinte:
run_this(){
$@
}
Deve $@
ser citado lá ou não? Brinquei um pouco e não consegui encontrar nenhum caso em que a falta de aspas causasse um problema. Por outro lado, o uso de aspas faz com que seja interrompido ao passar um comando contendo espaços como uma variável entre aspas:
#!/usr/bin/sh
set -x
run_this(){
$@
}
run_that(){
"$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
A execução do script acima retorna:
$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users 0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found
Eu posso contornar isso se usar em run_that $comm
vez de run_that "$comm"
, mas como a run_this
função (sem aspas) funciona com ambos, parece a aposta mais segura.
Então, no caso específico de usar $@
em uma função cujo trabalho é executar $@
como um comando, deve $@
ser citado? Explique por que não deve / não deve ser citado e dê um exemplo de dados que podem quebrá-lo.
fonte
run_that
O comportamento de definitivamente é o que eu esperaria (e se houver um espaço no caminho para o comando?). Se você quisesse o outro comportamento, certamente o citaria no local da chamada onde sabe quais são os dados? Eu esperaria chamar essa função comorun_that ls -l
, que funciona da mesma maneira em qualquer versão. Existe um caso que fez você esperar diferente?${mycmd[@]}
.Respostas:
O problema está em como o comando é passado para a função:
"$@"
deve ser usado no caso geral em que suarun_this
função é prefixada para um comando normalmente escrito.run_this
leva a citar o inferno:Não tenho certeza de como devo passar um nome de arquivo com espaços para
run_this
.fonte
run_this
.$@
aspas acidentalmente. Eu deveria ter deixado um exemplo. : Drun_this
. Esse é basicamente o mesmo problema com o preenchimento de comandos complexos em cadeias de caracteres, conforme discutido na Bash FAQ 050 .Também é:
Ou:
ou:
Mas:
Não faz muito sentido.
Se você deseja executar o
ls -l
comando (não ols
comando comls
e-l
como argumentos), faça:Mas se (mais provável), é o
ls
comando comls
e-l
como argumentos, você deve executar:Agora, se é mais do que um simples comando que você deseja executar, se você deseja fazer atribuições de variáveis, redirecionamentos, pipes ..., apenas
interpret_this_shell_code
fará:embora você possa sempre fazer:
fonte
Olhando para ele da perspectiva bash / ksh / zsh,
$*
e$@
são um caso especial de expansão geral da matriz. As expansões de matriz não são como expansões variáveis normais:Com as
$*
/${a[*]}
expansions, você junta a matriz com o primeiro valor deIFS
- que é o espaço por padrão - em uma sequência gigante. Se você não citar, ele será dividido como uma string normal.Com as
$@
/${a[@]}
expansions, o comportamento depende se a expansão$@
/${a[@]}
é citada ou não:"$@"
ou"${a[@]}"
), você obtém o equivalente"$1" "$2" "$3" #...
ou"${a[1]}" "${a[2]}" "${a[3]}" # ...
$@
ou${a[@]}
), você obtém o equivalente$1 $2 $3 #...
ou${a[1]} ${a[2]} ${a[3]} # ...
Para comandos de quebra automática, você definitivamente deseja as expansões entre aspas (1.).
Mais informações boas sobre matrizes do bash (e do tipo bash): https://lukeshu.com/blog/bash-arrays.html
fonte
Desde quando você não aspas duplas
$@
, você deixou todos os problemas no link que você deu à sua função.Como você pode executar um comando chamado
*
? Você não pode fazer isso comrun_this
:E você vê, mesmo quando ocorreu um erro,
run_that
deu a você uma mensagem mais significativa.A única maneira de expandir
$@
para palavras individuais é usar aspas duplas. Se você deseja executá-lo como um comando, deve passar o comando e seus parâmetros como palavras separadas. É o que você fez no lado do chamador, não dentro da sua função.é uma escolha melhor. Ou se o seu shell suportar matrizes:
Mesmo quando o shell não suporta array, você ainda pode brincar com ele usando
"$@"
.fonte
A execução de variáveis em
bash
é uma técnica propensa a falhas. É simplesmente impossível escrever umarun_this
função que lide corretamente com todos os casos de borda, como:ls | grep filename
)ls > /dev/null
)if
while
etc.Se tudo o que você quer fazer é evitar a repetição de código, é melhor usar funções. Por exemplo, em vez de:
Você deveria escrever
Se os comandos estiverem disponíveis apenas em tempo de execução, você deve usar o
eval
que foi projetado especificamente para lidar com todas as peculiaridades que causamrun_this
falhas:Observe que isso
eval
é conhecido por problemas de segurança, mas se você passar variáveis de fontes não confiáveis pararun_this
, também enfrentará a execução arbitrária de códigos.fonte
A escolha é sua. Se você não citar
$@
nenhum dos seus valores, sofrerá expansão e interpretação adicionais. Se você citar todos os argumentos passados, a função será reproduzida literalmente em sua expansão. Você nunca poderá manipular de forma confiável tokens de sintaxe de shell como&>|
e etc, de qualquer maneira, sem analisar os argumentos de qualquer maneira - e assim você terá as opções mais razoáveis de entregar sua função em um dos seguintes:"$@"
....ou...
$@
.Nenhuma das maneiras está errada se for intencional e se os efeitos do que você escolher forem bem compreendidos. Ambas as formas têm vantagens uma sobre a outra, embora as vantagens da segunda raramente sejam particularmente úteis. Ainda...
... não é inútil , apenas raramente é de grande utilidade . E em um
bash
shell, porquebash
, por padrão, não adere uma definição de variável ao seu ambiente, mesmo quando a definição é anexada à linha de comando de um built-in especial ou a uma função, o valor global de$IFS
não é afetado e sua declaração é local. apenas para arun_this()
chamada.Similarmente:
... o globbing também é configurável. As cotações servem a um propósito - não são à toa. Sem eles, a expansão do shell passa por uma interpretação extra - interpretação configurável . Ela costumava ser - com algumas muito antigas conchas - que
$IFS
foi globalmente aplicadas a todos os de entrada, e não apenas expansões. De fato, as referidas conchas se comportaram da mesmarun_this()
maneira que quebraram todas as palavras de entrada no valor de$IFS
. E assim, se o que você está procurando é que o comportamento shell muito antiga, então você deve usarrun_this()
.Não estou procurando por isso e estou bastante pressionado no momento para encontrar um exemplo útil para isso. Geralmente, prefiro que os comandos que meu shell execute sejam aqueles que digito nele. E assim, dada a escolha, eu quase sempre
run_that()
. Exceto aquilo...Qualquer coisa pode ser citada. Os comandos serão executados entre aspas. Funciona porque, no momento em que o comando é realmente executado, todas as palavras de entrada já foram removidas entre aspas - que é o último estágio do processo de interpretação de entrada do shell. Portanto, a diferença entre
'ls'
els
só pode importar enquanto o shell estiver interpretando - e é por isso que a citaçãols
garante que qualquer apelido nomeadols
não seja substituído pela minhals
palavra de comando citada . Fora isso, as únicas coisas que as aspas afetam são a delimitação de palavras (que é como e por que funciona a citação de espaço em branco de variável / entrada) e a interpretação de metacaracteres e palavras reservadas.Então:
Você nunca será capaz de fazer isso com um
run_this()
ou outrorun_that()
.Mas nomes de funções, ou
$PATH
comandos, ou builtins serão executados entre aspas ou sem aspas, e é exatamente assimrun_this()
e comorun_that()
funciona em primeiro lugar. Você não poderá fazer nada de útil com$<>|&(){}
nenhum deles. Curtoeval
, é.Mas sem ele, você está limitado aos limites de um comando simples em virtude das aspas que você usa (mesmo quando não o faz, porque
$@
age como uma cotação no início do processo quando o comando é analisado por metacaracteres) . A mesma restrição se aplica às atribuições e redirecionamentos da linha de comando, limitados à linha de comando da função. Mas isso não é grande coisa:Eu poderia ter tão facilmente
<
redirecionado a entrada ou>
saída lá como abri o tubo.De qualquer forma, de uma maneira geral, não há um caminho certo ou errado aqui - cada um tem seus usos. Você deve escrever como pretende usá-lo e saber o que pretende fazer. Citações omitindo pode ter um propósito - caso contrário, não haveria ser citações em tudo - mas se você omiti-los por razões não relevantes para a sua finalidade, você está apenas escrevendo código ruim. Faça o que você quer dizer; Eu tento de qualquer maneira.
fonte