47

How can one replace a part of a line with sed?

The line

DBSERVERNAME     xxx

should be replaced to:

DBSERVERNAME     yyy

The value xxx can vary and there are two tabs between dbservername and the value. This name-value pair is one of many from a configuration file.

I tried with the following backreference:

echo "DBSERVERNAME    xxx" | sed -rne 's/\(dbservername\)[[:blank:]]+\([[:alpha:]]+\)/\1 yyy/gip'

and that resulted in an error: invalid reference \1 on `s' command's RHS.

Whats wrong with the expression? Using GNU sed.

calvinkrishy
  • 3,798
  • 8
  • 30
  • 45

7 Answers7

66

This works:

sed -rne 's/(dbservername)\s+\w+/\1 yyy/gip'

(When you use the -r option, you don't have to escape the parens.)

Bit of explanation:

  • -r is extended regular expressions - makes a difference to how the regex is written.
  • -n does not print unless specified - sed prints by default otherwise,
  • -e means what follows it is an expression. Let's break the expression down:
    • s/// is the command for search-replace, and what's between the first pair is the regex to match, and the second pair the replacement,
    • gip, which follows the search replace command; g means global, i.e., every match instead of just the first will be replaced in a line; i is case-insensitivity; p means print when done (remember the -n flag from earlier!),
    • The brackets represent a match part, which will come up later. So dbservername is the first match part,
    • \s is whitespace, + means one or more (vs *, zero or more) occurrences,
    • \w is a word, that is any letter, digit or underscore,
    • \1 is a special expression for GNU sed that prints the first bracketed match in the accompanying search.
Nick Bull
  • 9,518
  • 6
  • 36
  • 58
Jeremy Stein
  • 19,171
  • 16
  • 68
  • 83
  • 10
    I know you guys have a lot experience in shell commands...but please care the aspiring linux noobs wherever possible.... everything inside `''` needs to be explained....or I have to go through the whole [doc](https://www.gnu.org/software/sed/manual/sed.html) – Mahesha999 May 20 '17 at 07:41
  • Note that with `sed -n` and `/p` in the pattern, this will only print the changed lines (filtering out the rest of the file) which I'm not sure is what the questioner wanted. (Versus `sed -rpe 's/.../.../gi` which would print every line, with just the matches changed.) – Joshua Goldberg Nov 20 '20 at 19:07
10

Others have already mentioned the escaping of parentheses, but why do you need a back reference at all, if the first part of the line is constant?

You could simply do

sed -e 's/dbservername.*$/dbservername yyy/g'
Vicky
  • 12,934
  • 4
  • 46
  • 54
  • 1
    Good point! I had just learnt about using regexps + sed and mind just decided 'regexp' on seeing this particular task, then as 'jwz' said 'I had two problems' (though not directly due to the regexps) :) – calvinkrishy Jul 04 '09 at 19:40
  • This gets my vote because it's the simplest solution to a simple problem. Using a backreference would save you from mistyping the two copies of `dbservername`. It's obvious here, but when I modify postgresql.conf in sed I find it hard to see at a glance whether I typed `listen_address` or `listen_addresses` (postgresql.conf). Both look plausible, but only one is correct. – Iain Samuel McLean Elder Sep 30 '13 at 21:43
2

You're escaping your ( and ). I'm pretty sure you don't need to do that. Try:

sed -rne 's/(dbservername)[[:blank:]]+\([[:alpha:]]+\)/\1 yyy/gip'
apandit
  • 3,304
  • 1
  • 26
  • 32
1

You shouldn't be escaping things when you use single quotes. ie.

echo "DBSERVERNAME    xxx" | sed -rne 's/(dbservername[[:blank:]]+)([[:alpha:]]+)/\1 yyy/gip'
Matthew Scharley
  • 127,823
  • 52
  • 194
  • 222
1

You shouldn't be escaping your parens. Try:

echo "DBSERVERNAME    xxx" | sed -rne 's/(dbservername)[[:blank:]]+([[:alpha:]]+)/\1 yyy/gip'
Pesto
  • 23,810
  • 2
  • 71
  • 76
1

This might work for you:

echo "DBSERVERNAME     xxx" | sed 's/\S*$/yyy/'
DBSERVERNAME     yyy
potong
  • 55,640
  • 6
  • 51
  • 83
0

Try this

sed -re 's/DBSERVERNAME[ \t]*([^\S]+)/\yyy/ig' temp.txt

or this

awk '{if($1=="DBSERVERNAME") $2 ="YYY"} {print $0;}' temp.txt

Mirage
  • 30,868
  • 62
  • 166
  • 261