Заставить bash раскрыть переменные в строке, загруженной из файла
Я пытаюсь понять, как заставить bash (force?) Раскрыть переменные в строке (которая была загружена из файла).
У меня есть файл с именем "thing.txt"с содержимым:
hello $FOO world
Тогда я бегу
export FOO=42
echo $(cat something.txt)
это возвращает:
hello $FOO world
Он не расширяет $FOO, хотя переменная была установлена. Я не могу проверить или получить исходный файл - так как он попытается выполнить его (он не исполняемый как есть - я просто хочу строку с интерполированными переменными).
Есть идеи?
14 ответов
Я наткнулся на то, что я думаю, что ответ на этот вопрос: envsubst
команда.
envsubst < something.txt
Если он еще не доступен в вашем дистрибутиве, он находится в
Пакет GNU gettext
,
@Rockallite - я написал небольшой скрипт-обертку, чтобы позаботиться о проблеме '\$'.
(Кстати, есть "особенность" envsubst, объясненная по адресу https://unix.stackexchange.com/a/294400/7088 для расширения только некоторых переменных во входных данных, но я согласен, что экранирование исключений гораздо более удобный.)
Вот мой сценарий:
#! /bin/bash
## -*-Shell-Script-*-
CmdName=${0##*/}
Usage="usage: $CmdName runs envsubst, but allows '\$' to keep variables from
being expanded.
With option -sl '\$' keeps the back-slash.
Default is to replace '\$' with '$'
"
if [[ $1 = -h ]] ;then echo -e >&2 "$Usage" ; exit 1 ;fi
if [[ $1 = -sl ]] ;then sl='\' ; shift ;fi
sed 's/\\\$/\${EnVsUbDolR}/g' | EnVsUbDolR=$sl\$ envsubst "$@"
Многие из ответов, используя eval
а также echo
работать, но разбивать на разные вещи, например, на несколько строк, пытаться экранировать метасимволы оболочки, экранировать внутри шаблона, не предназначенного для расширения с помощью bash, и т. д.
У меня была та же проблема, и я написал эту функцию оболочки, которая, насколько я могу судить, обрабатывает все правильно. Из-за правил подстановки команд bash это по-прежнему удаляет только завершающие символы новой строки из шаблона, но я никогда не обнаруживал, что это проблема, если все остальное остается нетронутым.
apply_shell_expansion() {
declare file="$1"
declare data=$(< "$file")
declare delimiter="__apply_shell_expansion_delimiter__"
declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter"
eval "$command"
}
Например, вы можете использовать это так с parameters.cfg
который на самом деле является сценарием оболочки, который просто устанавливает переменные, и template.txt
это шаблон, который использует эти переменные:
. parameters.cfg
printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt
На практике я использую это как своего рода облегченную систему шаблонов.
Вы не хотите печатать каждую строку, вы хотите оценить ее, чтобы Bash мог выполнять подстановку переменных.
FOO=42
while read; do
eval echo "$REPLY"
done < something.txt
Увидеть help eval
или руководство по Bash для получения дополнительной информации.
Другой подход (который кажется неприличным, но я все равно выкладываю его здесь):
Записать содержимое файла thing.txt во временный файл, заключив в него оператор echo:
something=$(cat something.txt)
echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out
затем отправьте его обратно в переменную:
RESULT=$(source temp.out)
и в $RESULT все это будет расширено. Но это так неправильно!
Если вы хотите, чтобы ссылки на переменные были расширены (цель, которую я поставил перед собой), вы можете сделать следующее.
contents="$(cat something.txt)"
echo $(eval echo \"$contents\")
(Ключевыми здесь являются экранированные кавычки вокруг $content)
bash
метод (однострочный вариант ответа Майкла Нила), использующий подстановку процессов и команд:FOO=42 . <(echo -e echo $(<something.txt))
Выход:
hello 42 world
Обратите внимание, что
export
не нужноGNU
sed
Оцените метод, если что-то имеет много строк:FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
Следующее решение:
позволяет заменять переменные, которые определены
оставляет неизменными переменные заполнители, которые не определены. Это особенно полезно при автоматическом развертывании.
поддерживает замену переменных в следующих форматах:
${var_NAME}
$var_NAME
сообщает, какие переменные не определены в среде, и возвращает код ошибки для таких случаев
TARGET_FILE=someFile.txt;
ERR_CNT=0;
for VARNAME in $(grep -P -o -e '\$[\{]?(\w+)*[\}]?' ${TARGET_FILE} | sort -u); do
VAR_VALUE=${!VARNAME};
VARNAME2=$(echo $VARNAME| sed -e 's|^\${||g' -e 's|}$||g' -e 's|^\$||g' );
VAR_VALUE2=${!VARNAME2};
if [ "xxx" = "xxx$VAR_VALUE2" ]; then
echo "$VARNAME is undefined ";
ERR_CNT=$((ERR_CNT+1));
else
echo "replacing $VARNAME with $VAR_VALUE2" ;
sed -i "s|$VARNAME|$VAR_VALUE2|g" ${TARGET_FILE};
fi
done
if [ ${ERR_CNT} -gt 0 ]; then
echo "Found $ERR_CNT undefined environment variables";
exit 1
fi
foo=45
file=something.txt # in a file is written: Hello $foo world!
eval echo $(cat $file)
$ eval echo $(cat something.txt)
hello 42 world
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.
catexp () {
local function_name="${FUNCNAME[0]}"
local uuid="$(uuidgen)"
if [[ $# = 0 && -t 0 ]]; then
echo "$function_name: Missing argument and stdin." >&2
return 1
fi
. <(
printf -- '%s\n' "cat << ${uuid}"
cat "$@" 2> >(perl -spe 's/^cat: /$function_name: /' -- -function_name="$function_name" >&2)
exit_code=$?
printf -- '\n%s\n' "${uuid}"
printf -- '%s\n' "(exit ${exit_code})"
) | perl -0pe 's/\n$//'
pipestatus
return $?
}
pipestatus () {
set "${PIPESTATUS[@]}"
local i
for (( i=1; i<$#; i++ )); do
if [[ ${!i} -ge 1 ]]; then break; fi
done
return ${!i}
}
echo "hello \$FOO world" > source.txt
FOO=42 # Also without export
catexp source.txt
Или:
catexp < source.txt
Или:
cat source.txt | catexp
Также:
catexp file1 file2 file3 ...
envsubst
является отличным решением (см. ответ LenW), если замещаемый контент имеет "разумную" длину.
В моем случае мне нужно было заменить содержимое файла, чтобы заменить имя переменной. envsubst
требует, чтобы содержимое экспортировалось как переменные среды, и bash имеет проблему при экспорте переменных среды, размер которых превышает мегабайт или около того.
awk решение
Используя решение cuonglm из другого вопроса:
needle="doc1_base64" # The "variable name" in the file. (A $ is not needed.)
needle_file="doc1_base64.txt" # Will be substituted for the needle
haystack=$requestfile1 # File containing the needle
out=$requestfile2
awk "BEGIN{getline l < \"${needle_file}\"}/${needle}/{gsub(\"${needle}\",l)}1" $haystack > $out
Это решение работает даже для больших файлов.
expenv () {
LF=$'\n'
echo "cat <<END_OF_TEXT${LF}$(< "$1")${LF}END_OF_TEXT" | bash
return $?
}
expenv "file name"