"I want users to not have to spell out path parts when there exist variables for it (for example, $HOME for the home directory)."
Isso pode ser feito sem eval
:
$ s='$HOME/.config'
$ s="${s//\$HOME/$HOME}"
$ echo "$s"
/home/john1024/.config
Isso tem algumas limitações. Por um lado, se HOMES e HOME forem nomes de variáveis que você deseja substituir, então, para evitar correspondências falsas, HOMES deve ser substituído antes HOME.
Aplicando substituições para todas as variáveis exportadas
Usando o bash:
while IFS== read -r name val
do
s="${s//\$$name/$val}"
done < <(printenv)
Por exemplo:
$ export A=alpha; export B=beta
$ s='$HOME/$A/$B'
$ while IFS== read -r name val; do s="${s//\$$name/$val}"; done < <(printenv)
$ echo "$s"
/home/john1024/alpha/beta
Como essa abordagem não classifica os nomes das variáveis por tamanho, ela tem o problema de sobreposição da variável mencionado acima.
Podemos corrigir isso classificando os nomes das variáveis de acordo com o comprimento:
while IFS== read -r n name val
do
s="${s//\$$name/$val}"
done < <(printenv | awk '/^[^ \t]/{key=$0; sub(/=.*/,"",key); printf "%s=%s\n",length(key),$0}' | sort -rnt=)
Se o usuário inserir um nome de variável que não exista, mas os caracteres iniciais corresponderem a algum nome mais curto, o nome mais curto será substituído. Se isso for importante, podemos evitá-lo exigindo que o usuário use a notação de chaves para essas variáveis com este código:
while IFS== read -r n name val
do
s="${s//\$\{$name\}/$val}"
done < <(printenv | awk '/^[^ \t]/{key=$0; sub(/=.*/,"",key); printf "%s=%s\n",length(key),$0}' | sort -rnt=)
Como exemplo:
$ s='${HOME}/${A}/${B}'
$ while IFS== read -r n name val; do s="${s//\$\{$name\}/$val}"; done < <(printenv | awk '/^[^ \t]/{key=$0; sub(/=.*/,"",key); printf "%s=%s\n",length(key),$0}' | sort -rnt=)
$ echo "$s"
/home/john1024/alpha/beta