Как заменить $ {} заполнителей в текстовом файле?

164

Я хочу направить вывод файла «шаблона» в MySQL, файл, имеющий переменные, такие как ${dbName}вкрапленные. Что такое утилита командной строки для замены этих экземпляров и вывода вывода на стандартный вывод?

Дана вменяемая
источник

Ответы:

192

Сед !

Данный шаблон.txt:

Число: $ {i}
Слово это $ {слово}

мы просто должны сказать:

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
Спасибо Джонатан, именно то, что я искал.
Дана Саня
3
Небольшая подсказка: если «1» или «собака» в данном примере будет содержать символ доллара, вам придется избегать его с обратной косой чертой (в противном случае замена не произойдет).
MatthieuP
9
Вам также не нужно cat. Все, что вам нужно, это sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text.
HardlyKnowEm
3
Что делать, если текст замены является паролем? В этом случае sedбудет ожидать экранированный текст, который является проблемой.
jpbochi
179

Обновить

Вот решение от yottatsa по аналогичному вопросу, которое выполняет замену только таких переменных, как $ 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

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

plockc
источник
6
Я считаю, что голая envsubstне работает, если ваши envars не экспортируются.
Тоддиус Жо
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. Создайте небольшой скрипт оболочки, который устанавливает переменные, а затем проанализируйте шаблон с помощью самой оболочки. Вот так (отредактируйте, чтобы правильно обрабатывать новые строки):

Файл template.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
gnud
источник
2
Почему не просто: пока читаешь строку; do eval echo "$ line"; сделано <./template.txt ??? Нет необходимости читать весь файл в память, а только выплевывать его по одной строке за счет интенсивного использования головы и хвоста. Но с 'eval' все в порядке - если шаблон не содержит символы оболочки, такие как обратные кавычки.
Джонатан Леффлер
16
Это очень опасно! Все bashкоманды на входе будут выполнены. Если шаблон: «the words is; rm -rf $ HOME», вы потеряете файлы.
rzymek
1
@rzymek - помните, он хочет передать этот файл напрямую в базу данных. Таким образом, по-видимому, вход является доверенным.
gnud
4
@gnud Существует разница между доверием файла, достаточного для хранения его содержимого, и доверием, достаточным для выполнения всего, что в нем содержится.
Марк
3
Чтобы отметить ограничения: (а) двойные кавычки во входных данных незаметно отбрасываются, (б) readкоманда, как написано, обрезает начальные и конечные пробелы в каждой строке и «ест» \ символы. (В) используйте это, только если вы полностью доверять или контролировать ввод, потому что подстановки команд ( `…` или $(…)), встроенные во ввод, позволяют выполнять произвольные команды из-за использования 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
ChaPuZ
источник
1
Как уже было отмечено в другом месте: используйте это, только если вы полностью доверяете или управляете вводом, потому что подстановки команд ( `…` или $(…)), встроенные во ввод, позволяют выполнять произвольные команды из-за использования evalи непосредственное выполнение кода оболочки из-за использования source. Кроме того, двойные кавычки во входных данных незаметно отбрасываются, и они echoмогут ошибочно принять начало строки за один из параметров командной строки.
mklement0
К сожалению, это удаляет все двойные кавычки (") из файла результатов. Есть ли способ сделать то же самое, не удаляя двойные кавычки?
Ивайло Славов
Я нашел то, что искал здесь: stackoverflow.com/a/11050943/795158 ; Я использовал envsubst. Разница в том, что нужно экспортировать переменные, что было хорошо для меня.
Ивайло Славов
если текстовый файл содержит "` "или". " , замена будет неудачной.
Шуйцян
12

Вот надежная функция Bash, которая, несмотря на использованиеeval следует использовать безопасно.

Все ${varName}ссылки на переменные во входном тексте расширяются на основе переменных вызывающей оболочки.

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

Для того, чтобы лечить , $как буквальные, \экранирующий его; например:\${HOME}

Обратите внимание, что ввод принимается только через стандартный ввод .

Пример:

$ 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управляющие символы присутствуют на входе, потому что эти символы. используются внутренне - так как функция обрабатывает текст , это должно быть безопасным допущением.

mklement0
источник
2
Это один из лучших ответов здесь. Даже с использованием evalэто довольно безопасно для использования.
анубхава
1
Это решение работает с файлами JSON! ( "правильно убегая !)
WBAR
2
Приятно, что в этом решении вы можете ${FOO:-bar}указать значения по умолчанию для отсутствующих переменных или вывести что-то, только если оно установлено - ${HOME+Home is ${HOME}}. Я подозреваю, что с небольшим расширением он может также возвращать коды завершения для отсутствующих переменных, ${FOO?Foo is missing}но в настоящее время tldp.org/LDP/abs/html/parameter-substitution.html не содержит их список, если это поможет
Stuart Moore
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
neu242
источник
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
Это круто. Не всегда есть Perl, но когда вы делаете, это просто и прямо.
Аарон Макмиллин
5

Если вы открыты для использования Perl , это мое предложение. Хотя, вероятно, есть некоторые специалисты по sed и / или 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, он может быть использован следующим образом:

replace-script < yourfile | mysql
Бо Сименсен
источник
1
Работает для отдельных переменных, но как включить «или» для других?
Дана Саня
2
Есть много способов сделать это с помощью Perl, все в зависимости от того, насколько сложным и / или безопасным вы хотели это сделать. Более сложные примеры можно найти здесь: perlmonks.org/?node_id=718936
Бо Сименсен,
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}, а не другие, более рискованные расширения безопасности.
Чарльз Даффи
... тем не менее, я должен отличаться (цитаты в шаблоне и вне шаблона в состоянии совпадения). Когда вы помещаете в строку одинарную кавычку, вы 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
user976433
источник
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

spudfkc
источник
2
Отличный ответ! Это правильная система шаблонов, с которой гораздо проще работать, чем с другими ответами.
Эрфан
Кстати, лучше избегать catи использовать <my-file.conf.templateвместо этого, чтобы вы дали sigilреальный дескриптор файла вместо FIFO.
Чарльз Даффи
2

Я нашел эту тему, хотя удивляюсь тому же самому. Это вдохновило меня на это (осторожно с галочкой)

$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world
GlueC
источник
4
Баш стенография для $(cat file)это$(< file)
Гленна Джекман
3
Видимо, этот метод портит разрывы строк, т.е. мой файл отображается в одной строке.
Артур Корензан,
@ArthurCorenzan: Действительно, разрывы строк заменяются пробелами. Чтобы исправить это, вы должны будете использовать, eval echo "\"$(cat FILE)\""но это может все еще не соответствовать тому, что двойные кавычки во входных данных отбрасываются.
mklement0
Как уже было отмечено в другом месте: используйте это, только если вы полностью доверяете или контролируете вход, потому что подстановки команд ( `…` или $(…)), встроенные во вход, позволяют выполнять произвольные команды из-за использования eval.
mklement0
2

Здесь много вариантов, но я решил бросить свой в кучу. Это основано на Perl, только целевые переменные вида $ {...}, принимает файл для обработки в качестве аргумента и выводит преобразованный файл на стандартный вывод:

use Env;
Env::import();

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

print "$text";

Конечно, я на самом деле не Perl человек, поэтому легко может быть фатальный недостаток (работает для меня, хотя).

sfitts
источник
1
Работает отлично. Вы можете оставить Env::import();строку - импорт подразумевается use. Кроме того, я предлагаю не создавать сначала весь вывод в памяти: просто используйте print;вместо $text .= $_;цикла и удалите команду post-loop 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 и вуаля, надеюсь, вы не уничтожите свою базу данных :-).

paxdiablo
источник
1
Вам не нужно экспортировать переменные из файла config.data; достаточно просто установить их. Вы также, кажется, не читаете файл шаблона в любой момент. Или, возможно, файл шаблона изменен и содержит операции 'echo' ... или я что-то упустил?
Джонатан Леффлер
1
Хорошие замечания по экспорту, я делаю это по умолчанию, чтобы они были доступны для подоболочек, и это не причинит вреда, поскольку они умирают, когда уходят из системы. Файл 'template' - это сам скрипт с его эхо-операторами. Нет необходимости вводить третий файл - это в основном операция типа mailmerge.
paxdiablo
1
«Сам скрипт со своими эхо-операторами» - это не шаблон, это скрипт. Подумайте о разнице в удобочитаемости (и удобстве сопровождения) между <xml type = "$ TYPE"> и echo '<xml type = "' $ TYPE '">'
Пьер-Оливье Варес
1
@Pierre, в моем скрипте конфигурации нет выражений echo, они просто экспортируются, и я показал, как этого можно избежать даже при минимальной предварительной обработке. Если вы говорите об утверждении echo в других моих скриптах (например,go.bash ), у вас неправильный конец - они не являются частью решения, они просто показывают, что переменные установить правильно.
paxdiablo
1
@paxdiablo: Кажется, вы просто забыли вопрос: «Я хочу передать вывод файла шаблона в MySQL >>. Поэтому вопрос использования шаблона - это не «неправильный конец флешки». Экспорт переменных и вторя их в другом сценарии просто не дает ответа на вопрос , на всех
Пьер-Оливье Вареше
0

Вместо Perl редактирование потенциально нескольких файлов с резервными копиями.

  perl -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : ""/eg' \
    -i.orig \
    -p config/test/*
joehep
источник
0

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

Вам нужен лучший рендер. Вам нужен лучший рендер. Вам нужен Renderest!

Данный шаблон.txt:

Здравствуйте, {{person}}!

Бегать:

$ person = Bob ./render template.txt

И вы увидите выход

Привет, Боб!

Запишите его в файл, перенаправив стандартный вывод в файл:

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

И если вам случится рендерить скрипт, в котором есть переменные $ {}, которые вы не хотите интерполировать, Renderest поможет вам без необходимости делать что-либо еще!

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

Марк Маглана
источник