Citando dentro de $ (substituição de comando) no Bash

126

No meu ambiente Bash, uso variáveis ​​contendo espaços e essas variáveis ​​na substituição de comandos. Infelizmente não consigo encontrar a resposta no SE.

Qual é a maneira correta de citar minhas variáveis? E como devo fazer isso se estes estiverem aninhados?

DIRNAME=$(dirname "$FILE")

ou cito fora da substituição?

DIRNAME="$(dirname $FILE)"

ou ambos?

DIRNAME="$(dirname "$FILE")"

ou eu uso back-ticks?

DIRNAME=`dirname "$FILE"`

Qual é a maneira certa de fazer isso? E como posso verificar facilmente se as aspas estão definidas corretamente?

PrimoCocaína
fonte
1
Essa é uma boa pergunta, mas, considerando todos os problemas com espaços em branco incorporados, por que você dificulta sua vida usando-os de propósito?
Joe
3
@ Joe, com espaços em branco incorporados você quer dizer espaço nos nomes de arquivo? Pessoalmente, não os uso com frequência, mas estou trabalhando com diretórios e arquivos de outras pessoas, dos quais não tenho certeza se eles contêm espaços. Além disso, acho que é melhor acertar de uma vez, para não ter que me preocupar no futuro.
CousinCocaine 8/03
4
Sim. O que vamos fazer com essas "outras pessoas"? <G>
Joe

Respostas:

188

Em ordem do pior para o melhor:

  • DIRNAME="$(dirname $FILE)" não fará o que você deseja se $FILEcontiver caracteres em branco ou em branco \[?*.
  • DIRNAME=`dirname "$FILE"`é tecnicamente correto, mas os backticks não são recomendados para expansão de comandos devido à complexidade extra ao aninhar-los.
  • DIRNAME=$(dirname "$FILE")está correto, mas apenas porque esta é uma tarefa . Se você usar a substituição de comando em qualquer outro contexto, como export DIRNAME=$(dirname "$FILE")ou du $(dirname "$FILE"), a falta de aspas causará problemas se o resultado da expansão contiver espaços em branco ou caracteres brilhantes.
  • DIRNAME="$(dirname "$FILE")"é a maneira recomendada. Você pode substituir DIRNAME=por um comando e um espaço sem alterar mais nada e dirnamerecebe a sequência correta.

Para melhorar ainda mais:

  • DIRNAME="$(dirname -- "$FILE")"funciona se $FILEcomeça com um traço.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"funciona mesmo que $FILEtermine com uma nova linha, pois $()retira novas linhas no final da saída e dirnamegera uma nova linha após o resultado. Sheesh dirname, por que você tem que ser diferente?

Você pode aninhar expansões de comando quantas vezes quiser. Com $()você sempre criar um novo contexto citando, para que possa fazer coisas como esta:

foo "$(bar "$(baz "$(ban "bla")")")"

Você não quer tentar isso com backticks.

l0b0
fonte
3
Resposta clara à minha pergunta. Quando aninho essas variáveis, posso continuar citando como faço agora?
CousinCocaine
3
Existe uma referência / recurso detalhando o comportamento das aspas dentro de uma subestação de comando entre aspas?
AmadeusDrZaius
12
@AmadeusDrZaius "Com $()você sempre cria um novo contexto de cotação", assim é como fora das aspas externas. Não há mais nada, até onde eu sei.
L0b0 4/03
4
@ l0b0 Obrigado, sim, achei sua explicação muito clara. Eu só estava me perguntando se estava em um manual em algum lugar também. Eu o encontrei (embora não oficialmente) em Wooledge . Acho que se você ler cuidadosamente sobre a ordem de substituição , poderá derivar esse fato como resultado.
AmadeusDrZaius
1
Portanto, aspas aninhadas são aceitáveis, mas elas nos descartam porque a maioria dos esquemas de cores de sintaxe não detecta a circunstância especial. Arrumado.
Luke Davis
19

Você sempre pode mostrar os efeitos da cotação variável com printf.

Divisão de palavras feita em var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 citado, portanto, nenhuma divisão de palavras:

$ printf '[%s]\n' "$var1"
[hello     world]

Palavra dividida por var1dentro $(), equivalente a echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

Não há divisão de palavras var1, não há problema em não citar o $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Palavra dividida var1novamente:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Citando as duas, a maneira mais fácil de ter certeza.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Problema de globbing

Não citar uma variável também pode levar à expansão global de seu conteúdo:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Observe que isso acontece depois que a variável é expandida apenas. Não é necessário citar um globo durante a atribuição:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Use set -fpara desativar esse comportamento:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

E set +fpara reativá-lo:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
Graeme
fonte
8
As pessoas tendem a esquecer que a divisão de palavras não é o único problema; talvez você queira mudar seu exemplo var1='hello * world'para ilustrar também o problema de globbing.
Stéphane Chazelas
10

Além da resposta aceita:

Embora eu geralmente concorde com a resposta da @ l0b0 aqui, suspeito que a colocação de backticks vazios na lista "pior para o melhor" seja pelo menos parcialmente um resultado da suposição que $(...)está disponível em todos os lugares. Percebo que a pergunta especifica o Bash, mas há muitas vezes em que o Bash significa /bin/sh, o que nem sempre pode ser realmente o shell Bourne Again completo.

Em particular, o shell Bourne comum não saberá o que fazer $(...), portanto, scripts que afirmam ser compatíveis com ele (por exemplo, através de uma #!/bin/shlinha shebang) provavelmente se comportarão mal se forem realmente executados pelo "real" /bin/sh- isto é interesse especial quando, digamos, produzir scripts init ou empacotar pré e pós scripts, e conseguir um em um local surpreendente durante a instalação de um sistema básico.

Se algo disso parece algo que você planeja fazer com essa variável, o aninhamento é provavelmente menos uma preocupação do que ter o script realmente, previsivelmente, executado. Quando se trata de um caso bastante simples e a portabilidade é uma preocupação, mesmo que eu espere que o script geralmente seja executado em sistemas onde /bin/shestá o Bash, geralmente costumo usar backticks por esse motivo, com várias atribuições em vez de aninhar.

Dito tudo isso, a onipresença de shells que implementam $(...)(Bash, Dash, et al.) Nos deixa em um bom local para ficar com a sintaxe POSIX mais bonita, mais fácil de aninhar e mais recentemente preferida na maioria dos casos, por todas as razões que @ l0b0 menciona.

Além: isso também apareceu ocasionalmente no StackOverflow -

rsandwick3
fonte
//, excelente resposta. Também já tive problemas de compatibilidade com o / bin / sh no passado. Você tem algum conselho sobre como lidar com o problema dele usando métodos compatíveis com versões anteriores?
Nathan Basanese
na minha experiência: às vezes você pode precisar alterar os backticks em dólares americanos, e nem uma vez isso ajudou (foi necessário, para ser específico) a mudar os dólares americanos em dólares americanos. Eu só escrevo backticks no meu código javascript, e não no código do shell.
Steven Lu