Принуждение bash к расширению переменных в строке, загруженной из файла

88

Я пытаюсь понять, как заставить bash (принудительно?) Расширять переменные в строке (которая была загружена из файла).

У меня есть файл с названием something.txt с содержанием:

hello $FOO world

Я тогда бегу

export FOO=42
echo $(cat something.txt)

это возвращает:

   hello $FOO world

Он не расширил $ ​​FOO, даже если переменная была установлена. Я не могу выполнить оценку или источник файла - поскольку он попытается выполнить его (он не исполняемый как есть - мне просто нужна строка с интерполированными переменными).

Любые идеи?

Майкл Нил
источник
3
Помните о последствиях для безопасностиeval .
Приостановлено до дальнейшего уведомления.
Вы имели в виду, что комментарий к ответу ниже?
Майкл Нил
Я имел ввиду комментарий для вас. Предлагается использовать более одного ответа, evalи важно осознавать последствия.
Приостановлено до дальнейшего уведомления.
@DennisWilliamson gotcha - Я немного поздно ответил, но хороший совет. И, конечно же, за прошедшие годы у нас были такие вещи, как "контузный шок", так что ваш комментарий выдержал испытание временем! советы шляпу
Майкл Нил
Отвечает ли это на ваш вопрос? Bash расширяет переменную в переменной
LoganMzz

Ответы:

111

Я наткнулся на то, что, как мне кажется, является ответом на этот вопрос: 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  "$@"
LenW
источник
5
+1. Это единственный ответ, который гарантированно не выполняет подстановки команд, удаления кавычек, обработки аргументов и т. Д.
Джош Келли,
6
Это работает только для полностью экспортированных переменных оболочки (в bash). например, S = '$ a $ b'; а = набор; экспорт b = экспортировано; эхо $ S | envsubst; урожайность: «экспортировано»
gaoithe
1
спасибо - думаю, по прошествии всех этих лет я должен принять этот ответ.
Майкл Нил
1
Однако он не обрабатывает $экранирование знака доллара ( ), например echo '\$USER' | envsubst, выводит текущее имя пользователя с префиксом обратной косой черты, а не $USER.
Rockallite
3
кажется, получает это на Mac с потребностями домашнего пивоваренияbrew link --force gettext
KeepCalmAndCarryOn
25

Многие ответы с использованием 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"
}

Например, вы можете использовать его таким образом с a, parameters.cfgкоторый на самом деле является сценарием оболочки, который просто устанавливает переменные, и template.txtс шаблоном, который использует эти переменные:

. parameters.cfg
printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt

На практике я использую это как своего рода облегченную систему шаблонов.

WJL
источник
4
Это один из лучших приемов, которые я нашел на данный момент :). Основываясь на вашем ответе, так легко создать функцию, которая расширяет переменную строкой, содержащей ссылки на другие переменные - просто замените $ data на $ 1 в своей функции, и мы идем! Я считаю, что это лучший ответ на исходный вопрос, BTW.
galaxy
Даже здесь есть обычные evalподводные камни. Попробуйте добавить ; $(eval date)в конец любой строки во входном файле, и он запустит dateкоманду.
anubhava
1
@anubhava Я не понимаю, что вы имеете в виду; в данном случае это действительно желаемое поведение при расширении. Другими словами, да, вы должны иметь возможность выполнять произвольный код оболочки с этим.
wjl
22

можешь попробовать

echo $(eval echo $(cat something.txt))
пицца
источник
9
Используйте, echo -e "$(eval "echo -e \"`<something.txt`\"")"если вам нужно сохранить новые строки.
pevik
Работает как шарм
kyb 06
1
Но только если у вас template.txtесть текст. Эта операция опасна, потому что она выполняет (оценивает) команды, если они есть.
kyb
2
но эта удаленная двойная цитата
Томас Деко
Во всем виновата подоболочка.
ingyhere
8

Вы не хотите печатать каждую строку, вы хотите оценить ее, чтобы Bash мог выполнять замену переменных.

FOO=42
while read; do
    eval echo "$REPLY"
done < something.txt

См. help evalИли руководство по Bash для получения дополнительной информации.

Тодд А. Джейкобс
источник
2
evalопасно ; вы не только $varnameрасширяетесь (как если бы это было envsubst), но $(rm -rf ~)также и.
Чарльз Даффи
4

Другой подход (который кажется неприятным, но я все равно помещаю его здесь):

Запишите содержимое something.txt во временный файл, обернув его оператором echo:

something=$(cat something.txt)

echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out

затем верните его обратно в переменную:

RESULT=$(source temp.out)

и в $ RESULT все это будет развернуто. Но это кажется таким неправильным!

Майкл Нил
источник
1
неприглядно, но он максимально прямолинейный, простой и работает.
kyb
3

Если вы хотите, чтобы были расширены только ссылки на переменные (цель, которую я поставил перед собой), вы можете сделать следующее.

contents="$(cat something.txt)"
echo $(eval echo \"$contents\")

(Ключевыми здесь являются экранированные кавычки вокруг $ contents)

SS781
источник
Я пробовал это, однако $ () удаляет окончания строк в моем случае, что нарушает результирующую строку
moz
Я изменил строку eval на, eval "echo \"$$contents\""и она сохранила пробелы
moz
1

Следующее решение:

  • позволяет заменять переменные, которые определены

  • оставляет неизменными заполнители переменных, которые не определены. Это особенно полезно при автоматическом развертывании.

  • поддерживает замену переменных в следующих форматах:

    ${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
Апродан
источник
1
  1. Если something.txt имеет только одну строку, bashметод (более короткая версия «неприглядного» ответа Майкла Нила ), использующий подстановку процесса и команды :

    FOO=42 . <(echo -e echo $(<something.txt))
    

    Выход:

    hello 42 world
    

    Обратите внимание, что в exportэтом нет необходимости.

  2. Если something.txt содержит одну или несколько строк, метод оценки GNU :sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    
agc
источник
1
Я нашел sedоценку, которая лучше всего соответствует моей цели. Мне нужно было создать сценарии из шаблонов, значения которых будут заполняться из переменных, экспортированных в другой файл конфигурации. Он правильно обрабатывает экранирование для расширения команды, например, вставьте \$(date)в шаблон для получения $(date)вывода. Благодарность!
gadamiak
0
$ 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.
ClearCrescendo
источник
0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)
Божский Бох
источник
1
Спасибо за этот фрагмент кода, который может оказать некоторую немедленную помощь. Надлежащее объяснение будет значительно улучшить свою долгосрочную ценность, показывая , почему это является хорошим решением проблемы и сделает его более полезным для читателей будущих с другими подобными вопросами. Пожалуйста , измените свой ответ , чтобы добавить некоторые объяснения, в том числе допущений , которые вы сделали.
Козерог
-1

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

Это решение работает даже с большими файлами.

Ларри К.
источник
-3

Следующие работы: bash -c "echo \"$(cat something.txt)"\"

Эдди
источник
Это не только неэффективно, но и чрезвычайно опасно; он запускает не только безопасные расширения $foo, но и небезопасные $(rm -rf ~).
Чарльз Даффи