$ ls -l /tmp/test/my\ dir/
total 0
Eu queria saber por que as seguintes maneiras de executar o comando acima falham ou são bem-sucedidas?
$ abc='ls -l "/tmp/test/my dir"'
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
$ bash -c $abc
'my dir'
$ bash -c "$abc"
total 0
$ eval $abc
total 0
$ eval "$abc"
total 0
Respostas:
Isso já foi discutido em várias questões no unix.SE, tentarei coletar todos os problemas que pudermos apresentar aqui. Referências no final.
Por que falha
A razão pela qual você enfrenta esses problemas é a divisão de palavras e o fato de as aspas expandidas das variáveis não agirem como aspas, mas são apenas caracteres comuns.
Os casos apresentados na pergunta:
Aqui,
$abc
é dividido els
obtém os dois argumentos"/tmp/test/my
edir"
(com as aspas na frente do primeiro e na parte de trás do segundo):Aqui, a expansão é citada, portanto, é mantida como uma única palavra. O shell tenta encontrar um programa chamado
ls -l "/tmp/test/my dir"
, incluindo espaços e aspas.E aqui, apenas a primeira palavra ou
$abc
é usada como argumento-c
, então o Bash é executadols
no diretório atual. As outras palavras são argumentos para bash, e são usados para preencher$0
,$1
etc.Com
bash -c "$abc"
, eeval "$abc"
, há uma etapa adicional de processamento de shell, que faz com que as cotações funcionem, mas também faz com que todas as expansões de shell sejam processadas novamente , portanto, existe o risco de executar acidentalmente uma expansão de comando a partir de dados fornecidos pelo usuário, a menos que você cuidado ao citar.Melhores maneiras de fazer isso
As duas melhores maneiras de armazenar um comando são: a) usar uma função; b) usar uma variável de matriz (ou os parâmetros posicionais).
Usando uma função:
Simplesmente declare uma função com o comando dentro e execute a função como se fosse um comando. As expansões nos comandos dentro da função são processadas apenas quando o comando é executado, não quando definido, e você não precisa citar os comandos individuais.
Usando uma matriz:
As matrizes permitem a criação de variáveis com várias palavras, onde as palavras individuais contêm espaço em branco. Aqui, as palavras individuais são armazenadas como elementos distintos da matriz e a
"${array[@]}"
expansão expande cada elemento como palavras shell separadas:A sintaxe é um pouco horrível, mas as matrizes também permitem que você construa a linha de comando peça por peça. Por exemplo:
ou mantenha partes da linha de comando constantes e use a matriz preenchendo apenas uma parte, opções ou nomes de arquivos:
A desvantagem dos arrays é que eles não são um recurso padrão; portanto, os shells POSIX comuns (como
dash
o padrão/bin/sh
no Debian / Ubuntu) não os suportam (mas veja abaixo). Bash, ksh e zsh, no entanto, portanto, é provável que seu sistema tenha algum shell que suporte matrizes.Usando
"$@"
Em shells sem suporte para matrizes nomeadas, ainda é possível usar os parâmetros posicionais (a pseudo-matriz
"$@"
) para conter os argumentos de um comando.A seguir, devem ser apresentados os bits de script portáteis que equivalem aos bits de código na seção anterior. A matriz é substituída por
"$@"
, a lista de parâmetros posicionais. A configuração"$@"
é feita comset
, e as aspas duplas"$@"
são importantes (isso faz com que os elementos da lista sejam citados individualmente).Primeiro, simplesmente armazene um comando com argumentos
"$@"
e execute-o:Configurando condicionalmente partes das opções da linha de comandos para um comando:
Somente usando
"$@"
para opções e operandos:(É claro,
"$@"
geralmente é preenchido com os argumentos do próprio script, portanto, você precisará salvá-los em algum lugar antes de redistribuir"$@"
.)Tenha cuidado com
eval
!Como
eval
introduz um nível adicional de processamento de cotação e expansão, você precisa ter cuidado com a entrada do usuário. Por exemplo, isso funciona desde que o usuário não digite aspas simples:Mas se eles fornecerem a entrada
'$(uname)'.txt
, seu script executará a substituição de comando com prazer.Uma versão com matrizes é imune a isso, já que as palavras são mantidas separadas o tempo todo, não há aspas ou outro processamento para o conteúdo de
filename
.Referências
fonte
cmd="ls -l $(printf "%q" "$filename")"
. não é bonito, mas se o usuário estiver decidido a usar umeval
, isso ajuda. É também muito útil para enviar o comando que as coisas semelhantes, comossh foohost "ls -l $(printf "%q" "$filename")"
, ou no espírito desta questão:ssh foohost "$cmd"
.$ alias abc='ls -l "/tmp/test/my dir"'
A maneira mais segura de executar um comando (não trivial) é
eval
. Em seguida, você pode escrever o comando como faria na linha de comando e ele é executado exatamente como se você tivesse acabado de inseri-lo. Mas você tem que citar tudo.Caso simples:
caso não tão simples:
fonte
O segundo sinal de cotação quebra o comando.
Quando eu corro:
Isso me deu um erro.
Mas quando eu corro
Não há nenhum erro
Não há como corrigir isso no momento (para mim), mas você pode evitar o erro não tendo espaço no nome do diretório.
Esta resposta disse que o comando eval pode ser usado para corrigir isso, mas não funciona para mim :(
fonte
Se isso não funcionar
''
, você deve usar``
:Atualize melhor maneira:
fonte
$( command... )
vez de backticks.Que tal um liner python3?
fonte