O padrão POSIX impõe que a expansão de palavras seja feita na seguinte ordem (a ênfase é minha):
Expansão de til (consulte Expansão de til), expansão de parâmetro (consulte Expansão de parâmetro), substituição de comando (consulte Substituição de comando) e expansão aritmética (consulte Expansão aritmética) devem ser executadas, do início ao fim. Veja o item 5 em Reconhecimento de token.
A divisão de campo (consulte Divisão de campo) deve ser realizada nas partes dos campos gerados pela etapa 1, a menos que o IFS seja nulo.
A expansão do nome do caminho (consulte Expansão do nome do caminho) deve ser executada, a menos que o conjunto -f esteja em vigor.
A remoção de cotação (consulte Remoção de cotação) deve sempre ser realizada por último.
O único ponto que nos interessa aqui é o primeiro: como você pode ver, a expansão til é processada antes da expansão do parâmetro:
- O shell tenta expandir o til
echo $x
, não há nenhum til a ser encontrado e, portanto, prossegue.
- O shell tenta uma expansão de parâmetro
echo $x
, $x
é encontrado e expandido e a linha de comando se torna echo ~/someDirectory
.
- O processamento continua, já que a expansão do til já foi processada, o
~
personagem permanece como está.
Ao usar as aspas ao atribuir o $x
, você estava solicitando explicitamente para não expandir o til e tratá-lo como um caractere normal. Uma coisa que muitas vezes se perde é que, nos comandos do shell, você não precisa citar toda a cadeia, para que você possa fazer a expansão acontecer corretamente durante a atribuição de variável:
user@host:~$ set -o xtrace
user@host:~$ x=~/'someDirectory'
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
E você também pode fazer com que a expansão ocorra na echo
linha de comando, desde que isso ocorra antes da expansão do parâmetro:
user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Se, por algum motivo, você realmente precisar afetar o til da $x
variável sem expansão e poder expandi-lo no echo
comando, prossiga duas vezes para forçar $x
a ocorrência de duas expansões da variável:
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ echo "$( eval echo $x )"
++ eval echo '~/someDirectory'
+++ echo /home/user/someDirectory
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
No entanto, esteja ciente de que, dependendo do contexto em que você usa essa estrutura, ela pode ter efeitos colaterais indesejados. Como regra geral, prefira evitar usar qualquer coisa que exija eval
quando você tiver outra maneira.
Se você deseja resolver especificamente o problema de til, em oposição a qualquer outro tipo de expansão, essa estrutura seria mais segura e portátil:
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ case "$x" in "~/"*)
> x="${HOME}/${x#"~/"}"
> esac
+ case "$x" in
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Essa estrutura verifica explicitamente a presença de um líder ~
e o substitui pelo diretório inicial do usuário, caso ele seja encontrado.
Após o seu comentário, isso x="${HOME}/${x#"~/"}"
pode ser realmente surpreendente para alguém que não é usado na programação de shell, mas está de fato vinculado à mesma regra POSIX citada acima.
Conforme imposto pelo padrão POSIX, a remoção da cotação ocorre por último e a expansão dos parâmetros ocorre muito cedo. Assim, ${#"~"}
é avaliado e expandido muito antes da avaliação das aspas externas. Por sua vez, conforme definido nas regras de expansão de parâmetros :
Em cada caso em que um valor de palavra é necessário (com base no estado do parâmetro, conforme descrito abaixo), a palavra deve ser sujeita a expansão de til, expansão de parâmetro, substituição de comando e expansão aritmética.
Portanto, o lado direito do #
operador deve ser citado ou escapado adequadamente para evitar a expansão do til.
Portanto, para dizer de maneira diferente, quando o interpretador de shell olha x="${HOME}/${x#"~/"}"
, ele vê:
${HOME}
e ${x#"~/"}
deve ser expandido.
${HOME}
é expandido para o conteúdo da $HOME
variável
${x#"~/"}
aciona uma expansão aninhada: "~/"
é analisado, mas, sendo citado, é tratado como um literal 1 . Você poderia ter usado aspas simples aqui com o mesmo resultado.
${x#"~/"}
a própria expressão agora é expandida, resultando na ~/
remoção do prefixo do valor de $x
.
- O resultado do exposto agora é concatenado: a expansão
${HOME}
, o literal /
, a expansão ${x#"~/"}
.
- O resultado final é colocado entre aspas duplas, impedindo funcionalmente a divisão de palavras. Eu digo funcionalmente aqui, porque essas aspas duplas não são tecnicamente necessárias (veja aqui e ali, por exemplo), mas como um estilo pessoal, assim que as atribuições vão além
a=$b
, geralmente acho mais claro adicionar aspas duplas.
A propósito, se olharmos mais de perto a case
sintaxe, você verá a "~/"*
construção que se baseia no mesmo conceito que x=~/'someDirectory'
eu expliquei acima (aqui novamente, aspas simples e duplas podem ser usadas de forma intercambiável).
Não se preocupe se essas coisas parecerem obscuras à primeira vista (talvez até na segunda ou mais tarde!). Na minha opinião, a expansão de parâmetros é, com subconjuntos, um dos conceitos mais complexos a serem compreendidos ao se programar em linguagem shell.
Sei que algumas pessoas podem discordar vigorosamente, mas se você gostaria de aprender mais sobre a programação do shell, incentivo-o a ler o Advanced Bash Scripting Guide : ele ensina o Bash scripting, portanto, com muitas extensões e sinos. assobios em comparação ao script de shell POSIX, mas achei bem escrito com vários exemplos práticos. Depois que você gerencia isso, é fácil restringir-se aos recursos do POSIX quando necessário; pessoalmente, acho que entrar diretamente no domínio POSIX é uma curva de aprendizado desnecessária para iniciantes (compare meu substituto do POSIX com o Bash do mexdular, como o regex equivalente a ter uma idéia do que quero dizer;)!).
1 : O que me leva a encontrar um bug no Dash que não implementa a expansão de til aqui corretamente (uso verificável x='~/foo'; echo "${x#~/}"
). A expansão de parâmetros é um campo complexo, tanto para o usuário quanto para os próprios desenvolvedores de shell!
x='~'; print -l ${x} ${~x}
. Desisti depois de vasculhar obash
manual por um tempo.