Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SED doesn't process multiple commands in {} after d(elete), a(ppend) or c(hange)

Tags:

sed

awk

sed should process after a matching-pattern multiple commands which are given in braces like {cmd1;cmd2;cmd3}. But in the given code below, all commands followed after d(elite) are ignored.

script.sed

s/^\(interface GigabitEthernet0\)$/\1\/0/
/interface GigabitEthernet0\/0$/{
n       # process next line = 1st line (after match) to be deleted
d       # Should delete '1st line (after match) to be deleted'
n       # process next line = 2nd line to be altered
s/2nd line to be altered/2ND LINE AFTER ALTERATION/
n
s/3rd line to be altered/3RD LINE AFTER ALTERATION/
}

input.txt

interface GigabitEthernet0
  1st line (after match) to be deleted
  2nd line to be altered
  3rd line to be altered
  4th line stays unchanged

sed -i -f script.sed example.txt

Expected output:

interface GigabitEthernet0/0
  2ND LINE AFTER ALTERATION
  3RD LINE AFTER ALTERATION
  4th line stays unchanged

Effective output:

interface GigabitEthernet0/0
  2nd line to be altered
  3rd line to be altered
  4th line stays unchanged

As seen in the output above, line 1st line (after match) to be deleted has been effectively deleted. But all following lines (2nd, 3rd, 4th) are not substituted.

BTW: Commands like a(ppend) or c(hange) behaves in the same manner; all followed commands are ignored.

like image 695
HRitter Avatar asked May 19 '19 14:05

HRitter


3 Answers

sed is for doing s/old/new, that is all. Just use awk, e.g. with GNU awk for inplace editing (just like you're doing with GNU sed) and the switch statement:

$ cat tst.awk
/^interface GigabitEthernet0$/ { lineNr=1 }
lineNr%5 {
    switch (lineNr++) {
    case 1: $0 = $0 "/0";               break
    case 2: next;                       break
    case 3: sub(/2nd line/,"WHATEVER"); break
    case 4: $0 = toupper($0);           break
    }
}
{ print }

$ cat file
interface GigabitEthernet0
  1st line (after match) to be deleted
  2nd line to be altered
  3rd line to be altered
  4th line stays unchanged

$ awk -i inplace -f tst.awk file

$ cat file
interface GigabitEthernet0/0
  WHATEVER to be altered
  3RD LINE TO BE ALTERED
  4th line stays unchanged

And just in case you really do just want to print new lines instead of modify the existing ones in the range after you match your regexp (like in @AlexHarvey's sed answer), then that'd just be:

awk '/interface GigabitEthernet0/ {
    print $0 "/0\n  2ND LINE AFTER ALTERATION\n  3RD LINE AFTER ALTERATION"
    c = 4
}
!(c&&c--)' file
interface GigabitEthernet0/0
  2ND LINE AFTER ALTERATION
  3RD LINE AFTER ALTERATION
  4th line stays unchanged

See the list of common awk idioms at https://stackoverflow.com/a/17914105/1745001 for what c&&c-- does.

Note that you don't have to specify the initial regexp multiple times, you don't have to worry about what chars might show up in your replacement text, and you can just give a number for how many lines are impacted you don't need to write the same character that number of times.

like image 124
Ed Morton Avatar answered Oct 31 '22 14:10

Ed Morton


As it is stated in the POSIX sed specification, the command d deletes the pattern space and starts the next cycle.

I tweaked your script a bit to get desired output:

/^interface GigabitEthernet0$/{
s//&\/0/
n 
N
s/.*\n//
s/2nd line to be altered/2ND LINE AFTER ALTERATION/
n
s/3rd line to be altered/3RD LINE AFTER ALTERATION/
}

It works, but also shows us that sed is not the right tool for this job. So, as Ed suggested, use awk instead.

like image 26
oguz ismail Avatar answered Oct 31 '22 14:10

oguz ismail


As noted by others, the immediate issue is that the d command not only deletes, but also immediately ends the cycle, i.e. stops processing further commands.

But, there is a simpler sed solution available to you here. You seem to have over-complicated this, based on your apparent requirement.1

# test.sh

l1="interface GigabitEthernet0/0"
l2="  2ND LINE AFTER ALTERATION"
l3="  3RD LINE AFTER ALTERATION"

replacement="$l1\n$l2\n$l3"

sed '/interface GigabitEthernet0/ {N;N;N; s!.*!'"$replacement"'!}' example.txt

Output:

interface GigabitEthernet0/0
  2ND LINE AFTER ALTERATION
  3RD LINE AFTER ALTERATION
  4th line stays unchanged

Further explanation:

  • on matching /interface GigabitEthernet0/,
  • slurp the next 3 lines into pattern space (N;N;N),
  • and then just replace the whole thing with the lines you actually want.
  • Noting that you need to replace sed's delimiter / with a character you trust not to appear in the input stream. I chose !.

1 And while I think there is an elegance to Ed's AWK solution, it is trivial to use sed here, where you just need to read forwards in a file by a few lines and do a s/// replacement.

like image 1
Alex Harvey Avatar answered Oct 31 '22 14:10

Alex Harvey