What is the correct syntax for a bash multi line Heredoc (w/ Sed)?

2.3k views Asked by At

While using Sed to search/ insert a config file, I'm greeted by errors. What's causing them, and how can I fix them?

The Heredoc I'm looking to insert can be defined as follows:

read -d '' APPLICATION_ENV_STATE <<'EOF'
defined('APPLICATION_ENV') || define('APPLICATION_ENV',(getenv('APPLICATION_ENV') 
    ? getenv('APPLICATION_ENV') : 'production'));
EOF

While my Sed command uses the variable like this:

sed -i "/\/\/ \*\* MySQL settings \*\* \/\//i$APPLICATION_ENV_STATE" wp-config.php

Which results in:

sed: -e expression #1, char 1: unknown command: `?'

In addition to an extra characters after command error.

However, the following Heredoc works, but results in some less than pretty formatting in my text file:

read -d '' APPLICATION_ENV_STATE <<'EOF'
defined('APPLICATION_ENV') || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
EOF  

How do I get the first example to work?

2

There are 2 answers

3
declension On BEST ANSWER

AIUI, it's not the heredoc that's the problem, it's understanding which process is doing what at various times.

In your script that runs the sed command, Bash is substituting the variable before sed even sees it. Being a multi-line string, it would need escaping for sed). From the man page for sed, under the i command:

i \     Insert text, which has each embedded newline preceded by a back-slash.

Personally, I'd recommend using cat or echo if you can (or a scriping language like Python / Ruby / PHP), having broken the template up into atomic elements, so you can simply concatenate the relevant pieces together.

If you do want to continue with the current method though, you'll at least need to replace the newlines with backslashed newlines - try something like:

echo $APPLICATION_ENV_STATE | sed 's/$/\\/'
7
Ed Morton On

You're using the wrong tool. The only constructs you should be using in sed are s, g, and p (with -n). Just use awk and avoid all the quoting/escaping nonsense:

$ cat file
foo
// ** MySQL settings ** //
bar

$ awk -v app="defined('APPLICATION_ENV') || define('APPLICATION_ENV',(getenv('APPLICATION_ENV')
    ? getenv('APPLICATION_ENV') : 'production'));" '
{print} index($0,"// ** MySQL settings ** //"){print app}' file
foo
// ** MySQL settings ** //
defined('APPLICATION_ENV') || define('APPLICATION_ENV',(getenv('APPLICATION_ENV')
    ? getenv('APPLICATION_ENV') : 'production'));
bar

Notice that you don't need to escape the RE metachars in the string you want to search for because awk can treat a string as a string and you don't need to escape newlines in the string you're adding and you don't need a here doc with a shell variable, etc.

Your read/sed command as written would fail for various character combinations in your search and/or replacement strings - see Is it possible to escape regex metacharacters reliably with sed for how to robustly search/replace "strings" in sed but then just use awk so you don't have to worry about any of it.