d3pd
d3pd

Reputation: 8315

Bash - how to replace a single line starting with a pattern with multiple lines defined in a variable?

I have a file that at some point contains a line like the following:

VIRTUAL_ENV="/afs/sern.ch/user/l/lronhubbard/virtual_environment"

It is the sole line in the file that begins with VIRTUAL_ENV. Using a script, I want to replace this line with the following lines:

directory_bin="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
directory_env="$(dirname "${directory_bin}")"
VIRTUAL_ENV="${directory_env}"

How could this be done?

To start with, I've got the replacement lines stored in a here document in the script:

IFS= read -d '' text << "EOF"
directory_bin="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
directory_env="$(dirname "${directory_bin}")"
VIRTUAL_ENV="${directory_env}"
EOF

Next, I can replace the line in the file with something else (here, xxx) using sed:

sed -i "s/^VIRTUAL_ENV.*/xxx/g" test.txt

Now, how can I use the defined here document variable ${text}, with all of its newlines and whatnot, instead of xxx in the sed command?


EDIT: Following the suggestion by rslemos, I have implemented the following using a temporary file instead of a here document:

#!/bin/bash

temporary_filename="$(tempfile)"

cat > "${temporary_filename}" << "EOF"
directory_bin="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
directory_env="$(dirname "${directory_bin}")"
VIRTUAL_ENV="${directory_env}"
EOF

sed -i "/^VIRTUAL_ENV.*/ {
   r ${temporary_filename}
   d
}" test.txt

rm "${temporary_filename}"

I'd still like to know how to use a here document directly so that I'm not unnecessarily using the hard drive.

Upvotes: 0

Views: 418

Answers (3)

potong
potong

Reputation: 58371

This might work for you (GNU sed & bash):

cat <<\! | sed $'/PATTERN/{r /dev/stdin\n;d}' file
HERE DOCUMENT
STUFF
HERE
!

Use cat to pipe the stdin into the sed command and read it as a file using r command.

N.B. \! quotes all text in the here document use ! to interpolate variables. $'...' allows commands needing a newline (i.e. sed commands such as r,a,i,c,R,w,W) to be written as one line.

Upvotes: 1

rslemos
rslemos

Reputation: 2731

I would go with sed alone:

/^VIRTUAL_ENV/{
r source.txt
d
}

Where source.txt is the file where lie the new lines to replace a line starting with "VIRTUAL_ENV".

If you really need the new lines (source.txt) to be a here document, the /dev/fd/0 trick will work (in Linux at least). But you could also combine mktemp and mkfifo.

EDIT

To address the "I'd still like to know how to use a here document directly":

!/bin/bash

sed -e '/^VIRTUAL_ENV/{' -e 'r /dev/fd/0' -ed -e'}' test.txt << "EOF"
directory_bin="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
directory_env="$(dirname "${directory_bin}")"
VIRTUAL_ENV="${directory_env}"
EOF

Not very portable, because of the /dev/fd/0 part (works in Linux, though).

Upvotes: 2

tripleee
tripleee

Reputation: 189327

Many dialects of sed allow you to escape newlines in the substitution:

sed -i 's/^VIRTUAL_ENV.*/directory_bin="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"\
directory_env="$(dirname "${directory_bin}")"\
VIRTUAL_ENV="${directory_env}"/' test.txt

That's a single-quoted string spanning three lines, with a backslash added for continuation at each internal line boundary.

This is not portable to every sed variant out there, but works fine on *BSD (including OSX) and Linux at least.

Incidentally, you could also simply put all of these commands on a single line, with semicolons between them.

Upvotes: 0

Related Questions