4

I am attempting to use Bash in order to run a form of an install process. During this process, a configuration file is copied and certain values are replaced inside of it. Such a config can be found below:

server {
    listen 80;
    root ${INSTALLPATH};
    server_name ${SITEURL};

    client_max_body_size 20m;
    client_body_timeout 120s;

    location / {
        try_files /public/router.php =404;
        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
        fastcgi_pass ${PHPSERV};
        fastcgi_index router.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }

    location /assets {
        try_files /app/$uri =404;
    }

}

#Enables HTTPS access
#This requires you to install certificates and is not enabled by default
#If you wish to enable HTTPS, uncomment (remove the #s) from the below lines
#And change the ssl_certificate and ssl_certificate_key to point to the     correct
#certificates.

#server {
#    listen 443;
#    root ${INSTALLPATH};
#    server_name ${SITEURL};
#
#    ssl on;
#    ssl_certificate     /etc/nginx/ssl/site.crt;
#    ssl_certificate_key /etc/nginx/ssl/site.key;
#
#    location / {
#        try_files /public/router.php =404;
#        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
#        fastcgi_pass ${PHPSERV};
#        fastcgi_index router.php;
#        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#        include /etc/nginx/fastcgi_params;
#    }
#
#    location /assets {
#        try_files /app/$uri =404;
#    }
#
#}

I have been finding that most examples stem off the use of eval, and I have tried using that to do this replacement, however it appears that the file here does not properly expand, and bash tries to execute some stuff and can't.

Currently, I have this

INSTALLPATH="/var/www/html/mycustomsite/"
PHPSERV="127.0.0.1:9000"
SITEURL="example.com"

while read -r line; do
    eval echo -e "${line}"
done < template

However, this does not properly replace the values that are declared, nor does it generate the file correctly. It loses for example any line that starts with # and tries to execute others (along with losing some spacing).

What would be the correct way to do this, using just Bash and commands available on most Linux systems?

Joshua Taylor
  • 75
  • 1
  • 8
  • related http://stackoverflow.com/questions/29487986/sed-find-replace-using-bash-arrays-fails/29488120#29488120 – Nikos M. May 29 '15 at 22:14
  • Dynamically is what I was partly going for, since I would like to avoid editing that replacement code as little as possible in futures, but that would fit what I am needing – Joshua Taylor May 29 '15 at 22:17
  • you can adapt the template tag format easily, this is what is used by default but easily changed in one line – Nikos M. May 29 '15 at 22:18
  • i find it best to declare the tags the template uses (since i know what template i load) to avoid replacements that would not be needed – Nikos M. May 29 '15 at 22:20
  • 1
    On-topic: http://mywiki.wooledge.org/TemplateFiles – Charles Duffy May 29 '15 at 22:40
  • @CharlesDuffy, yes, this could efficiently be done by using `sed`, but I like my *inline document* way in my [*compatible answer*](http://stackoverflow.com/a/30539879/1765658)... – F. Hauri - Give Up GitHub May 29 '15 at 23:01
  • @F.Hauri, the linked wiki page does not restrict itself to `sed`, but also suggests the heredoc approach (and has for years; at present, last edit is late 2010). That said, the `sed` approach is certainly preferable from a security perspective (if one doesn't trust the template author to run arbitrary code). – Charles Duffy May 29 '15 at 23:13
  • @CharlesDuffy You're right! I'm not really ok with my 1st answer. I've edited them for adding a note about security and a sed version! – F. Hauri - Give Up GitHub May 29 '15 at 23:22

1 Answers1

6

Security tips

This don't take care of security issues! Using eval is evil!

The compatible answer is not better!

Of course, you have to be confident about the content of your template!!

If else, try using ! (see my last answer)

Quick way only!:

Under you can simply:

eval "INSTALLPATH='/somepath/somewhere' SITEURL='example.com' PHPSERV='127.0.0.1:9000'; echo \"$(<template)\""

or

eval "INSTALLPATH='/somepath/somewhere'
    SITEURL='example.com'
    PHPSERV='127.0.0.1:9000';
    echo \"$(<template)\""

As you're using eval, you could store your resulting config file into one variable:

eval "INSTALLPATH='/somepath/somewhere'
    SITEURL='example.com'
    PHPSERV='127.0.0.1:9000';
    cfgBody=\"$(<template)\""

Then

echo "$cfgBody"

and/or

echo "$cfgBody" >/cfgpath/cfgfile

Doing this into a loop

tmplBody="$(<template)"
while read INSTALLPATH SITEURL PHPSERV CFGFILE;do
    [ "$CFGFILE" ] && eval "echo \"$tmplBody\"" >"$CFGFILE"
  done <<<"
    /somepath/somewhere            example.com  127.0.0.1:9000  /tmp/file1
    '/some\ other\ path/elsewhere' sample2.com  127.0.0.1:9001  /tmp/file2
"

Note: On second line, there are escaped spaces (prepanded with a backshash \ and quotes '. The backslash tell read to not split the variable, and the quotes have to be added into the resulting /tmp/file2.

Soft way (compatible answer)

Under , you may do this way:

#!/bin/sh

(
    cat <<eohead
    #!/bin/sh
    INSTALLPATH='/somepath/somewhere'
    SITEURL='example.com'
    PHPSERV='127.0.0.1:9000';
    cat <<eof
eohead
    cat template
    echo eof
) | /bin/sh

This don't require bash, was tested under and .

Without eval !

sedcmd=''
for var in INSTALLPATH SITEURL PHPSERV;do
    printf -v sc 's/${%s}/%s/;' $var "${!var//\//\\/}"
    sedcmd+="$sc"
  done
sed -e "$sedcmd" <template

Could work into a loop:

while read INSTALLPATH SITEURL PHPSERV CFGFILE;do
    if  [ "$CFGFILE" ] ;then
        sedcmd=''
        for var in INSTALLPATH SITEURL PHPSERV;do
            printf -v sc 's/${%s}/%s/;' $var "${!var//\//\\/}"
            sedcmd+="$sc"
          done
        sed -e "$sedcmd" <template >"$CFGFILE"
      fi
  done <<<"
    /somepath/somewhere             example.com  127.0.0.1:9000  /tmp/file1
    '/some\ other\ path/elsewhere'  sample2.com  127.0.0.1:9001  /tmp/file2
"

Compatible answer, using sed

This could work without so called bashisms, into a loop:

#!/bin/sh

while read INSTALLPATH SITEURL PHPSERV CFGFILE;do

    sedcmd="s|\\\${INSTALLPATH}|${INSTALLPATH}|;"
    sedcmd="${sedcmd}s|\\\${SITEURL}|${SITEURL}|;"
    sedcmd="${sedcmd}s|\\\${PHPSERV}|${PHPSERV}|;"

    sed -e "$sedcmd" template >"$CFGFILE"

  done <<eof
    /somepath/somewhere             example.com  127.0.0.1:9000  /tmp/file1
    '/some\ other\ path/elsewhere'  sample2.com  127.0.0.1:9001  /tmp/file2
eof

Comparing outputs:

diff -u99 /tmp/file{1,2}
--- /tmp/file1        2015-05-31 11:02:03.407463963 +0200
+++ /tmp/file2        2015-05-31 11:02:03.407463963 +0200
@@ -1,22 +1,22 @@
 server {
     listen 80;
-    root /somepath/somewhere;
-    server_name example.com;
+    root '/some other path/elsewhere';
+    server_name sample2.com;

     client_max_body_size 20m;
     client_body_timeout 120s;

     location / {
         try_files /public/router.php =404;
         fastcgi_split_path_info ^(.+?\.php)(/.*)$;
-        fastcgi_pass 127.0.0.1:9000;
+        fastcgi_pass 127.0.0.1:9001;
         fastcgi_index router.php;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
         include /etc/nginx/fastcgi_params;
     }

     location /assets {
         try_files /app/$uri =404;
     }

 }
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137