Eu li sobre isso que devo citar variáveis no bash, por exemplo, "$ foo" em vez de $ foo. No entanto, ao escrever um script, encontrei um caso em que funciona sem aspas, mas não com elas:
wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line
relative_path="$3" # /XXX received from command line
Este funciona:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Este não (observe as aspas duplas entre $ wget_options):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
Qual é a razão para isto?
A primeira linha é a boa versão; ou devo suspeitar que há um erro oculto em algum lugar que causa esse comportamento?
Em geral, onde encontro boa documentação para entender como o bash e suas citações funcionam? Ao escrever este script, sinto que comecei a trabalhar com base em tentativa e erro, em vez de entender as regras.
wget
não sabe o que--mirror --no-host-directories
significa (como um argumento), mas lida com isso quando é dividido em dois argumentos. Muito poucos programas tratam espaços e aspas especialmente quando estão dentro do vetor de argumento. O problema é quebash
, e outras conchas, são destinadas a ser>bash
, então você pode imaginar que$a
é exatamente equivalente a escrever diretamente seu conteúdo. Agora a questão é evidente:a="-a -b"; cmd "$a"
expande paracmd "-a -b"
, mascmd
provavelmente não sabe o que isso significa.cmd $a
expande-se paracmd -a -b
, o que provavelmente faz o trabalho.Respostas:
Basicamente, você deve colocar aspas variáveis nas expansões para protegê-las da divisão de palavras (e geração de nome de arquivo). No entanto, no seu exemplo,
A divisão de palavras é exatamente o que você deseja .
Com
"$wget_options"
(citado),wget
não sabe o que fazer com o argumento único--mirror --no-host-directories
e reclamaPara
wget
ver as duas opções--mirror
e--no-host-directories
como separadas, a divisão de palavras deve ocorrer.Existem maneiras mais robustas de fazer isso. Se você estiver usando
bash
ou qualquer outro shell que use matrizes comobash
do, consulte a resposta de glenn jackman . A resposta de Gilles descreve adicionalmente uma solução alternativa para cascas mais simples, como o padrão/bin/sh
. Ambos armazenam essencialmente cada opção como um elemento separado em uma matriz.Pergunta relacionada com boas respostas: Por que meu script de shell engasga com espaços em branco ou outros caracteres especiais?
A citação dupla de expansões variáveis é uma boa regra geral. Faça isso . Então esteja ciente dos poucos casos em que você não deve fazer isso. Eles se apresentarão a você por meio de mensagens de diagnóstico, como a mensagem de erro acima.
Existem também alguns casos em que você não precisa citar expansões variáveis. Mas é mais fácil continuar usando aspas duplas de qualquer maneira, pois isso não faz muita diferença. Um desses casos é
Outro é
fonte
$IFS
contém o valor correto. Aqui você precisa dividir o espaço e o texto passa a não conter nenhuma guia ou nova linha, portanto o valor padrão de$IFS
faria, mas se esse código for usado em uma função que pode ser chamada em um contexto em que$IFS
poderia ter sido modificado , que você gostaria de definir$IFS
de antemão (e, possivelmente, restaurá-lo mais tarde ou usar um escopo local para ele se o resto do código assume uma não modificada$IFS
)A maneira mais robusta de codificar é usar uma matriz:
fonte
cp
ersync
farão coisas inesperadas se o seu comando se expandir para algo comorsync '' rest of parameters
. Isso é ótimo para criar uma peça de comando por peça condicionalmente e depois executá-la uma vez em um só lugar.Você está tentando armazenar uma lista de strings em uma variável de string. Não serve. Não importa como você acessa a variável, algo está quebrado.
wget_options='--mirror --no-host-directories'
define a variávelwget_options
como uma sequência que contém um espaço. Neste ponto, não há como saber se o espaço deve fazer parte de uma opção ou um separador entre as opções.Quando você acessa a variável com uma substituição entre aspas
wget "$wget_options"
, o valor da variável é usado como uma sequência. Isso significa que ele é passado como um único parâmetro parawget
, portanto, é uma única opção. Isso ocorre no seu caso, porque você pretendia que isso significasse várias opções.Quando você usa uma substituição não cotada
wget $wget_options
, o valor da variável de sequência passa por um processo de expansão apelidado de "split + glob":$IFS
variável). Isso resulta em uma lista intermediária de seqüências de caracteres.Isso funciona no seu exemplo, porque o processo de divisão transforma o espaço em um separador, mas geralmente não funciona, pois uma opção pode conter espaços e caracteres curinga.
No ksh, bash, yash e zsh, você pode usar uma variável de matriz. Uma matriz na terminologia do shell é uma lista de cadeias, portanto, não há perda de informações. Para criar uma variável de matriz, coloque parênteses em torno dos elementos da matriz ao atribuir o valor à variável. Para acessar todos os elementos da matriz, use - isto é uma generalização de , que forma uma lista dos elementos da matriz. Observe que você também precisa de aspas duplas aqui, caso contrário, cada elemento passa por divisão + glob.
"${VARIABLE[@]}"
"$@"
No sh simples, não há variáveis de matriz. Se você não se importa de perder os argumentos posicionais, pode usá-los para armazenar uma lista de strings.
Para obter mais informações, consulte Por que meu script de shell é bloqueado em espaço em branco ou em outros caracteres especiais?
fonte
(set -- ...; exec wget "$@" ...)
.