Por que as opções em uma variável entre aspas falham, mas funcionam quando não estão citadas?

15

Eu li sobre o que eu deveria citar variáveis no bash, por exemplo "$ foo" em vez de $ foo. No entanto, ao escrever um script, encontrei um caso em que ele 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 aroung $ wget_options):

wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
  • Qual é o motivo disso?

  • A primeira linha é a boa versão; ou devo suspeitar que existe um erro oculto em algum lugar que causa esse comportamento?

  • Em geral, onde eu encontro uma boa documentação para entender como o bash e suas citações funcionam? Durante a escrita deste script, sinto que comecei a trabalhar em uma base de tentativa e erro, em vez de entender as regras.

por z32a7ul 26.08.2017 / 14:40

3 respostas

27

Basicamente, você deve duplicar as expansões de variáveis para protegê-las da divisão de palavras (e geração de nome de arquivo). No entanto, no seu exemplo,

wget_options='--mirror --no-host-directories'
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
A

divisão de palavras é exatamente o que você quer .

Com "$wget_options" (cotado), wget não sabe o que fazer com o único argumento --mirror --no-host-directories e reclama

wget: unknown option -- mirror --no-host-directories

Para wget para 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 como bash do, consulte a resposta de glenn jackman . A resposta de Gilles também descreve uma solução alternativa para shells mais simples, como o padrão /bin/sh .

Pergunta relacionada com boas respostas: Por que meu script de shell sufoca em espaços em branco ou outros caracteres especiais?

Duplicar expansões variáveis é uma boa regra prática. Faça isso . Então esteja ciente dos poucos casos em que você não deveria 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 para citar expansões variáveis. Mas é mais fácil continuar usando aspas duplas, já que não faz muita diferença. Um desses casos é

variable=$other_variable

Outra é

case $variable in
    ...) ... ;;
esac
    
por 26.08.2017 / 14:49
27

A maneira mais robusta de codificar isso é usar uma matriz:

wget_options=(
    --mirror 
    --no-host-directories
    --directory_prefix="$1"
)
wget "${wget_options[@]}" "$2/$3"
    
por 26.08.2017 / 14:47
14

Você está tentando armazenar uma lista de strings em uma variável de string. Não cabe. Não importa como você acessa a variável, algo está quebrado.

wget_options='--mirror --no-host-directories' define a variável wget_options como uma string que contém um espaço. Neste ponto, não há como saber se o espaço deve ser parte de uma opção ou um separador entre as opções.

Quando você acessa a variável com uma substituição citada wget "$wget_options" , o valor da variável é usado como uma string. Isso significa que ele é passado como um único parâmetro para wget , por isso é uma única opção. Isso quebra no seu caso porque você pretendia significar várias opções.

Quando você usa uma substituição sem aspas wget $wget_options , o valor da variável string sofre um processo de expansão apelidado de “split + glob”:

  1. Pegue o valor da variável e divida-a em partes delimitadas por espaços em branco (supondo que você não tenha modificado a variável $IFS ). Isso resulta em uma lista intermediária de strings.
  2. Para cada elemento da lista intermediária, se for um padrão curinga que corresponda a um ou mais arquivos, substitua esse elemento pela lista de arquivos correspondentes.

Isso funciona no seu exemplo, porque o processo de divisão transforma o espaço em um separador, mas não funciona em geral, já que uma opção pode conter espaços e caracteres curinga.

Em ksh, bash, yash e zsh, você pode usar uma variável de matriz. Um array na terminologia do shell é uma lista de strings, portanto, não há perda de informações. Para criar uma variável de matriz, coloque parênteses ao redor dos elementos da matriz ao atribuir o valor à variável. Para acessar todos os elementos da matriz, use "${VARIABLE[@]}" - essa é uma generalização de "$@" , que forma uma lista dos elementos da matriz. Note que você também precisa das aspas duplas aqui, caso contrário, cada elemento sofre split + glob.

wget_options=(--mirror --no-host-directories --user-agent="I can haz spaces")
wget "${wget_options[@]}" …

Em sh simples, não há variáveis de matriz. Se você não se importar em perder os argumentos posicionais, poderá usá-los para armazenar uma lista de strings.

set -- --mirror --no-host-directories --user-agent="I can haz spaces"
wget "$@" …

Para mais informações, consulte Por que meu script de shell sufoca em espaços em branco ou outros caracteres especiais?

    
por 27.08.2017 / 02:55