0

on the following example file

more ambari-agent.ini
[server]
hostname=AABB

we want to match hostname word in file and replace only the value after = ( equals character )

so we did that

VAR=server_100

sed -ire "s/(hostname=)[^=]*$/\$VAR/"  /tmp/ambari-agent.ini

but still we have

more ambari-agent.ini
[server]
hostname=AABB

while expected results should be

more ambari-agent.ini
[server]
hostname=server_100

where we are wrong ?

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
yael
  • 13,106
  • The -i option takes an argument, which will be used as a backup suffix. You are using re as the backup suffix. This is a typo. You also escape the $ on your variable, so it's value won't be inserted. – Kusalananda Dec 16 '19 at 09:06
  • I must to use the "-" because we want to update the file and not redirect it to other file – yael Dec 16 '19 at 09:25

2 Answers2

1
VAR=anything
escaped_VAR=$(
  printf '%s\n' "$VAR" |
    LC_ALL=C sed 's|[/&\\]|\\&|g;$!s/$/\\/'
)

LC_ALL=C sed -E -i -e "s/^(hostname=).*/\1$escaped_VAR/" -- "$the_file"

With FreeBSD/macOS sed, replace -i with -i ''.

For both GNU (and NetBSD/OpenBSD which eventually copied GNU instead of FreeBSD) and FreeBSD sed, -i takes an argument which is the name of the backup extension. With GNU sed, the argument is optional and has to be stuck to the -i option, while for FreeBSD sed, it's required (but passing an empty one also disables keeping the backup copy).

-ire is interpreted as -i with re as argument with both GNU and FreeBSD. -i -e is interpreted as -i with a -e argument with FreeBSD, whilst it's interpreted as a -i with no argument followed by the -e option with GNU sed.

Other notes:

  • GNU used -r for ERE, BSDs used -E. -E is a lot better and most implementations have settled on that now including GNU sed. That's the one that will be specified by POSIX. Here EREs are not necessary anyway. The only feature that EREs have over BREs is | (alternation), the rest is just syntactic differences¹. sed -i "s/^\(hostname=\).*/\1$escaped_VAR/" would work just as well.
  • .* may fail to match until the end of the line if the input contains sequence of bytes not forming valid characters in locales other than C (hence the LC_ALL=C).
  • expanding $VAR asis in the code passed to sed would amount to a command injection vulnerability if the content was not sanitized and &, \, / and newline escaped. Using \$VAR like you did, would just replace with a literal $VAR, not the contents of the $VAR shell variable.
  • doing s/(hostname=)[^=]*$/$escaped_VAR/ would only replace the hostname line if the value (or whatever is to the right of hostname= which could also be comments for instance) didn't contain =. I don't expect it was intended.
  • without the ^, that would match hostname= anywhere in the file. ^ makes sure it's only done for hostname= instance that are found at the start of the line. If you want to allow leading whitespace, you can always use "/^([[:space:]]*hostname...

¹ POSIX EREs are also missing the back-reference feature of BREs though most ERE implementations now support them as an extension

-1

The previous answer also works as a function, I'm just lacking reputation to answer with a comment there.

VAR=anything
function escape() { printf '%s\n' "$1" | LC_ALL=C sed 's|[+/&\\]|\\&|g;$!s/$/\\/' }
LC_ALL=C sed -E -e "s/^(hostname=).*/\1$(escape $VAR)/" -i -- "$file"

Please note that I have amended the + character to the escape rule.

This also works nicely with dynamically provided VAR or otherwise provided substitution values.

LC_ALL=C sed -E -e "s/^(> API_SECRET=).*/\1$(escape $(pwgen -n 64 1))/" -i -- diff.patch
VAR="admin"; LC_ALL=C sed -E -e "s/^(+SP_ADMIN_USER=).*/\1$(escape "${VAR}")/" -i diff.patch

The command differs from the original example by omitting a possibly redundant -e and also --.

It's possible to further parametrise this logic with another function, omitting VAR altogether.

function replace() { LC_ALL=C sed -E -e "s/^($(escape "${1}")).*/\1$(escape "${2}")/" -i "${3}" }
replace "+ADMIN_USER=" "admin" diff.patch

It could then be used programmatically to replace values after chosen line beginnings.