224

I want to pipe the output of a "template" file into MySQL, the file having variables like ${dbName} interspersed. What is the command line utility to replace these instances and dump the output to standard output?

The input file is considered to be safe, but faulty substitution definitions could exist. Performing the replacement should avoid performing unintended code execution.

Dana the Sane
  • 14,762
  • 8
  • 58
  • 80

19 Answers19

266

Update

Here is a solution from yottatsa on a similar question that only does replacement for variables like $VAR or ${VAR}, and is a brief one-liner

i=32 word=foo envsubst < template.txt

Of course if i and word are in your environment, then it is just

envsubst < template.txt

On my Mac it looks like it was installed as part of gettext and from MacGPG2

Old Answer

Here is an improvement to the solution from mogsie on a similar question, my solution does not require you to escale double quotes, mogsie's does, but his is a one liner!

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

The power on these two solutions is that you only get a few types of shell expansions that don't occur normally $((...)), `...`, and $(...), though backslash is an escape character here, but you don't have to worry that the parsing has a bug, and it does multiple lines just fine.

Community
  • 1
  • 1
plockc
  • 4,241
  • 3
  • 18
  • 9
  • 10
    I'm finding the bare `envsubst` doesn't work if your envars aren't exported. – Toddius Zho Feb 11 '15 at 19:35
  • 8
    @ToddiusZho: There is no such thing as an environment variable that isn't exported - it is precisely exporting that makes a _shell_ variable an environment variable. `envsubst`, as its name suggests, only recognizes _environment_ variables, not _shell_ variables. It's also worth noting that `envsubst` is a _GNU_ utility, and therefore not preinstalled or available on all platforms. – mklement0 Mar 25 '15 at 18:11
  • 3
    Maybe another way to say is that envsubst only see's it's own process environment variables, so "normal" shell variables you might have defined earlier (on separate lines) are not inherited by child processes unless you "export" them. In my example usage of gettext above, I'm modifying the inherited gettext environment through a bash mechanism by prefixing them to the command I'm about to run – plockc Mar 25 '15 at 23:56
  • 1
    I have one string with $HOME in it, i found $HOME is worked as default shell to do, instead $HOME as my own /home/zw963, but, it seem like not support $(cat /etc/hostname) substitution, so it not complete match my own demand. – zw963 Jan 09 '16 at 16:06
  • I resolve this with export my_host=$(cat /etc/hostname) first, and than, use $my_host in string, it worked! – zw963 Jan 09 '16 at 16:15
  • 4
    Thanks for the "Old Answer", as it not only allows variables, but also a shell commands like $(ls -l) – Alek Jun 26 '16 at 15:40
  • Really good answer but keep in mind that it also replaces variables that are "excidentally" exported / exist as env vars and should not be replaced. – eventhorizon May 02 '18 at 08:58
  • amazingly envsubst seems to have been installed on my work windows7 machine as part of Git for Windows https://gitforwindows.org – simbo1905 Dec 04 '18 at 16:41
  • @eventhorizon You can limit variables to replace with the `SHELL-FORMAT` argument. Here's what I use to pass variables from a .env file: `env $(paste "$ENV_FILE") envsubst "$(printf '${%s} ' $(cut -d= -f1 "$ENV_FILE"))" /path/to/output/file`. FWIW, this should be the accepted answer. `envsubst` is the simplest and easiest solution. – imiric Jul 06 '19 at 09:49
  • `envsubst` doesn't work if variable values are large: `/usr/bin/envsubst: Argument list too long` – Martynas Jusevičius Feb 04 '23 at 13:50
244

Sed!

Given template.txt:

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

we just have to say:

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

Thanks to Jonathan Leffler for the tip to pass multiple -e arguments to the same sed invocation.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
user
  • 3,938
  • 1
  • 23
  • 19
  • 18
    You can combine those two sed commands into one: sed -e "s/\${i}/1/" -e "s/\${word}/dog/"; that is more efficient. You can run into problems with some versions of sed at maybe 100 such operations (problem from years ago - may not still be true, but beware HP-UX). – Jonathan Leffler Jan 06 '09 at 14:11
  • 3
    Small hint: if "1" or "dog" in the given example would contain a dollar symbol, you would have to escape it with a backslash (otherwise replacement does not occur). – MatthieuP Jan 10 '09 at 02:35
  • 9
    You also don't need the `cat`. All you need is `sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text`. – HardlyKnowEm Apr 02 '13 at 19:54
  • So how would you pipe that command into MySQL? I get this error: cannot open cat: No such file. Here's what I'm running:mysql --user=xxx --password=xxx < cat /var/www/db-create.sql | sed -e "s/\${database}/$database/" – Corgalore Sep 30 '13 at 16:57
  • 1
    Just to provide context: This answer is not a generic templating solution; it requires the set of placeholders to be known in advance. – mklement0 Mar 25 '15 at 19:30
  • 4
    What if the replacement text is a password? In this case, `sed` will expect an escaped text, which is a hassle. – jpbochi Jul 15 '15 at 14:28
  • 3
    To write the result to a textfile you can use `sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text | tee newFile` – rubiktubik Apr 12 '18 at 13:40
  • This is is a really good solution which replaces only the given variables. – eventhorizon May 02 '18 at 08:57
  • 1
    `sed -e "s|\${i}|1|" -e "s|\${word}|dog|" template.txt > out.txt` would put the result in a file. Notice I have replaced forward-slashes `/` with pipes `|` for better escaping special characters. – Junaid Qadir Shekhanzai Oct 16 '18 at 13:45
  • What if the text contains a backslash followed by an n - how to stop it from replacing those two characters with a single newline character? – Max Waterman Jul 24 '20 at 10:03
52

Use /bin/sh. Create a small shell script that sets the variables, and then parse the template using the shell itself. Like so (edit to handle newlines correctly):

File template.txt:

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

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

Output:

#sh script.sh
the number is 1
the word is dog
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
gnud
  • 77,584
  • 5
  • 64
  • 78
  • 2
    Why not just: while read line ; do eval echo "$line"; done < ./template.txt ??? There's no need to read the whole file into memory, only to spit it out one line at a time via intensive use of head and tail. But the 'eval' is OK - unless the template contains shell characters like back quotes. – Jonathan Leffler Jan 06 '09 at 14:18
  • 26
    This is very dangerous! All the `bash` command in the input will be executed. If the template is: "the words is; rm -rf $HOME" you'll loose files. – rzymek Apr 26 '13 at 13:36
  • 1
    @rzymek - remember, he wants to pipe this file directly to the database. So appearently, the input is trusted. – gnud Apr 26 '13 at 15:36
  • 4
    @gnud There is a difference between trusting a file enough to store it's contents and trusting it enough to execute anything it contains. – Mark Dec 22 '14 at 19:33
  • 3
    To note the constraints: (a) double quotes in the input are quietly discarded, (b) the `read` command, as written, trims leading and trailing whitespace from each line and 'eats' `\ ` chars., (c) only use this if you fully trust or control the input, because command substitutions (`\`…\` ` or `$(…)`) embedded in the input allow execution of arbitrary commands due to use of `eval`. Finally, there's a small chance that `echo` mistakes the beginning of a line for one of its command-line options. – mklement0 Mar 25 '15 at 19:35
  • 1
    Breaks down if parenthesis are used. ex: "db.enableSharding(${db})" won't work. – JohnC Jun 16 '16 at 21:53
24

I was thinking about this again, given the recent interest, and I think that the tool that I was originally thinking of was m4, the macro processor for autotools. So instead of the variable I originally specified, you'd use:

$echo 'I am a DBNAME' | m4 -DDBNAME="database name"
Dana the Sane
  • 14,762
  • 8
  • 58
  • 80
  • 1
    This solution has the fewest drawbacks of the answers here. Do you know of any way to replace ${DBNAME} instead of only DBNAME though? – Jack Davidson Jan 08 '19 at 21:46
  • @JackDavidson I would use `envsubst` for this simple variable replacement / templating usage, as mentioned in other answers. `m4` is a great tool, but it's a full-blown preprocessor with much more features and thus complexity which may not be needed if you simply want to replace some variables. – imiric Jul 06 '19 at 09:39
22

Create rendertemplate.sh:

#!/usr/bin/env bash

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

And template.tmpl:

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

Render the template:

$ export WORLD=Foo
$ CHEESE=Bar ./rendertemplate.sh template.tmpl 
Hello, Foo
Goodbye, Bar
neu242
  • 15,796
  • 20
  • 79
  • 114
16

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
  • 2
    As has been noted elsewhere: Only use this if you fully trust or control the input, because command substitutions (`\`…\` ` or `$(…)`) embedded in the input allow execution of arbitrary commands due to use of `eval`, and the direct execution of shell code due to use of `source`. Also, double quotes in the input are quietly discarded, and `echo` could mistake the beginning of a line for one of its command-line options. – mklement0 Mar 25 '15 at 19:45
  • Unfortunately, this strips all double quotes (") from the result file. Is there a way to do the same without removing the double quotes? – Ivaylo Slavov Mar 16 '16 at 08:56
  • I found what I was looking for here: http://stackoverflow.com/a/11050943/795158; I used envsubst. The difference is that the vars have to be exported which was OK with me. – Ivaylo Slavov Mar 16 '16 at 11:33
  • if text file contain "`" or "." ,substitude will failed. – shuiqiang Jul 16 '20 at 07:08
13

here's my solution with perl based on former answer, replaces environment variables:

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

Here's a robust Bash function that - despite using eval - should be safe to use.

All ${varName} variable references in the input text are expanded based on the calling shell's variables.

Nothing else is expanded: neither variable references whose names are not enclosed in {...} (such as $varName), nor command substitutions ($(...) and legacy syntax `...`), nor arithmetic substitutions ($((...)) and legacy syntax $[...]).

To treat a $ as a literal, \-escape it; e.g.:\${HOME}

Note that input is only accepted via stdin.

Example:

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

Function source code:

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
}

The function assumes that no 0x1, 0x2, 0x3, and 0x4 control characters are present in the input, because those chars. are used internally - since the function processes text, that should be a safe assumption.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 2
    This is one of the best answer here. Even with using `eval` it is pretty safe to use. – anubhava Sep 14 '17 at 20:15
  • 1
    This solution works with JSON files ! (escaping `"` properly!) – WBAR Nov 29 '17 at 11:17
  • 2
    A nice thing with this solution is it'll let you provide defaults for missing variables `${FOO:-bar}` or only output something if it's set - `${HOME+Home is ${HOME}}` . I suspect with a little extension it could also return exit codes for missing variables `${FOO?Foo is missing}` but doesn't currently https://www.tldp.org/LDP/abs/html/parameter-substitution.html has a list of these if that helps – Stuart Moore Aug 12 '19 at 09:45
  • 1
    Best answer here. All " and ' are fully escaped. Solution with only eval don't work for files with ' or " – jmcollin92 Mar 04 '22 at 07:14
6

I would suggest using something like Sigil: https://github.com/gliderlabs/sigil

It is compiled to a single binary, so it's extremely easy to install on systems.

Then you can do a simple one-liner like the following:

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

This is much safer than eval and easier then using regex or sed

spudfkc
  • 324
  • 3
  • 8
6

Here is a way to get the shell to do the substitution for you, as if the contents of the file were instead typed between double quotes.

Using the example of template.txt with contents:

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

The following line will cause the shell to interpolate the contents of template.txt and write the result to standard out.

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

Explanation:

  • i and word are passed as environment variables scopped to the execution of sh.
  • sh executes the contents of the string it is passed.
  • Strings written next to one another become one string, that string is:
    • 'echo "' + "$(cat template.txt)" + '"'
  • Since the substitution is between ", "$(cat template.txt)" becomes the output of cat template.txt.
  • So the command executed by sh -c becomes:
    • echo "The number is ${i}\nThe word is ${word}",
    • where i and word are the specified environment variables.
Apriori
  • 2,308
  • 15
  • 16
  • 1
    From a security perspective, this is bad news. If your template contains, say, `'$(rm -rf ~)'$(rm -rf ~)`, the literal quotes in the template file will match the ones you added before its expansion. – Charles Duffy May 15 '19 at 20:43
  • I don't the in-template quotes are matching the out-template quotes, I believe the shell is resolving the template and the in-terminal string independently (effective removing the quotes) then concatenating them. A version of the test that doesn't delete your home directory is ```'$(echo a)'$(echo a)```. It produces ```'a'a```. The main thing that's happening is that the first ```echo a``` inside the ```'``` is getting evaluated, which may not be what you expect since it's in ```'```, but is the same behavior as including ```'``` in a ```"``` quoted string. – Apriori May 16 '19 at 02:50
  • So, this is not secure in the sense that it allows the template author to have their code executed. However how the quotes are evaluated doesn't really affect security. Expanding anything a ```"```-quoted string (including ```$(...)```) is the point. – Apriori May 16 '19 at 02:50
  • Is that the point? I only see them asking for `${varname}`, not other, higher-security-risk expansions. – Charles Duffy May 16 '19 at 13:20
  • ...that said, I must differ (re: in-template and out-template quotes being able to match). When you put a single-quote in your string, you're splitting into a single-quoted string `echo "`, followed by a double-quoted string with the literal contetns of `template.txt`, followed by another literal string `"`, all concatenated into a single argument passed to `sh -c`. You're right that the `'` can't be matched (since it was consumed by the outer shell rather than passed to the inner one), but the `"` certainly can, so a template containing `Gotcha"; rm -rf ~; echo "` could be executed. – Charles Duffy May 16 '19 at 13:23
  • Fair point; so, the file can't contain literal ```"``` or it will mach ```"```in the shell. You could pass ```'echo "'"$(sed 's/"/\\"/g' template)"'"'``` instead of ```'echo "'"$(cat template.txt)"'"'``` to sh -c to get around that (but now were back to regex hacks). I'd need to think about it more, but there are probably still ways to *trick* it into do something unintuitive. – Apriori May 17 '19 at 17:53
  • Perhaps expanding ```$(...)``` is not the point of the original question; instead *I* was looking for a solution to that when I found this question, then provided an answer. Honestly, I don't have a use for the code execution part either, but replacing variables with a regexs is icky, and won't behave like shell variable in other ways, so I find a one-liner that has the shell perform interpolation to be fairly elegant. Unfortunately I don't know of a way call ```sh``` in a way that will expand variables, but won't execute code. – Apriori May 17 '19 at 17:54
  • If by `sh` you mean to imply availability on all baseline-POSIX systems, I don't know of something that does that either. OTOH, for those with GNU gettext, `envsubst` is a pretty handy tool. – Charles Duffy May 17 '19 at 19:00
  • can `env -i` as a prefix as well to cut off external environment – plockc Feb 19 '20 at 20:44
  • and `sh -c "echo \"$( – plockc Feb 19 '20 at 20:47
  • and if these might make it easier to find template and vars if there are many: `(env -i foo=bar sh -c "echo \"$(cat)\"") < template.txt` `cat template.txt | env -i foo=bar sh -c "echo \"$(cat)\""` – plockc Feb 19 '20 at 20:49
5

If you are open to using Perl, that would be my suggestion. Although there are probably some sed and/or AWK experts that probably know how to do this much easier. If you have a more complex mapping with more than just dbName for your replacements you could extend this pretty easily, but you might just as well put it into a standard Perl script at that point.

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

A short Perl script to do something slightly more complicated (handle multiple keys):

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

If you name the above script as replace-script, it could then be used as follows:

replace-script < yourfile | mysql
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Beau Simensen
  • 4,558
  • 3
  • 38
  • 55
  • 1
    Works for single variables, but how do I include 'or' for the others? – Dana the Sane Jan 06 '09 at 07:16
  • 2
    There are many ways you can do this with perl, all depending on how complicated and/or safe you wanted to do this. More complicated examples can be found here: http://www.perlmonks.org/?node_id=718936 – Beau Simensen Jan 06 '09 at 09:53
  • 3
    Using perl is so much cleaner than trying to use the shell. Spend the time to make this work rather than trying some of the other mentioned shell-based solutions. – jdigital Jan 06 '09 at 19:44
  • 1
    Recently had to tackle a similar issue. In the end I went with perl (envsubst looked promising for a bit, but it was too hard to control). – sfitts Jul 12 '14 at 01:51
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
  • 41
  • 1
  • 2
    This not safe since it will execute command substitutions in the template if they have a leading backslash e.g. `\$(date)` – Peter Dolberg Oct 21 '16 at 00:14
  • 1
    Aside from Peter's valid point: I suggest you use `while IFS= read -r line; do` as the `read` command, otherwise you'll strip leading and trailing whitespace from each input line. Also, `echo` could mistake the beginning of a line for one of its command-line options, so it's better to use `printf '%s\n'`. Finally, it's safer to double-quote `${1}`. – mklement0 Oct 21 '16 at 03:34
3

I found this thread while wondering the same thing. It inspired me to this (careful with the backticks)

$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
GlueC
  • 31
  • 1
  • 5
    A bash shorthand for `$(cat file)` is `$(< file)` – glenn jackman May 16 '11 at 12:58
  • 3
    Apparently this method mess up with the line breaks, i.e. my file got echoed all in one line. – Arthur Corenzan Sep 25 '14 at 19:04
  • @ArthurCorenzan: Indeed, line breaks are replaced with spaces. To fix that, you'd have to use `eval echo "\"$(cat FILE)\""` but that may still fall short in that double quotes in the input are discarded. – mklement0 Mar 25 '15 at 19:59
  • As has been noted elsewhere: Only use this if you fully trust or control the input, because command substitutions (`\`…\` ` or `$(…)`) embedded in the input allow execution of arbitrary commands due to use of `eval`. – mklement0 Mar 25 '15 at 20:09
  • to preserve line breaks and quotes: https://stackoverflow.com/a/17030906/10390714 – jetnet May 26 '22 at 19:03
2

Lots of choices here, but figured I'd toss mine on the heap. It is perl based, only targets variables of the form ${...}, takes the file to process as an argument and outputs the converted file on stdout:

use Env;
Env::import();

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

print "$text";

Of course I'm not really a perl person, so there could easily be a fatal flaw (works for me though).

sfitts
  • 938
  • 2
  • 9
  • 17
  • 1
    Works fine. You could drop the `Env::import();` line - importing is implied by `use`. Also, I suggest not building up the entire output in memory first: simply use `print;` instead of `$text .= $_;` inside the loop, and drop the post-loop `print` command. – mklement0 Mar 25 '15 at 21:34
1

It can be done in bash itself if you have control of the configuration file format. You just need to source (".") the configuration file rather than subshell it. That ensures the variables are created in the context of the current shell (and continue to exist) rather than the subshell (where the variable disappear when the subshell exits).

$ 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

If your config file cannot be a shell script, you can just 'compile' it before executing thus (the compilation depends on your input format).

$ 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

In your specific case, you could use something like:

$ 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

Then pipe the output of go.bash into MySQL and voila, hopefully you won't destroy your database :-).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    You don't have to export the variables from the config.data file; it is sufficient just to set them. You also don't seem to be reading the template file at any point. Or, perhaps, the template file is modified and contains the 'echo' operations...or am I missing something? – Jonathan Leffler Jan 06 '09 at 14:15
  • 1
    Good point on the exports, I do that by default so that they're available to subshells and it causes no harm since they die when go exits. The 'template' file is the script itself with it's echo statements. There's no need to introduce a third file - it's basically a mailmerge-type operation. – paxdiablo Jan 06 '09 at 23:41
  • 1
    The "script itself with it's echo statements" is not a template : it is a script. Think readibility (and maintainability) difference between and echo '' – Pierre-Olivier Vares Aug 06 '15 at 10:17
  • 1
    @Pierre, there are no echo statements in my config script, they're merely exports, and I've shown how you can avoid even that with a minimal amount of pre-processing. If you're talking about the echo statement in my other scripts (like `go.bash`), you've got the wrong end of the stick - they're not part of the solution, they're just a way of showing that the variables are being set correctly. – paxdiablo Aug 06 '15 at 12:12
  • 1
    @paxdiablo : It seems you just forgot the question : << I want to pipe the output of a "template" file into MySQL >>. So use of a template IS the question, it is not "the wrong end of the stick". Exporting variables and echoing them in another script just doesn't answer the question *at all* – Pierre-Olivier Vares Aug 07 '15 at 12:47
  • @Pierre, I suggest *you* re-read the question: What is the commandline utility to **replace these instances and dump the output to standard output?.** That's _exactly_ what my answer does, as do many others here, **including the one accepted by the OP.** But I suspect I'm not going to change your mind, and I'm darn sure you're not going to change mine :-) so we may as well leave it there. – paxdiablo Aug 07 '15 at 14:03
0

In place perl editing of potentially multiple files, with backups.

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

To me this is the easiest and most powerful solution, you can even include other templates using the same command eval echo "$(<template.txt):

Example with nested template

  1. create the template files, the variables are in regular bash syntax ${VARIABLE_NAME} or $VARIABLE_NAME

you have to escape special characters with \ in your templates otherwhise they will be interpreted by eval.

template.txt

Hello ${name}!
eval echo $(<nested-template.txt)

nested-template.txt

Nice to have you here ${name} :\)
  1. create source file

template.source

declare name=royman 
  1. parse the template
source template.source && eval echo "$(<template.txt)"
  1. the output
Hello royman!
Nice to have you here royman :)

roy man
  • 81
  • 8
-1

I created a shell templating script named shtpl. My shtpl uses a jinja-like syntax which, now that I use ansible a lot, I'm pretty familiar with:

$ cat /tmp/test
{{ aux=4 }}
{{ myarray=( a b c d ) }}
{{ A_RANDOM=$RANDOM }}
$A_RANDOM
{% if $(( $A_RANDOM%2 )) == 0 %}
$A_RANDOM is even
{% else %}
$A_RANDOM is odd
{% endif %}
{% if $(( $A_RANDOM%2 )) == 0 %}
{% for n in 1 2 3 $aux %}
\$myarray[$((n-1))]: ${myarray[$((n-1))]}
/etc/passwd field #$n: $(grep $USER /etc/passwd | cut -d: -f$n)
{% endfor %}
{% else %}
{% for n in {1..4} %}
\$myarray[$((n-1))]: ${myarray[$((n-1))]}
/etc/group field #$n: $(grep ^$USER /etc/group | cut -d: -f$n)
{% endfor %}
{% endif %}


$ ./shtpl < /tmp/test
6535
6535 is odd
$myarray[0]: a
/etc/group field #1: myusername
$myarray[1]: b
/etc/group field #2: x
$myarray[2]: c
/etc/group field #3: 1001
$myarray[3]: d
/etc/group field #4: 

More info on my github

olopopo
  • 296
  • 1
  • 8
-1

Use envsubst

Please don't use anything else (ie. don't eval)

Simple usage:

export name="Dana the Sane"

Create template with placeholder (template.txt):

Hello ${name}

To replace placeholder variables SAFELY. Feed the template to stdin of envsubst.

envsubst < template.txt
Hello Dana the Sane

Note, that command envsubst < template.txt > template.txt wont work. Redirection will zero the output file before reading it.

ocodo
  • 29,401
  • 18
  • 105
  • 117
  • A solution with envsubst is now the accepted answer. – Dana the Sane Sep 25 '22 at 15:25
  • `eval` is the **recommended** approach to some use cases in the bash docs. – Eugen Rieck Jun 01 '23 at 07:11
  • And? @EugenRieck it is not the best approach as it is insecure. Don't do it. – ocodo Jul 02 '23 at 10:17
  • It is insecure only if used insecurely. Of course a developer with little experience might be tempted to say it is "just insecure". BASH docs recommend it, but you might lack the skills to use it securely. In this case `envsubst` is all that remains for you - if the only tool you can handle is a hammer, all things look like nails. – Eugen Rieck Jul 03 '23 at 22:12
  • Bash docs do not explicitly recommend eval over envsubst. Perhaps as a junior developer you would assume a mention of eval is a recommendation. Maybe you can cite this recommendation for all of us. – ocodo Jul 04 '23 at 03:22