Como posso expandir uma variável entre aspas para nada se estiver vazia?

21

Digamos que eu tenha um script fazendo:

some-command "$var1" "$var2" ...

E, no caso de var1estar vazio, prefiro que seja substituído por nada em vez da string vazia, para que o comando executado seja:

some-command "$var2" ...

e não:

some-command '' "$var2" ...

Existe uma maneira mais simples do que testar a variável e incluí-la condicionalmente?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

Existe uma substituição de parâmetro que pode se expandir para nada no bash, zsh ou semelhante? Eu ainda posso querer usar globbing no restante dos argumentos, então desabilitar isso e não citar a variável não é uma opção.

muru
fonte
Eu sabia que tinha visto e provavelmente já havia usado isso antes, mas foi difícil pesquisar. Agora que Michael mostrou a sintaxe, lembrei-me onde eu tinha visto pela primeira vez rapidamente o suficiente: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
Muru
Se você sabe que é algum tipo de substituição de parâmetro, por que não procurou na seção de expansão de parâmetros da manpágina? (-;
Philippos
11
@ Philippos Eu não sabia o que era na época, só que eu já tinha visto ou usado antes. Conhecidos conhecidos e desconhecidos. :(
muru
11
pontos extras de cookie por mencionar o uso de uma matriz para conter os argumentos na própria pergunta.
ilkkachu

Respostas:

29

Os reservatórios compatíveis com Posix e Bash têm ${parameter:+word} :

Se o parâmetro for desabilitado ou nulo, nulo será substituído; caso contrário, a expansão da palavra (ou uma sequência vazia, se a palavra for omitida) deve ser substituída.

Então você pode fazer:

${var1:+"$var1"}

e foram var1verificados e "$var1"usados ​​se estiverem definidos e não vazios (com as regras comuns de aspas duplas). Caso contrário, ele se expande para nada. Observe que apenas a parte interna é citada aqui, não a coisa toda.

O mesmo também funciona no zsh. Você precisa repetir a variável, para que não seja o ideal, mas funciona exatamente como você queria.

Se você deseja que uma variável definida como vazia seja expandida para um argumento vazio, use em ${var1+"$var1"}vez disso.

Michael Homer
fonte
11
Bom o suficiente para mim. Portanto, nisso tudo não é citado, apenas a wordparte.
muru
11
Como a pergunta foi feita para "bash, zsh ou algo parecido" (e para o arquivo de perguntas e respostas), editei para refletir que esse é um recurso posix. Mesmo se você adicionar uma marca / bash à pergunta após o meu comentário. (-;
Philippos
2
Tomei a liberdade de editar a diferença entre :+e +.
ilkkachu
Muito obrigado por uma solução compatível com POSIX! Eu tento usar sh/ dashpara scripts em potencial de ponto de estrangulamento, então sempre aprecio quando alguém mostra como uma coisa é possível sem recorrer ao Bash.
JamesTheAwesomeDude
Eu estava procurando pelo efeito oposto (expanda para outra coisa se ela começar como nula). Acontece que essa lógica pode ser invertida alterando o + para a - como em: $ {empty_var: -replacement}
Alex Jansen
5

É o que zshfaz por padrão quando você omite as aspas:

some-command $var1 $var2

Na verdade, a única razão pela qual você ainda precisa de aspas no zsh em torno da expansão de parâmetros é evitar esse comportamento (a remoção vazia), pois zshnão tem outros problemas que afetam outras shells quando você não cita expansões de parâmetros (a divisão implícita + glob) .

Você pode fazer o mesmo com outros shells do tipo POSIX se desativar o split e glob:

(IFS=; set -o noglob; some-command $var1 $var2)

Agora, eu argumentaria que, se sua variável puder ter um valor 0 ou 1, deve ser uma matriz e não uma variável escalar e use:

some-command "${var1[@]}" "${var2[@]}"

E use var1=(value)quando var1deve conter um valor, var1=('')quando deve conter um valor vazio e var1=()quando não deve conter nenhum valor.

Stéphane Chazelas
fonte
0

Eu me deparei com isso usando o rsync em um script bash que iniciava o comando com ou sem uma -nalternância de execuções a seco. Acontece que o rsync e vários comandos gnu tomam ''como primeiro argumento válido e agem de maneira diferente do que se não estivesse lá.

Isso levou algum tempo para depurar, pois os parâmetros nulos são quase completamente invisíveis.

Alguém na lista rsync me mostrou uma maneira de evitar esse problema, além de simplificar bastante minha codificação. Se bem entendi, essa é uma variação da última sugestão de @ Stéphane Chazelas.

Crie seus argumentos de comando em várias variáveis ​​separadas. Eles podem ser configurados em qualquer ordem ou lógica adequada ao problema.

Então, no final, use as variáveis ​​para construir uma matriz com tudo em seu devido lugar e use-a como argumentos para o comando real.

Dessa forma, o comando é emitido apenas em um local do código, em vez de ser repetido para cada variação dos argumentos.

Qualquer variável que está vazia simplesmente desaparece usando esse método.

Eu sei que usar eval é altamente desaprovado. Não me lembro de todos os detalhes, mas parecia que precisava para que as coisas funcionassem dessa maneira - algo relacionado ao manuseio de parâmetros com espaço em branco incorporado.

Exemplo:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...
Joe
fonte
Essa é uma maneira pior de fazer o que descrevi na pergunta (construindo uma série de argumentos condicionalmente). Ao deixar as variáveis ​​sem aspas, quem sabe a que problemas você está se deixando aberto.
Muru
@uru Você está certo, mas eu ainda preciso de algo assim. Não vejo como corrigi-lo usando as técnicas das outras respostas. Seria ótimo ver um fragmento de código feito da maneira correta que une tudo. Vou jogar com a última opção de Stephane mais tarde, pois isso pode fazer o que eu quero.
Joe
Acabei de voltar para isso. Obrigado pelo exemplo. Faz sentido. Eu vou trabalhar com isso.
918 Joe
Eu tenho um caso de uso semelhante aqui, mas você poderia fazer algo assim: if ["$ dry" == "true"]; então seco = "- n"; fi ... então, se a variável dry for verdadeira, você a fará usar -n e, em seguida, criará o comando rsync como de costume e terá o seguinte: rsync $ {dry1: + "$ dry1"} ... se for nulo nada acontecerá, caso contrário, ele se tornará -n sob seu comando
Freedo