テキストファイルの$ {}プレースホルダーを置き換える方法は?


163

「テンプレート」ファイルの出力をMySQLにパイプします。このファイルには、${dbName}散在するような変数があります。これらのインスタンスを置き換えて出力を標準出力にダンプするコマンドラインユーティリティとは何ですか?

回答:


192

セド

与えられたtemplate.txt:

数は$ {i}です
単語は$ {word}です

私たちはただ言う必要があります:

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

複数の-e引数を同じsed呼び出しに渡すヒントを提供してくれたJonathan Lefflerに感謝します。


13
これら2つのsedコマンドを1つに結合できます。sed -e "s / \ $ {i} / 1 /" -e "s / \ $ {word} / dog /"; それはより効率的です。sedの一部のバージョンで、おそらく100のような操作で問題が発生する可能性があります(数年前の問題-まだ真実ではないかもしれませんが、HP-UXに注意してください)。
ジョナサンレフラー、

1
ジョナサン、私が探していたものに感謝します。
Dana the Sane

3
小さなヒント:例の「1」または「dog」にドル記号が含まれる場合、バックスラッシュでエスケープする必要があります(それ以外の場合、置換は行われません)。
MatthieuP

9
また、必要ありませんcat。必要なのはsed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text
HardlyKnowEm 2013

3
置換テキストがパスワードの場合はどうなりますか?この場合、sedエスケープされたテキストが必要ですが、これは面倒です。
jpbochi 2015

177

更新

$ VARや$ {VAR}などの変数の置換のみを行う同様の質問に対するyottatsaの解決策は次のとおりです。簡単なワンライナーです。

i=32 word=foo envsubst < template.txt

もちろん、言葉があなたの環境にあれば、それはちょうどです

envsubst < template.txt

私のMacでは、gettextの一部として、またMacGPG2からインストールされたようです。

古い答え

これは、同様の質問に対するmogsieからのソリューションの改善です。私のソリューションでは、二重引用符をescaleする必要はありませんが、mogsieはそうですが、彼は1つのライナーです!

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

これら二つの解決策の電源はバックスラッシュがにもかかわらずあなただけ、(...)が発生していないシェル拡張のいくつかのタイプを、通常$((...))、 `...`取得し、$ということですここではエスケープ文字を使用していますが、解析にバグがあることを心配する必要はありません。また、複数行を正常に実行します。


6
envsubstenvarsがエクスポートされていないと、ベアが機能しないことがわかりました。
Toddius Zho、2015

4
@ToddiusZho:エクスポートされない環境変数のようなものはありません- シェル変数を環境変数にするのは正確にエクスポートすることです。envsubstは、その名前が示すように、環境変数のみを認識し、シェル変数は認識しません。これenvsubstGNUユーティリティであるため、プリインストールされていないか、すべてのプラットフォームで利用できるわけではないことにも注意してください。
mklement0 2015年

2
別の言い方をすると、envsubstはそれ自体のプロセス環境変数のみを参照するため、以前に(別の行で)定義した「通常の」シェル変数は、「エクスポート」しない限り、子プロセスに継承されません。上記のgettextの私の使用例では、私は実行するのに約だコマンドにそれらを付けることによって、bashの機構を介して継承されたgettextの環境を変更しています
plockc

1
$ HOMEが含まれている文字列が1つあります。$ HOMEがデフォルトのシェルとして機能し、代わりに$ HOMEが自分の/ home / zw963として機能していることがわかりましたが、$(cat / etc / hostname)置換をサポートしていないようです。ですから、私の要求に完全には一致しません。
zw963 2016年

3
変数だけでなく、$(ls -l)などのシェルコマンドも使用できるため、「古い回答」をありがとう
Alek

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

2
なぜだけではない:行を読みながら; eval echo "$ line"; 完了<./template.txt ??? ファイル全体をメモリに読み込む必要はありません。ヘッドとテールを集中的に使用して、一度に1行ずつ吐き出すだけです。ただし、「eval」は問題ありません-テンプレートに逆引用符のようなシェル文字が含まれていない限り
ジョナサンレフラー、

16
これはとても危険です!bash入力内のすべてのコマンドが実行されます。テンプレートが「words is; rm -rf $ HOME」の場合、ファイルが失われます。
rzymek 2013

1
@rzymek-パイプして、このファイルを直接データベースに送りたいと思っています。そのため、入力は信頼できるようです。
gnud 2013

4
@gnudファイルの内容を保存するのに十分なほどファイルを信頼することと、ファイルに含まれるものを実行するのに十分なだけファイルを信頼することには違いがあります。
マーク・

3
制約に注意してください:(a)入力内の二重引用符は静かに破棄されます、(b)read記述されているように、コマンドは各行から先頭と末尾の空白を削除し、文字を「食べます」\ 、(c)これを完全に使用する場合にのみ使用します入力に埋め込まれたコマンド置換(`…` または$(…))により、の使用により任意のコマンドを実行できるため、入力を信頼または制御しますeval。最後に、echoコマンドラインオプションの1つと、行の先頭を間違える可能性が少しあります。
mklement0 2015年

23

私は最近の関心を考慮して、もう一度これについて考えていました。私が最初に考えていたツールはm4、autotoolsのマクロプロセッサだったと思います。したがって、最初に指定した変数の代わりに、次を使用します。

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

1
このソリューションには、ここでの回答の欠点がほとんどありません。DBNAMEだけでなく、$ {DBNAME}を置き換える方法を知っていますか?
ジャックデイビッドソン、

@JackDavidson envsubst他の回答で述べたように、私はこの単純な変数置換/テンプレートの使用法に使用します。m4は優れたツールですが、機能が充実した本格的なプリプロセッサであり、単純にいくつかの変数を置き換えたいだけの場合は、複雑さは必要ありません。
imiric

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行の先頭をコマンドラインオプションの1つと間違える可能性があります。
mklement0 2015年

残念ながら、これにより結果ファイルからすべての二重引用符( ")が削除されます。二重引用符を削除せずに同じことを行う方法はありますか?
Ivaylo Slavov

ここで探していたものが見つかりました:stackoverflow.com/a/11050943/795158 ; envsubstを使用しました。違いは、変数をエクスポートする必要があることですが、私には問題ありませんでした。
Ivaylo Slavov

テキストファイルに「 `」または「。」が含まれている場合 、実質は失敗します。
水曜

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
}

関数はという前提0x10x20x3、および0x4制御文字を、それらの文字から、入力に存在します。内部で使用されます -関数はテキストを処理するため、これは安全な前提です。


2
これは、ここでの最良の答えの1つです。使用しevalてもかなり安全です。
anubhava 2017

1
このソリューションはJSONファイルで動作します!("適切にエスケープします!)
WBAR 2017年

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

2
これにより、二重引用符で囲まれた文字列が
削除

試してみました:eval "echo $(cat $ 1)"-引用符なしで、私にとってはうまくいきました。
access_granted

2
セキュリティの観点からは、これは悪いニュースです。テンプレートにが含まれている場合は$(rm -rf ~)、それをコードとして実行しています。
Charles Duffy、

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を使用する必要はありませんが、使用する場合、これは単純で簡単です。
アーロンマクミリン2017

5

Perlを使用することにオープンであれば、それが私の提案です。おそらくこれをもっと簡単に行う方法を知っているsedAWKのエキスパートがおそらくいるでしょう。置換用の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
単一の変数で機能しますが、他の変数に「or」を含めるにはどうすればよいですか?
Dana the Sane

2
perlでこれを行う方法はたくさんありますが、どれほど複雑で安全なのかによって異なります。より複雑な例はここにあります:perlmonks.org/?node_id
Beau

3
perlを使用する方が、シェルを使用するよりもずっとクリーンです。言及されている他のシェルベースのソリューションを試すのではなく、時間をかけてこの作業を行ってください。
jdigital 2009年

1
最近同様の問題に取り組む必要がありました。最終的に私はperlを使いました(envsubstは少し有望に見えましたが、制御するのが難しすぎました)。
14

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環境変数がの実行にscoppedとして渡されますsh
  • sh 渡された文字列の内容を実行します。
  • 隣り合って書かれた文字列は1つの文字列になり、その文字列は次のようになります。
    • ' 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 ~)、テンプレートファイルのリテラルの引用符は、展開前に追加した引用符と一致します。
Charles Duffy、

テンプレート内の引用符がテンプレート外の引用符と一致していないと思います。シェルがテンプレートとターミナル内の文字列を個別に解決して(効果的に引用符を削除して)連結していると思います。ホームディレクトリを削除しないテストのバージョンは'$(echo a)'$(echo a)です。生産し'a'aます。発生している主なことは、最初echo a'が評価されていることです。これはにあるため、期待どおりではないかもしれませんが、引用文字列に'含めるのと同じ動作です。'"
2019年

したがって、テンプレートの作成者がコードを実行できるようにするという意味では、これは安全ではありません。ただし、見積もりの​​評価方法は、実際にはセキュリティに影響しません。"引用符で囲まれた文字列(を含む$(...))を展開することがポイントです。
2019年

それがポイントですか?私は彼らが求めて見${varname}ていない他、高セキュリティリスクの拡大、。
Charles Duffy、

...とはいえ、私は異なる必要があります(再:テンプレート内とテンプレート外の引用が一致できること)。文字列に単一引用符を入れると、単一引用符付きの文字列に分割されecho "、その後に二重引用符付きの文字列がtemplate.txt続く文字列が続き、さらに別のリテラル文字列が続き"、すべてがに渡される単一の引数に連結されsh -cます。'一致させることはできませんが(内側のシェルに渡されるのではなく、外側のシェルによって消費されるため)、"確かに一致するため、テンプレートを含むテンプレートをGotcha"; rm -rf ~; echo "実行できます。
Charles Duffy

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)
Peter Dolberg

1
Peterの有効な点は別while IFS= read -r line; doとして、readコマンドとして使用することをお勧めします。そうしないと、各入力行から先頭と末尾の空白が削除されます。また、echo行の先頭をコマンドラインオプションの1つと間違える可能性があるため、を使用することをお勧めします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
正解です。これは適切なテンプレートシステムであり、他の回答よりもはるかに簡単に操作できます。
Erfan

ところで、FIFOの代わりに実際のファイルハンドルを与えるために、代わりに回避catして使用<my-file.conf.templateすることをお勧めしますsigil
Charles Duffy

2

同じことを考えながらこのスレッドを見つけました。それは私にインスピレーションを与えました(バッククォートに注意)

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

4
のbash省略形$(cat file)$(< file)
glenn jackman、

3
どうやら、このメソッドは改行を混乱させます。つまり、ファイルがすべて1行でエコーされます。
アーサーCorenzan

@ArthurCorenzan:実際、改行はスペースに置き換えられます。これを修正するには、使用する必要eval echo "\"$(cat FILE)\""がありますが、それでも入力内の二重引用符が破棄されるので不十分な場合があります。
mklement0

他の場所で述べたように、入力に完全に信頼または制御する場合にのみ使用してください。入力に埋め込まれたコマンド置換(`…` または$(…))により、eval
mklement0

2

ここにはたくさんの選択肢がありますが、私は鉱山を山積みにすると思いました。これはperlベースで、$ {...}形式の変数のみを対象とし、処理するファイルを引数として取り、変換されたファイルをstdoutに出力します。

use Env;
Env::import();

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

print "$text";

もちろん、私は実際にはperlの人ではないので、致命的な欠陥が簡単に発生する可能性があります(私にとってはうまくいきます)。


1
正常に動作します。あなたはEnv::import();行をドロップすることができます-インポートは暗示されていuseます。また、最初にメモリ内の出力全体を構築しないことをお勧めします。ループprint;$text .= $_;内部ではなく単に使用し、ポストループprintコマンドを削除します。
mklement0 2015年

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
エクスポートの良い点は、サブシェルで使用できるようにデフォルトでそれを行うことで、終了時に終了するので害はありません。「テンプレート」ファイルは、エコーステートメントを含むスクリプト自体です。3番目のファイルを導入する必要はありません-それは基本的には差し込みタイプの操作です。
paxdiablo 2009年

1
「echoステートメントを含むスクリプト自体」はテンプレートではなく、スクリプトです。<xml type = "$ TYPE">とecho '<xml type = "' $ TYPE '">'の読みやすさ(および保守性)の違いについて考えてください
Pierre-Olivier Vares

1
@Pierre、私の構成スクリプトにはエコーステートメントがありません。それらは単なるエクスポートであり、最小限の前処理でそれを回避する方法を示しました。他のスクリプト(などgo.bash)でechoステートメントについて話している場合、スティックの終わりが間違っています-それらは解決策の一部ではなく、変数が存在していることを示すための単なる方法です正しく設定してください。
paxdiablo 2015

1
@paxdiablo:質問を忘れたようです:<<「テンプレート」ファイルの出力をMySQLにパイプしたい>>。したがって、テンプレートの使用は問題です。それは「棒の間違った終わり」ではありません。変数をエクスポートして別のスクリプトでエコーしても、質問にまったく
Pierre-Olivier Vares

0

複数のファイルがバックアップされている可能性のあるperl編集。

  perl -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : ""/eg' \
    -i.orig \
    -p config/test/*
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.