Як замінити заповнювачі $ {} у текстовому файлі?


163

Я хочу передавати висновок файлу "шаблон" у MySQL, при цьому файл має такі змінні, як ${dbName}перемежовані. Яка утиліта командного рядка замінює ці екземпляри та скидає вихід на стандартний вихід?

Відповіді:


192

Сед !

Дано template.txt:

Кількість - $ {i}
Слово - $ {word}

ми просто повинні сказати:

sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.txt

Дякую Джонатану Леффлеру за пораду передавати кілька -eаргументів на одне sedвиклик.


13
Ви можете об'єднати ці дві команди sed в одну: sed -e "s / \ $ {i} / 1 /" -e "s / \ $ {word} / dog /"; що є більш ефективним. Ви можете зіткнутися з проблемами з деякими версіями sed, можливо, 100 таких операцій (проблема років тому - можливо, все ще не відповідає дійсності, але остерігайтеся HP-UX).
Джонатан Леффлер

1
Дякую Джонатане, саме тому, що я шукав.
Dana Sane

3
Невеликий натяк: якщо "1" або "собака" у наведеному прикладі міститимуть символ долара, вам доведеться уникнути його з зворотним нахилом (інакше заміна не відбудеться).
MatthieuP

9
Вам також не потрібні cat. Все, що вам потрібно - це sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text.
HardlyKnowEm

3
Що робити, якщо текст заміни - це пароль? У цьому випадку sedбуде очікувати уникнутий текст, який є клопотом.
jpbochi

177

Оновлення

Ось рішення від йоттаца з подібного питання, яке замінює лише такі змінні, як $ VAR або $ {VAR}, і є коротким однолінійним

i=32 word=foo envsubst < template.txt

Звичайно, якщо я і слово є у вашому оточенні, то це просто

envsubst < template.txt

На моєму Mac це виглядає так, що він був встановлений як частина gettext та з MacGPG2

Стара відповідь

Ось вдосконалення рішення Mogsie за аналогічним запитанням, моє рішення не вимагає від вас збільшувати подвійні цитати, як це робить mogsie, але його - це один лайнер!

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

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


6
Я вважаю, що голі envsubstне спрацьовують, якщо ваші товариші не експортуються.
Тоддіус Жо

4
@ToddiusZho: Не існує такої речі, як змінна середовища, яка не експортується - саме експорт, який робить змінну оболонки змінною середовища. envsubst, як випливає з назви, розпізнає лише змінні середовища , а не змінні оболонки . Варто також зазначити, що envsubstце утиліта GNU , а тому не встановлена ​​або доступна на всіх платформах.
mklement0

2
Можливо, ще один спосіб сказати, що envsubst бачить лише свої власні змінні середовища процесів, тому "нормальні" змінні оболонки, які ви могли визначити раніше (в окремих рядках), не успадковуються дочірніми процесами, якщо ви не "експортуєте" їх. У моєму прикладі використання gettext вище, я змінюю успадковане середовище gettext через механізм bash, префіксуючи їх до команди, яку я збираюся запустити
plockc

1
У мене є одна рядок із $ HOME, і я знайшов, що $ HOME працює як оболонка за замовчуванням, замість цього $ HOME як власний / home / zw963, але, схоже, не підтримує заміну $ (cat / etc / hostname), тож це не відповідає моїм власним вимогам.
zw963

3
Дякуємо за "Старий відповідь", оскільки він не лише дозволяє змінні, але і команди оболонки типу $ (ls -l)
Алек,

46

Використовуйте /bin/sh. Створіть невеликий сценарій оболонки, який встановлює змінні, а потім проаналізуйте шаблон, використовуючи саму оболонку. Так (редагуйте, щоб правильно обробляти нові рядки):

Шаблон файлу.txt:

the number is ${i}
the word is ${word}

Файл script.sh:

#!/bin/sh

#Set variables
i=1
word="dog"

#Read in template one line at the time, and replace variables (more
#natural (and efficient) way, thanks to Jonathan Leffler).
while read line
do
    eval echo "$line"
done < "./template.txt"

Вихід:

#sh script.sh
the number is 1
the word is dog

2
Чому б не просто: під час читання рядка; зробити eval "$ line"; зроблено <./template.txt ??? Немає необхідності читати весь файл у пам’яті, а лише виплюнути його по одному рядку за допомогою інтенсивного використання голови та хвоста. Але 'eval' нормально - якщо тільки в шаблоні немає символів оболонки, як цитати.
Джонатан Леффлер

16
Це дуже небезпечно! Вся bashкоманда на вході буде виконана. Якщо шаблон такий: "слова є; rm -rf $ HOME", ви втратите файли.
rzymek

1
@rzymek - пам’ятайте, він хоче передавати цей файл безпосередньо в базу даних. Отже, у відповідь на введення довіряють.
gnud

4
@gnud Існує різниця між довірою файлу, достатньою для зберігання його вмісту, та достатньою довірою для виконання всього, що він містить.
Mark

3
Щоб відзначити обмеження: (a) подвійні лапки у введенні спокійно відкидаються; (b) readкоманда, як написано, обрізає провідні та кінцеві пробіли з кожного рядка та "їсть" \ символи. (C) використовуйте це лише якщо ви повністю довіряти або керувати входом, тому що заміни команд ( `…` або $(…)), вбудовані у вхід, дозволяють виконувати довільні команди завдяки використанню eval. Нарешті, є невеликий шанс echoпомилитися на початку рядка для одного з його параметрів командного рядка.
mklement0

23

Я знову замислювався над цим, враховуючи недавній інтерес, і думаю, що інструментом, про який я спочатку думав, був m4макропроцесор для автоінструментів. Тож замість змінної, яку я спочатку вказав, ви використовуєте:

$echo 'I am a DBNAME' | m4 -DDBNAME="database name"

1
Це рішення має найменші недоліки відповідей тут. Чи знаєте ви про будь-який спосіб замінити $ {DBNAME} замість лише DBNAME?
Джек Девідсон

@JackDavidson Я використовував би envsubstдля цієї простої заміни / використання шаблонів змінну, як зазначено в інших відповідях. m4це чудовий інструмент, але це повноцінний препроцесор з набагато більшою кількістю функцій і, таким чином, складність, яка може не знадобитися, якщо ви просто хочете замінити деякі змінні.
іммірік

13

template.txt

Variable 1 value: ${var1}
Variable 2 value: ${var2}

data.sh

#!/usr/bin/env bash
declare var1="value 1"
declare var2="value 2"

parser.sh

#!/usr/bin/env bash

# args
declare file_data=$1
declare file_input=$2
declare file_output=$3

source $file_data
eval "echo \"$(< $file_input)\"" > $file_output

./parser.sh data.sh template.txt parsed_file.txt

parsed_file.txt

Variable 1 value: value 1
Variable 2 value: value 2

1
Як було зазначено в іншому місці: Використовуйте це лише у випадку, якщо ви повністю довіряєте або керуєте входом, оскільки заміни команд ( `…` або $(…)), вбудовані у вхід, дозволяють виконувати довільні команди завдяки використанню evalта прямому виконанню коду оболонки через використання source. Крім того, подвійні лапки у введенні спокійно відкидаються, і вони echoможуть помилково почати рядок для одного з його параметрів командного рядка.
mklement0

На жаль, це знімає всі подвійні лапки (") з результатного файлу. Чи є спосіб зробити те ж саме, не видаляючи подвійних лапок?"
Івайло Славов

Я знайшов те, що шукав тут: stackoverflow.com/a/11050943/795158 ; Я використав envsubst. Різниця полягає в тому, що вар доводиться експортувати, що зі мною було нормально.
Івайло Славов

якщо текстовий файл містить "` "чи". " , substitude не вдасться.
shuiqiang

12

Ось надійна функція Bash, яка, незважаючи на використання, evalповинна бути безпечною у використанні.

Всі ${varName}посилання змінних у вхідному тексті розширюються на основі змінних оболонки виклику.

Ніщо інше не розширюється: ні змінні посилання, імена яких не укладені {...}(наприклад $varName), ні підстановки команд ( $(...)і застарілий синтаксис `...`), ні арифметичні підстановки ( $((...))і застарілий синтаксис $[...]).

Трактувати a $як буквальне, \-швидти його; наприклад:\${HOME}

Зауважте, що введення приймається лише через stdin .

Приклад:

$ expandVarsStrict <<<'$HOME is "${HOME}"; `date` and \$(ls)' # only ${HOME} is expanded
$HOME is "/Users/jdoe"; `date` and $(ls)

Вихідний код функції:

expandVarsStrict(){
  local line lineEscaped
  while IFS= read -r line || [[ -n $line ]]; do  # the `||` clause ensures that the last line is read even if it doesn't end with \n
    # Escape ALL chars. that could trigger an expansion..
    IFS= read -r -d '' lineEscaped < <(printf %s "$line" | tr '`([$' '\1\2\3\4')
    # ... then selectively reenable ${ references
    lineEscaped=${lineEscaped//$'\4'{/\${}
    # Finally, escape embedded double quotes to preserve them.
    lineEscaped=${lineEscaped//\"/\\\"}
    eval "printf '%s\n' \"$lineEscaped\"" | tr '\1\2\3\4' '`([$'
  done
}

Функція не припускав , що немає 0x1, 0x2, 0x3, і 0x4керуючі символи присутні на вході, тому що ці символи. використовуються внутрішньо - оскільки функція обробляє текст , це має бути безпечним припущенням.


2
Це одна з найкращих відповідей тут. Навіть при використанні evalйого досить безпечно у використанні.
анубхава

1
Це рішення працює з файлами JSON! (втеча "належним чином!)
WBAR

2
Приємна річ з цим рішенням, це те, що він дозволить вам надавати за замовчуванням відсутніх змінних ${FOO:-bar}або виводити щось лише якщо воно встановлено - ${HOME+Home is ${HOME}}. Я підозрюю, що з невеликим розширенням він також може повернути вихідні коди для відсутніх змінних, ${FOO?Foo is missing}але наразі tldp.org/LDP/abs/html/parameter-substitution.html не має їх списку, якщо це допомагає
Стюарт Мур

11

Створити rendertemplate.sh:

#!/usr/bin/env bash

eval "echo \"$(cat $1)\""

І template.tmpl:

Hello, ${WORLD}
Goodbye, ${CHEESE}

Візуалізуйте шаблон:

$ export WORLD=Foo
$ CHEESE=Bar ./rendertemplate.sh template.tmpl 
Hello, Foo
Goodbye, Bar

2
Це знімає рядки з подвійним цитуванням
vrtx54234

Спробував: eval "echo $ (cat $ 1)" - без котирувань, і це працювало на мене.
access_granted

2
З точки зору безпеки, це погані новини. Якщо ваш шаблон містить $(rm -rf ~), ви запускаєте його як код.
Чарльз Даффі

eval "echo \"$(cat $1)\"" Чудово працює!
dev devv

10

ось моє рішення perl, засноване на колишній відповіді, замінює змінні середовища:

perl -p -e 's/\$\{(\w+)\}/(exists $ENV{$1}?$ENV{$1}:"missing variable $1")/eg' < infile > outfile

2
Це чудово. Не завжди майте перл, але коли ви це робите, це просто і прямо.
Аарон Макміллін

5

Якщо ви відкриті для використання Perl , це було б моєю пропозицією. Хоча, мабуть, є деякі фахівці з сед та / або AWK, які, напевно, знають, як це зробити набагато простіше. Якщо у вас є більш складне відображення з більш ніж просто dbName для вашої заміни, ви можете розширити це досить легко, але ви могли б так само добре вкласти його в стандартний сценарій Perl в цій точці.

perl -p -e 's/\$\{dbName\}/testdb/s' yourfile | mysql

Короткий сценарій Perl, щоб зробити щось трохи складніше (обробляти кілька клавіш):

#!/usr/bin/env perl
my %replace = ( 'dbName' => 'testdb', 'somethingElse' => 'fooBar' );
undef $/;
my $buf = <STDIN>;
$buf =~ s/\$\{$_\}/$replace{$_}/g for keys %replace;
print $buf;

Якщо ви назвали вищезазначений скрипт як сценарій заміни, він може бути використаний наступним чином:

replace-script < yourfile | mysql

1
Працює для одиничних змінних, але як я включаю "чи" для інших?
Dana Sane

2
Існує багато способів зробити це за допомогою perl, все залежно від того, наскільки складно та / або безпечно ви хотіли це зробити. Більш складні приклади можна знайти тут: perlmonks.org/?node_id=718936
Beau Simensen

3
Використовувати perl набагато чистіше, ніж намагатися використовувати оболонку. Витратьте час, щоб зробити цю роботу, а не пробувати деякі інші згадані рішення на основі оболонок.
jdigital

1
Нещодавно довелося вирішити подібне питання. Врешті-решт я пішов з perl (envsubst трохи виглядав перспективно, але це було занадто важко контролювати).
sfitts

5

Ось спосіб змусити оболонку зробити заміну замість вас, як ніби вміст файла замість цього був набраний між подвійними лапки.

Використовуючи приклад template.txt із вмістом:

The number is ${i}
The word is ${word}

Наступний рядок призведе до того, що оболонка буде інтерполювати вміст template.txt і записувати результат у стандартне значення.

i='1' word='dog' sh -c 'echo "'"$(cat template.txt)"'"'

Пояснення:

  • iі wordпередаються як змінні середовища, скопійовані до виконання sh.
  • sh виконує вміст рядка, який він передає.
  • Рядки, написані поруч один з одним, стають одним рядком;
    • ' echo "' + " $(cat template.txt)" + ' "'
  • Оскільки заміна знаходиться між ", " $(cat template.txt)" стає результатом cat template.txt.
  • Отже команда, що виконується, sh -cстає:
    • echo "The number is ${i}\nThe word is ${word}",
    • де iі wordє вказані змінні середовища.

З точки зору безпеки, це погані новини. Якщо ваш шаблон містить, скажімо, '$(rm -rf ~)'$(rm -rf ~)буквальні цитати у файлі шаблону будуть відповідати тим, які ви додали до його розширення.
Чарльз Даффі

Я не вважаю, що котирування в шаблоні відповідають цитатам поза шаблоном, я вважаю, що оболонка розв'язує шаблон і встроковий рядок самостійно (ефективно видаляючи лапки), потім об'єднуючи їх. Версія тесту, яка не видаляє ваш домашній каталог, є '$(echo a)'$(echo a). Це виробляє 'a'a. Головне, що відбувається, це те, що перше echo aвсередині 'оцінюється, що може бути не тим, чого ви очікуєте, оскільки воно є ', але це така ж поведінка, як і включення 'в "цитований рядок.
Апріорі

Отже, це не є безпечним у тому сенсі, що дозволяє автору шаблонів виконувати свій код. Однак те, як оцінюються цитати, насправді не впливає на безпеку. "Справа в розширенні чого-небудь, котируемого рядка (включаючи $(...)).
Апріорі

У цьому справа? Я бачу лише, як вони просять ${varname}не про інші розширення з підвищеною небезпекою.
Чарльз Даффі

... що сказано, я повинен відрізнятися (re: in-template та out-template цитати можуть відповідати). Коли ви поміщаєте одну цитату у свій рядок, ви розбиваєтесь на рядок з одним котируванням echo ", за яким слідує рядок з подвійним цитуванням з буквальними контекстами template.txt, а потім інший буквальний рядок ", об'єднаний в один аргумент, переданий до sh -c. Ви маєте рацію, що це 'неможливо зіставити (оскільки його споживає зовнішня оболонка, а не передається внутрішній), але, "безумовно, може, тому шаблон, що містить, Gotcha"; rm -rf ~; echo "може бути виконаний.
Чарльз Даффі

4

file.tpl:

The following bash function should only replace ${var1} syntax and ignore 
other shell special chars such as `backticks` or $var2 or "double quotes". 
If I have missed anything - let me know.

script.sh:

template(){
    # usage: template file.tpl
    while read -r line ; do
            line=${line//\"/\\\"}
            line=${line//\`/\\\`}
            line=${line//\$/\\\$}
            line=${line//\\\${/\${}
            eval "echo \"$line\""; 
    done < ${1}
}

var1="*replaced*"
var2="*not replaced*"

template file.tpl > result.txt

2
Це не безпечно, оскільки він буде виконувати заміну команд у шаблоні, якщо у них є провідна косої риси, наприклад\$(date)
Пітер Долберг,

1
Крім правильної точки Пітера: я пропоную вам використовувати while IFS= read -r line; doяк readкоманду, інакше ви знімете провідні та кінцеві пробіли з кожного рядка введення. Крім того, echoможе помилитися початок рядка для одного з його параметрів командного рядка, тому краще використовувати printf '%s\n'. Нарешті, безпечніше подвійне цитування ${1}.
mklement0

4

Я б запропонував використовувати щось на зразок Sigil : https://github.com/gliderlabs/sigil

Він зібраний в єдиний двійковий файл, тому його вкрай просто встановити в системах.

Тоді ви можете зробити простий однокласинник, як описано нижче:

cat my-file.conf.template | sigil -p $(env) > my-file.conf

Це набагато безпечніше, ніж evalі простіше, ніж використовувати регулярний вираз абоsed


2
Чудова відповідь! Це правильна система шаблонів, з якою набагато простіше працювати, ніж з іншими відповідями.
Ерфан

BTW, краще уникати catта використовувати <my-file.conf.templateзамість цього, щоб ви дали sigilсправжню обробку файлів замість FIFO.
Чарльз Даффі

2

Я знайшов цю тему, цікавившись те саме. Це мене надихнуло на це (обережно із задніми руками)

$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world

4
Баш стенографія для $(cat file)це$(< file)
Гленна Джекман

3
Мабуть, цей метод зіпсувався з розривами рядків, тобто мій файл відлунив все в одному рядку.
Артур Корензан

@ArthurCorenzan: Дійсно, розриви рядків замінюються пробілами. Щоб виправити це, вам доведеться скористатися, eval echo "\"$(cat FILE)\""але це все ще може бути невдалим у тому, що подвійні лапки у введенні будуть відкинуті.
mklement0

Як було зазначено в іншому місці: Використовуйте це лише у випадку, якщо ви повністю довіряєте або керуєте входом, оскільки заміни команд ( `…` або $(…)), вбудовані у вхід, дозволяють виконувати довільні команди за рахунок використання eval.
mklement0

2

Тут багато варіантів, але я подумав, що я кину міну на купу. Він заснований на perl, націлює лише змінні форми $ {...}, приймає файл для обробки як аргумент і виводить перетворений файл на stdout:

use Env;
Env::import();

while(<>) { $_ =~ s/(\${\w+})/$1/eeg; $text .= $_; }

print "$text";

Звичайно, я насправді не перламурова людина, тому легко може статися фатальний недолік (хоча для мене це працює).


1
Добре працює. Ви можете скинути Env::import();рядок - імпорт має на увазі use. Крім того, я пропоную не збирати спочатку весь вихід у пам'яті: просто використовуйте print;замість $text .= $_;всередині циклу та відкиньте команду пост-цикл print.
mklement0

1

Це можна зробити в самому файлі bash, якщо ви контролюєте формат файлу конфігурації. Вам просто потрібно створити (".") Файл конфігурації, а не підзавантажити його. Це гарантує, що змінні створюються в контексті поточної оболонки (і продовжують існувати), а не підпакеті (де змінна зникає при виході нижньої оболонки).

$ cat config.data
    export parm_jdbc=jdbc:db2://box7.co.uk:5000/INSTA
    export parm_user=pax
    export parm_pwd=never_you_mind

$ cat go.bash
    . config.data
    echo "JDBC string is " $parm_jdbc
    echo "Username is    " $parm_user
    echo "Password is    " $parm_pwd

$ bash go.bash
    JDBC string is  jdbc:db2://box7.co.uk:5000/INSTA
    Username is     pax
    Password is     never_you_mind

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

$ cat config.data
    parm_jdbc=jdbc:db2://box7.co.uk:5000/INSTA # JDBC URL
    parm_user=pax                              # user name
    parm_pwd=never_you_mind                    # password

$ cat go.bash
    cat config.data
        | sed 's/#.*$//'
        | sed 's/[ \t]*$//'
        | sed 's/^[ \t]*//'
        | grep -v '^$'
        | sed 's/^/export '
        >config.data-compiled
    . config.data-compiled
    echo "JDBC string is " $parm_jdbc
    echo "Username is    " $parm_user
    echo "Password is    " $parm_pwd

$ bash go.bash
    JDBC string is  jdbc:db2://box7.co.uk:5000/INSTA
    Username is     pax
    Password is     never_you_mind

У вашому конкретному випадку ви можете використовувати щось на кшталт:

$ cat config.data
    export p_p1=val1
    export p_p2=val2
$ cat go.bash
    . ./config.data
    echo "select * from dbtable where p1 = '$p_p1' and p2 like '$p_p2%' order by p1"
$ bash go.bash
    select * from dbtable where p1 = 'val1' and p2 like 'val2%' order by p1

Потім передайте вихід go.bash в MySQL і voila, сподіваємось, ви не знищите свою базу даних :-).


1
Вам не доведеться експортувати змінні з файлу config.data; достатньо лише їх встановити. Ви також, здається, не читаєте файл шаблону в будь-який момент. Або, можливо, файл шаблону модифікований і містить операції 'ехо' ... чи я щось пропускаю?
Джонатан Леффлер

1
Хороший момент щодо експорту, я роблю це за замовчуванням, щоб вони були доступні для передплатників, і це не заподіює шкоди, оскільки вони гинуть при виході з виходу. Файл 'template' - це сам скрипт, який має ехо-заяви. Не потрібно вводити третій файл - це в основному операція типу "mailmerge".
paxdiablo

1
"Сам скрипт з його ехо-заявами" не є шаблоном: це сценарій. Подумайте, різниця у читальності (та ремонтопридатності) між <xml type = "$ TYPE"> та ехою "<xml type =" '$ TYPE "">>
П'єр-Олів'є Варес

1
@Pierre, у моєму скрипті конфігурації немає ехо-заяв, вони просто експортуються, і я показав, як можна уникнути навіть цього з мінімальною кількістю попередньої обробки. Якщо ви говорите про заяву echo в інших моїх сценаріях (на кшталт go.bash), у вас неправильний кінець палички - вони не є частиною рішення, вони просто спосіб показати, що змінні є встановити правильно.
paxdiablo

1
@paxdiablo: Здається, ви просто забули питання: << Я хочу передати вихід файлу "шаблону" в MySQL >>. Тож використання шаблону - це питання, це не "неправильний кінець палички". Експорт змінних і вторячи їх в іншому сценарії просто не дає відповіді на питання , на всіх
П'єр-Олів'є Vares

0

Замість Perl редагуйте потенційно декілька файлів із резервними копіями.

  perl -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : ""/eg' \
    -i.orig \
    -p config/test/*

0

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

Вам потрібен кращий рендер. Вам потрібен найкращий рендер. Вам потрібен The Renderest!

Дано template.txt:

Привіт, {{person}}!

Виконати:

$ person = Bob ./render template.txt

І ви побачите вихід

Привіт, Бобе!

Запишіть його у файл, перенаправивши stdout у файл:

$ person = Bob ./render template.txt> rended.txt

І якщо у вас трапляється сценарій із змінними $ {}, які ви не хочете інтерполювати, Renderest вас охоплює, не роблячи нічого іншого!

Продовжуйте діставати свою копію за адресою https://github.com/relaxdiego/renderest

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.