"Я хочу, чтобы пользователям не приходилось указывать части пути, если для этого существуют переменные (например, $ HOME для домашнего каталога). "
Это можно сделать без eval
:
$ s='$HOME/.config'
$ s="${s//\$HOME/$HOME}"
$ echo "$s"
/home/john1024/.config
Это имеет некоторые ограничения. Во-первых, если и HOMES, и HOME являются именами переменных, которые вы хотите подставить, то, чтобы избежать ложных совпадений, HOMES необходимо подставить перед HOME.
Использование bash:
while IFS== read -r name val
do
s="${s//\$$name/$val}"
done < <(printenv)
Например:
$ 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
Поскольку этот подход не сортирует имена переменных по длине, возникает проблема перекрытия переменных, упомянутая выше.
Мы можем исправить это, отсортировав имена переменных по длине:
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=)
Если пользователь вводит имя переменной, которое не существует, но начальные символы соответствуют некоторому более короткому имени, более короткое имя будет заменены. Если это имеет значение, мы можем избежать этого, потребовав от пользователя использовать скобки для этих переменных с помощью этого кода:
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=)
В качестве примера:
$ 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