Если использование Perl - вариант, и вы довольны основанием расширений только на переменных среды (в отличие от всех переменных оболочки ), рассмотрите надежный ответ Стюарта П. Бентли .
Этот ответ направлен на предоставление решения только для bash, которое, несмотря на его использование, eval
должно быть безопасным в использовании .
Эти цели являются:
- Поддержка расширения ссылок на обе
${name}
и $name
переменные.
- Запретить все другие расширения:
- подстановки команд (
$(...)
и унаследованный синтаксис `...`
)
- арифметические замены (
$((...))
и унаследованный синтаксис $[...]
).
- Разрешить выборочное подавление раскрытия переменных с помощью префикса
\
( \${name}
).
- Сохраняйте специальные символы. на входе, в частности,
"
и \
экземпляры.
- Разрешить ввод через аргументы или через стандартный ввод.
ФункцияexpandVars()
:
expandVars() {
local txtToEval=$* txtToEvalEscaped
# If no arguments were passed, process stdin input.
(( $# == 0 )) && IFS= read -r -d '' txtToEval
# Disable command substitutions and arithmetic expansions to prevent execution
# of arbitrary commands.
# Note that selectively allowing $((...)) or $[...] to enable arithmetic
# expressions is NOT safe, because command substitutions could be embedded in them.
# If you fully trust or control the input, you can remove the `tr` calls below
IFS= read -r -d '' txtToEvalEscaped < <(printf %s "$txtToEval" | tr '`([' '\1\2\3')
# Pass the string to `eval`, escaping embedded double quotes first.
# `printf %s` ensures that the string is printed without interpretation
# (after processing by by bash).
# The `tr` command reconverts the previously escaped chars. back to their
# literal original.
eval printf %s "\"${txtToEvalEscaped//\"/\\\"}\"" | tr '\1\2\3' '`(['
}
Примеры:
$ expandVars '\$HOME="$HOME"; `date` and $(ls)'
$HOME="/home/jdoe"; `date` and $(ls) # only $HOME was expanded
$ printf '\$SHELL=${SHELL}, but "$(( 1 \ 2 ))" will not expand' | expandVars
$SHELL=/bin/bash, but "$(( 1 \ 2 ))" will not expand # only ${SHELL} was expanded
- По соображениям производительности функция считывает ввод stdin сразу в память, но ее легко адаптировать к построчному подходу.
- Также поддерживает неосновные расширения переменных, например
${HOME:0:10}
, если они не содержат встроенных команд или арифметических подстановок, таких как${HOME:0:$(echo 10)}
- Такие встроенные замены фактически BREAK функции (потому что все
$(
и `
экземпляры слепо экранирование).
- Точно так же искаженные ссылки на переменные, такие как
${HOME
(отсутствие закрытия }
) BREAK функции.
- Из-за того, что bash обрабатывает строки с двойными кавычками, обратная косая черта обрабатывается следующим образом:
\$name
предотвращает расширение.
- Сингл,
\
за которым не следует, $
сохраняется как есть.
- Если вы хотите представить несколько смежных
\
экземпляров, вы должны удвоить их ; например:
\\
-> \
- так же, как просто\
\\\\
-> \\
- Вход не должен содержать следующее (редко используемые символы), которые используются для внутренних целей:
0x1
, 0x2
, 0x3
.
- Существует в основном гипотетическая проблема: если bash представит новый синтаксис расширения, эта функция не сможет предотвратить такие расширения - см. Ниже решение, которое не использует
eval
.
Если вы ищете более ограниченное решение, которое поддерживает только${name}
расширения, то есть с обязательными фигурными скобками, игнорируя $name
ссылки, см. Этот мой ответ .
Вот улучшенная версия eval
бесплатного решения только для bash из принятого ответа :
Усовершенствования:
- Поддержка расширения как ссылок, так
${name}
и $name
переменных.
- Поддержка
\
-экранирование ссылок на переменные, которые не следует расширять.
- В отличие от
eval
решения на основе выше,
- неосновные расширения игнорируются
- неверно сформированные ссылки на переменные игнорируются (они не нарушают работу скрипта)
IFS= read -d '' -r lines # read all input from stdin at once
end_offset=${#lines}
while [[ "${lines:0:end_offset}" =~ (.*)\$(\{([a-zA-Z_][a-zA-Z_0-9]*)\}|([a-zA-Z_][a-zA-Z_0-9]*))(.*) ]] ; do
pre=${BASH_REMATCH[1]} # everything before the var. reference
post=${BASH_REMATCH[5]}${lines:end_offset} # everything after
# extract the var. name; it's in the 3rd capture group, if the name is enclosed in {...}, and the 4th otherwise
[[ -n ${BASH_REMATCH[3]} ]] && varName=${BASH_REMATCH[3]} || varName=${BASH_REMATCH[4]}
# Is the var ref. escaped, i.e., prefixed with an odd number of backslashes?
if [[ $pre =~ \\+$ ]] && (( ${#BASH_REMATCH} % 2 )); then
: # no change to $lines, leave escaped var. ref. untouched
else # replace the variable reference with the variable's value using indirect expansion
lines=${pre}${!varName}${post}
fi
end_offset=${#pre}
done
printf %s "$lines"