0

I have this code, which I do want for replacement of a specific pattern, but not multi in-place replacement.

 echo "ss qq" | sed  "s/ss/qq/g; s/qq/dd/g;"

The result is

dd dd

and wish it would be

qq dd

Also via "looping" we get the same result.

echo "ss qq" | sed  ":loop; s/ss/qq/g; s/qq/dd/g; t loop;"

its petty, very disappointing bug !!

Any suggestion why it's happening?

mr.tee
  • 39
  • 1
  • 5
  • `sed` semi-colon separated commands are executed consecutively, that is the reason. See [this thread](https://unix.stackexchange.com/a/21062/279389) that may work for you. – Wiktor Stribiżew May 27 '19 at 13:16
  • Try the solution posted here: https://stackoverflow.com/a/26568996/6515775 – mr.mams May 27 '19 at 13:22
  • If only single letter (not word) has to be changed, you could use `tr`: `echo "ss qq" | tr sq qd` gives `qq dd` – Toto May 27 '19 at 13:47

4 Answers4

2

As for "why it's happening": sed takes the input line by line, then applies each command sequentially. This becomes more clear if you look at the debug output of GNU sed (4.6 or newer) for your command:

$ sed --debug 's/ss/qq/g;s/qq/dd/g' <<< 'ss qq'
SED PROGRAM:
  s/ss/qq/g
  s/qq/dd/g
INPUT:   'STDIN' line 1
PATTERN: ss qq           # Pattern space before first command is applied
COMMAND: s/ss/qq/g
MATCHED REGEX REGISTERS
  regex[0] = 0-2 'ss'
PATTERN: qq qq           # Pattern space before second command is applied
COMMAND: s/qq/dd/g
MATCHED REGEX REGISTERS
  regex[0] = 0-2 'qq'
PATTERN: dd dd
END-OF-CYCLE:
dd dd
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
  • @mr.tee It requires GNU sed 4.6 or newer, but the principle is the same – the debug output is just for illustration. – Benjamin W. May 27 '19 at 16:20
0

whish it would be

qq dd

then you do the substitution in invert order:

change sed "s/ss/qq/g;s/qq/dd/g;" -> sed "s/qq/dd/g;s/ss/qq/g;"

Kent
  • 189,393
  • 32
  • 233
  • 301
0

Maybe it is the behaviour of the y/// command you are looking for:

▶ echo "ss qq" | sed 'y/sq/qd/'                 
qq dd

This will transform all s into q and all q into d character by character.

Alex Harvey
  • 14,494
  • 5
  • 61
  • 97
0

This might work for you (GNU sed):

sed  's/ss\|qq/\n&/;:a;s/\nss/qq\n/;s/\nqq/dd\n/;s/\n\(.\)/\1\n/;ta;s/\n//' file

This introduces a marker in the form of a newline, as to where the last substitution occurred. At the end of the line the marker is removed.

The reason for the marker is because each invocation of a substitution command starts afresh from the beginning of the line, not from where the last substitution finished. The g flag may confuse the issue but only belongs the current invocation of the substitution.

As already mentioned, a better way is to is to use an alternative for the first substitution and replace this globally as the last command.

sed 's/ss/\n/g;s/qq/dd/g;s/\n/qq/g' file 
potong
  • 55,640
  • 6
  • 51
  • 83