Змушує bash розширювати змінні в рядку, завантаженому з файлу


89

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

У мене є файл під назвою "something.txt" із вмістом:

hello $FOO world

Я тоді біжу

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

це повертає:

   hello $FOO world

Це не розширило $ FOO, навіть якщо змінну було встановлено. Я не можу виправити або створити файл, оскільки він спробує його виконати (він не є виконуваним, як є - я просто хочу, щоб рядок із змінними інтерполювався).

Будь-які ідеї?



Ви мали на увазі коментар для відповіді нижче?
Майкл Ніл,

Я мав на увазі коментар для вас. Більше однієї відповіді пропонує використання, evalі важливо знати про наслідки.
Призупинено до подальшого повідомлення.

@DennisWilliamson gotcha - Я трохи пізно відповідаю, але хороша підказка. І, звичайно, у проміжні роки у нас були такі речі, як "шок від снарядів", тому ваш коментар витримав випробування часом! tips hat
Michael Neale

Це відповідає на ваше запитання? Змінна розширення 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  "$@"

5
+1. Це єдина відповідь, яка гарантовано не виконує заміни команд, видалення цитат, обробку аргументів тощо
Джош Келлі

6
Це просто працює для повністю експортованих вар-кодів оболонки (у bash). наприклад S = '$ a $ b'; a = набір; експорт b = експортується; echo $ S | envsubst; врожайність: "експортовано"
гейт

1
дякую - я думаю, що після всіх цих років я повинен прийняти цю відповідь.
Michael Neale

1
Однак він не обробляє екранування символу долара ( $), наприклад, echo '\$USER' | envsubstвиведе поточне ім'я користувача з префіксом косою рискою, а не $USER.
Rockallite

3
схоже отримує це на Mac з потребами brew link --force gettext
домоволодіння

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"
}

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

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

На практиці я використовую це як якусь легку шаблонну систему.


4
Це один з найкращих трюків, які я знайшов на сьогодні :). На основі вашої відповіді так легко створити функцію, яка розширює змінну за допомогою рядка, що містить посилання на інші змінні - просто замініть $ data на $ 1 у вашій функції, і ось ми починаємо! Я вважаю, що це найкраща відповідь на вихідне питання, до речі.
галактика

Навіть у цього є звичні 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

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
icky, але це дуже прямо, просто і працює.
kyb

3

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

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

(Екрановані лапки навколо $ contents тут є ключовими)


Я спробував це, однак $ () видаляє закінчення рядків у моєму випадку, що порушує отриманий рядок
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метод (коротша версія "icky" відповіді Майкла Ніла ), використовуючи заміну процесу та команди :

    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
    

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.

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 ~).
Чарльз Даффі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.