views:

1151

answers:

4

I have a skeleton text file with placeholder strings:

blah blah blah
blah $PLACEHOLDER_1$
blah
$PLACEHOLDER_2$

and so on. Specific "form" of placeholders does not matter -- I may change them to whatever most comfortable for specific implementation.

I have a bash script where I know values for placeholders, and I need to generate a new file, with placeholders replaced with values.

#! /bin/sh
PLACEHOLDER_1 = 'string 1'
PLACEHOLDER_2 = 'multiline 
string 
2'
# TODO: Generate file output.txt from file output.template 
#       using placeholders above.

I may do this in multiple passes with sed, but it is not fun. I do not want to use Perl. I want to use textutils and bash itself only.

What is the best way to do what I want in a single pass?

+3  A: 

You can still use sed to do the replace in a single pass. You just need to specify all the replacements in one command.

eg.

sed -i 's/PLACEHOLDER_1/string 1/g;s/PLACEHOLDER_2/string 2/g' <file>
Adam Peck
Or: sed -i -e 's/../../g' -e 's/../../g' -e 's/../../g' ...
glenn jackman
+2  A: 

Building on the previous answer, perhaps use an array and compute the sed string?

#!/bin/sh
PLACEHOLDER[0]='string 1'
PLACEHOLDER[1]='multiline 
string 
2'

s="sed -i "
for(( i=0 ; i<${#PLACEHOLDER[*]} ; i++ )) ; do 
    echo ${PLACEHOLDER[$i]}
    s=$s"s/PLACEHOLDER_$i/${PLACEHOLDER[$i]}/g;"
done
echo $s

Seems to fail on the multi-line strings, though.

I don't know how portable Bash arrays might be. Above snippet tested with "GNU bash, version 3.2.17(1)-release (i386-apple-darwin9.0)"

David Poole
It's because you need quotes around parameters that have spaces in them. Change "sed -i" to "sed -i'" and after the loop added the other quote and then it should work.
Adam Peck
You say "tested with bash" but your shebang says "sh". Is sh linked to bash on your system? If so, does its behavior not depend on its name?
Dennis Williamson
sh is bash on my system but (oddly enough), it's not linked. I have /bin/sh and bin/bash, two different files. A quirk of the Mac, I suppose. I don't know if GNU bash changes its behavior if it's invoked as 'sh' vs being invoked as 'bash'.
David Poole
+2  A: 

Here's a way to do it without sed:

First, a slightly modified template file in which the placeholders are bash variables:

blah blah blah
blah $PLACEHOLDER_1
blah
$PLACEHOLDER_2

And the script:

#! /bin/sh
templatefile=output.template
outputfile=output.txt

PLACEHOLDER_1='string 1'

PLACEHOLDER_2='multiline 
string 
2'

# DONE: Generate file output.txt from file output.template 
#       using placeholders above.

echo "$(eval "echo \"$(cat $templatefile)\"")" > $outputfile

Here's a version that demonstrates a template contained within the script, but with a twist. It also demonstrates default values, which can also be used in the template file version, plus you can do math in the template:

#! /bin/sh
template='blah blah blah
blah $PLACEHOLDER_1
blah
${PLACEHOLDER_2:-"some text"} blah ${PLACEHOLDER_3:-"some
lines
of
text"} and the total is: $((${VAL_1:-0} + ${VAL_2:-0}))'
# default operands to zero (or 1) to prevent errors due to unset variables
outputfile=output.txt

# gears spin, bells ding, values for placeholders are computed

PLACEHOLDER_1='string 1'

PLACEHOLDER_2='multiline 
string 
2'

VAL_1=2

VAL_2=4

unset PLACEHOLDER_3 # so we can trigger one of the defaults

# Generate file output.txt from variable $template 
#       using placeholders above.

echo "$(eval "echo \"$template\"")" > $outputfile

No sed, no loops, just hairy nesting and quotes. I'm pretty sure all the quoting will protect you from malicious stuff in a template file, but I'm not going to guarantee it.

Dennis Williamson
You can use -e on the either echo to interpret backslash escapes such as \n in your template and/or placeholders.
Dennis Williamson
Nope. Not safe. Templates can include "$(command)" which could do bad stuff. You must know whether you can trust your template. You have been warned.
Dennis Williamson
A: 

My bash only solution:

TEMPLATE='
foo
$var1
bar
$var2'
eval "echo \"$TEMPLATE\""
Andor