Como escapar do caractere curinga / asterisco no bash?

136

Por exemplo:

me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR

e usando o \caractere de escape:

me$ FOO="BAR \* BAR"
me$ echo $FOO
BAR \* BAR

Obviamente estou fazendo algo estúpido.

Como obtenho a saída BAR * BAR?

andyuk
fonte

Respostas:

139

Citar quando a configuração $FOOnão é suficiente. Você também precisa citar a referência da variável:

me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR
finnw
fonte
8
isso é misterioso, por que isso? o que está acontecendo?
Tofutim
Porque as variáveis ​​se expandem
Daniel
103

RESPOSTA CURTA

Como outros já disseram - você deve sempre citar as variáveis ​​para evitar comportamentos estranhos. Portanto, use echo "$ foo" em vez de apenas echo $ foo .

RESPOSTA LONGA

Eu acho que esse exemplo merece mais explicações, porque há mais coisas acontecendo do que parece.

Eu posso ver onde está sua confusão, porque depois de executar seu primeiro exemplo, você provavelmente pensou que o shell está obviamente fazendo:

  1. Expansão de parâmetros
  2. Expansão do nome do arquivo

Então, do seu primeiro exemplo:

me$ FOO="BAR * BAR"
me$ echo $FOO

Após a expansão do parâmetro é equivalente a:

me$ echo BAR * BAR

E após a expansão do nome do arquivo é equivalente a:

me$ echo BAR file1 file2 file3 file4 BAR

E se você apenas digitar echo BAR * BARna linha de comando, verá que eles são equivalentes.

Então você provavelmente pensou: "se eu escapar do *, posso impedir a expansão do nome do arquivo"

Então, do seu segundo exemplo:

me$ FOO="BAR \* BAR"
me$ echo $FOO

Após a expansão do parâmetro deve ser equivalente a:

me$ echo BAR \* BAR

E após a expansão do nome do arquivo deve ser equivalente a:

me$ echo BAR \* BAR

E se você tentar digitar "echo BAR \ * BAR" diretamente na linha de comando, ele realmente imprimirá "BAR * BAR" porque a expansão do nome do arquivo é impedida pela fuga.

Então, por que usar $ foo não funcionou?

É porque existe uma terceira expansão - Remoção de cotação. A partir do bash, a remoção manual de cotações é:

Após as expansões anteriores, todas as ocorrências não citadas dos caracteres '\', '' 'e' "'que não resultaram de uma das expansões acima são removidas.

Então, o que acontece é que, quando você digita o comando diretamente na linha de comando, o caractere de escape não é o resultado de uma expansão anterior, portanto o BASH o remove antes de enviá-lo ao comando echo, mas no segundo exemplo, o "\ *" era o resultado de uma expansão anterior do parâmetro, portanto NÃO é removida. Como resultado, o eco recebe "\ *" e é isso que imprime.

Observe que a diferença entre o primeiro exemplo - "*" não está incluída nos caracteres que serão removidos pela Remoção de cotação.

Espero que isto faça sentido. No final, a conclusão do mesmo - basta usar aspas. Eu apenas pensei em explicar por que escapar, que logicamente deveria funcionar se apenas a expansão Parameter e Filename estiver em jogo, não funcionou.

Para uma explicação completa das expansões do BASH, consulte:

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions

mithu
fonte
1
Ótima resposta! Agora não sinto que fiz uma pergunta tão idiota. :-)
andyuk 19/09/08
1
Existe alguma utilidade para escapar dos caracteres especiais?
precisa saber é o seguinte
"você sempre deve citar as variáveis ​​para evitar comportamentos estranhos" - quando quiser usá-las como seqüências de caracteres
Angelo
Embora a resposta de @finnw acima responda diretamente à pergunta, essa resposta é muito melhor para explicar o porquê disso, o que ajuda muito mais a extrapolar como o uso em nossos próprios cenários funcionaria. Este é o tipo de resposta que precisamos ver mais.
Nicolas Coombs
54

Vou adicionar um pouco a esse tópico antigo.

Normalmente você usaria

$ echo "$FOO"

No entanto, tive problemas mesmo com essa sintaxe. Considere o seguinte script.

#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"

As *necessidades devem ser transmitidas literalmente para curl, mas os mesmos problemas surgirão. O exemplo acima não funcionará (ele será expandido para nomes de arquivos no diretório atual) e nem funcionará \*. Você também não pode citar $curl_optsporque será reconhecido como uma opção única (inválida) para curl.

curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

Portanto, eu recomendaria o uso da bashvariável $GLOBIGNOREpara impedir a expansão do nome do arquivo completamente, se aplicado ao padrão global, ou usar o set -fsinalizador interno.

#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"  ## no filename expansion

Aplicando ao seu exemplo original:

me$ FOO="BAR * BAR"

me$ echo $FOO
BAR file1 file2 file3 file4 BAR

me$ set -f
me$ echo $FOO
BAR * BAR

me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR
AlexandreH
fonte
4
Ótima explicação, obrigado! Meu caso é SELECT * FROM etc.: essa é a única maneira que funciona.
knutole
1
Obrigado por apresentar a solução set -f!
hachre
5
FOO='BAR * BAR'
echo "$FOO"
tzot
fonte
Isso funciona, mas não é essencial alterar as aspas simples na primeira linha para aspas duplas.
finnw 19/09/08
1
Não, não é, mas o hábito de usar aspas simples é preferível quando você incluir caracteres especiais do shell e não desejar substituição.
tzot 19/09/08
3
echo "$FOO"
Rafał Dowgird
fonte
3

Pode valer a pena adquirir o hábito de usar, printfe não echona linha de comando.

Neste exemplo, não oferece muitos benefícios, mas pode ser mais útil com saídas mais complexas.

FOO="BAR * BAR"
printf %s "$FOO"
Dave Webb
fonte
Por que diabos? printf é um processo separado (pelo menos, não um bash embutido) e o uso do printf que você demonstra não tem nenhum benefício sobre o eco.
Ddaa 19/09/08
2
printf é um built-in bash e eu disse na minha resposta "Neste exemplo, ele não dá muito benefício, mas ele pode ser mais útil com saída mais complexa.
Dave Webb