如何在文本文件中替换$ {}占位符?

Dan*_*ane 137 bash command-line text-processing templating

我想将"模板"文件的输出传递给MySQL,该文件包含${dbName}散布的变量.什么是命令行实用程序来替换这些实例并将输出转储到标准输出?

use*_*ser 165

塞德!

给出template.txt:

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

我们只需要说:

sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.txt
Run Code Online (Sandbox Code Playgroud)

感谢Jonathan Leffler提示将多个-e参数传递给同一个sed调用.

  • 您可以将这两个sed命令合并为一个:sed -e"s /\$ {i}/1 /" - e"s /\$ {word}/dog /"; 这更有效率.您可能会遇到某些版本的sed可能遇到100个此类操作的问题(多年前的问题 - 可能仍然不是真的,但要注意HP-UX). (11认同)
  • 你也不需要`cat`.你只需要`sed -e"s /\$ {i}/1 /" - e"s /\$ {word}/dog /"template.text`. (8认同)
  • 小提示:如果给定示例中的"1"或"dog"包含美元符号,则必须使用反斜杠转义它(否则不会发生替换). (3认同)
  • 如果替换文本是密码怎么办?在这种情况下,`sed`将期望转义文本,这是一个麻烦. (3认同)
  • 要将结果写入文本文件,您可以使用 `sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text | 三通新文件` (3认同)

plo*_*ckc 154

更新

以下是来自yottatsa的类似问题的解决方案,该问题仅替换$ VAR或$ {VAR}等变量,并且是一个简短的单行

i=32 word=foo envsubst < template.txt
Run Code Online (Sandbox Code Playgroud)

当然,如果和你的话语在你的环境中,那么它就是

envsubst < template.txt
Run Code Online (Sandbox Code Playgroud)

在我的Mac上,它看起来像是作为gettextMacGPG2的一部分安装的

老答案

这是对mogsie在类似问题上的解决方案的改进,我的解决方案并不要求你使用双引号,mogsie's,但他是一个单行!

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
Run Code Online (Sandbox Code Playgroud)

这两个解决方案的优势在于你只能得到几种类型的shell扩展,这些扩展通常不会出现$((...)),`...`和$(...),尽管反斜杠一个转义字符在这里,但你不必担心解析有一个错误,它做多行就好了.

  • 如果你的envars没有导出,我发现裸的`envsubst`不起作用. (5认同)
  • @ToddiusZho:没有未导出的环境变量 - 正是导出使_shell_变量成为环境变量.`envsubst`,顾名思义,只识别_environment_变量,而不是_shell_变量.值得注意的是,`envsubst`是一个_GNU_实用程序,因此不会在所有平台上预安装或提供. (3认同)
  • 感谢"旧答案",因为它不仅允许变量,还有像$(ls -l)这样的shell命令 (3认同)
  • 也许另一种说法是envsubst只看到它自己的进程环境变量,所以你之前定义的"正常"shell变量(在不同的行上)不会被子进程继承,除非你"导出"它们.在我上面使用gettext的示例中,我正在通过bash机制修改继承的gettext环境,方法是将它们添加到我即将运行的命令中 (2认同)

gnu*_*nud 44

使用/bin/sh.创建一个设置变量的小shell脚本,然后使用shell本身解析模板.像这样(编辑以正确处理换行):

文件template.txt:

the number is ${i}
the word is ${word}
Run Code Online (Sandbox Code Playgroud)

文件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"
Run Code Online (Sandbox Code Playgroud)

输出:

#sh script.sh
the number is 1
the word is dog
Run Code Online (Sandbox Code Playgroud)

  • 这非常危险!输入中的所有`bash`命令都将被执行.如果模板是:"单词是; rm -rf $ HOME"你将丢失文件. (14认同)
  • @gnud信任文件足以存储它的内容并信任它足以执行它包含的任何内容之间存在差异. (4认同)
  • 要注意约束:(a)输入中的双引号被静静地丢弃,(b)`read`命令,如所写的,修剪每行的前导和尾随空格并'吃'``字符.,(c)只有在你完全信任或控制输入时才使用它,因为输入中嵌入的命令替换(`\`... \``或`$(...)`允许执行任意命令,因为使用了'eval`.最后,"echo"错误地为其命令行选项之一的行的开头错误. (3认同)
  • 为什么不:只读:读取线; 做eval echo"$ line"; 完成<./template.txt ??? 没有必要将整个文件读入内存,只需通过密集使用头部和尾部一次吐出一行.但'eval'没问题 - 除非模板包含shell引号等shell字符. (2认同)

Dan*_*ane 20

鉴于最近的兴趣,我再次考虑这个问题,我认为我最初想到的工具是m4autotools的宏处理器.因此,不是我最初指定的变量,而是使用:

$echo 'I am a DBNAME' | m4 -DDBNAME="database name"
Run Code Online (Sandbox Code Playgroud)


小智 12

template.txt

Variable 1 value: ${var1}
Variable 2 value: ${var2}
Run Code Online (Sandbox Code Playgroud)

data.sh

#!/usr/bin/env bash
declare var1="value 1"
declare var2="value 2"
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

parsed_file.txt

Variable 1 value: value 1
Variable 2 value: value 2
Run Code Online (Sandbox Code Playgroud)

  • 正如其他地方所指出的:仅当您完全信任或控制输入时才使用它,因为嵌入在输入中的命令替换(`\`...\` 或 `$(...)`)允许由于使用`eval`,以及由于使用 `source` 而直接执行 shell 代码。此外,输入中的双引号会被悄悄丢弃,并且 `echo` 可能会将一行的开头误认为是其命令行选项之一。 (2认同)

小智 10

这是我基于前一个答案的perl解决方案,替换环境变量:

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

  • 这很棒。不一定总是有perl,但是当您这样做时,这很简单明了。 (2认同)

mkl*_*nt0 10

这是一个强大的Bash功能 - 尽管使用eval- 应该是安全的使用.

${varName}输入文本中的所有变量引用都基于调用shell的变量进行扩展.

没有其他东西被扩展:既没有包含{...}(例如$varName)名称的变量引用,也没有包含命令替换($(...)和遗留语法`...`),也没有算术替换($((...))和遗留语法$[...]).

把a $当作文字来对待\它吧.例如:\${HOME}

请注意,输入仅通过stdin接受.

例:

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

功能源代码:

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
}
Run Code Online (Sandbox Code Playgroud)

该函数假定没有0x1,0x2,0x3,和0x4控制字符存在于输入,因为那些字符.在内部使用 - 因为函数处理文本,这应该是一个安全的假设.

  • 这是这里最好的答案之一.即使使用"eval",它也是非常安全的. (2认同)
  • 这个解决方案的一个好处是,它可以让您为缺少的变量 `${FOO:-bar}` 提供默认值,或者仅在设置时输出一些内容 - `${HOME+Home is ${HOME}}` 。我怀疑,通过一点扩展,它也可以返回缺少变量 `${FOO?Foo ismissing}` 的退出代码,但目前不会 https://www.tldp.org/LDP/abs/html/parameter-substitution。 html 有一个列表,如果有帮助的话 (2认同)

neu*_*242 8

创建rendertemplate.sh:

#!/usr/bin/env bash

eval "echo \"$(cat $1)\""
Run Code Online (Sandbox Code Playgroud)

而且template.tmpl:

Hello, ${WORLD}
Goodbye, ${CHEESE}
Run Code Online (Sandbox Code Playgroud)

渲染模板:

$ export WORLD=Foo
$ CHEESE=Bar ./rendertemplate.sh template.tmpl 
Hello, Foo
Goodbye, Bar
Run Code Online (Sandbox Code Playgroud)

  • 从安全的角度来看,这是个坏消息。如果您的模板包含`$(rm -rf ~)`,则您将其作为代码运行。 (4认同)
  • 这剥离了双引号字符串 (2认同)

Bea*_*sen 5

如果您愿意使用Perl,那将是我的建议.虽然可能有一些sed和/或AWK专家可能知道如何更容易地做到这一点.如果您有一个更复杂的映射,而不仅仅是dbName用于替换,那么您可以非常轻松地扩展它,但是您也可以将其放入标准的Perl脚本中.

perl -p -e 's/\$\{dbName\}/testdb/s' yourfile | mysql
Run Code Online (Sandbox Code Playgroud)

一个简短的Perl脚本,可以做一些稍微复杂的事情(处理多个键):

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

如果将上述脚本命名为replace-script,则可以按如下方式使用它:

replace-script < yourfile | mysql
Run Code Online (Sandbox Code Playgroud)

  • 使用perl比尝试使用shell要简单得多.花时间做这项工作,而不是尝试一些其他提到的基于shell的解决方案. (3认同)
  • 使用perl可以通过多种方式实现此目的,这取决于您希望如何复杂和/或安全.更复杂的例子可以在这里找到:http://www.perlmonks.org/?node_id = 718936 (2认同)

spu*_*fkc 5

我建议使用类似Sigil 的东西:https : //github.com/gliderlabs/sigil

它被编译为单个二进制文件,因此在系统上安装非常容易。

然后你可以做一个简单的单行如下:

cat my-file.conf.template | sigil -p $(env) > my-file.conf
Run Code Online (Sandbox Code Playgroud)

这比eval使用正则表达式或sed

  • 很好的答案!这是一个合适的模板系统,比其他答案更容易使用。 (2认同)

Apr*_*ori 5

这是一种让 shell 为您进行替换的方法,就好像文件的内容是在双引号之间键入的。

使用带有内容的 template.txt 示例:

The number is ${i}
The word is ${word}
Run Code Online (Sandbox Code Playgroud)

以下行将导致 shell 插入 template.txt 的内容并将结果写入标准输出。

i='1' word='dog' sh -c 'echo "'"$(cat template.txt)"'"'
Run Code Online (Sandbox Code Playgroud)

解释:

  • iword作为环境变量传递给sh.
  • sh 执行传递给它的字符串的内容。
  • 一个挨着一个写的字符串成为一个字符串,这个字符串是:
    • ' echo "' + " $(cat template.txt)" + ' "'
  • 由于替换在之间",“ $(cat template.txt)”成为 的输出cat template.txt
  • 所以执行的命令sh -c变为:
    • echo "The number is ${i}\nThe word is ${word}",
    • 其中iword是指定的环境变量。