Issue
In a file, I'm having the lines like this -
a.lo a.o: abc/util.c \
/usr/lib/def.h
b.lo b.o: hash/imp.h \
/usr/lib/toy.c \
c.lo c.o: high/scan.c \
high/scan_f.c
Here you can see one extra \ (back slash) at the end of line number 4 (/usr/lib/toy.c ). How can I use sed command to remove this / (back slash)? Because of this I'm getting "*** multiple target patterns. Stop." error.
P.S. - I'm having this extra \ (back slash) at multiple places in my file. So using sed to delete it by line number won't be feasible. Need something which can check for .lo .o and check a line before, if it finds a \ (back slash) remove it.
Solution
Maybe not the simplest but this should work:
sed -nE '${s/\\$//;p;};N;s/\\([^\\]*:)/\1/;P;D' input_file
The main idea is to concatenate input lines in the pattern space (a sed internal text buffer), such that it always contains 2 consecutive lines, separated by a newline character. We then just delete the last \
before a :
, if any, print the first of the 2 lines and remove it from the pattern space before continuing with the next line.
sed
commands are separated by semi-columns (;
) and grouped with curly braces ({...}
). They are optionally preceded by a line(s) specification, for instance $
that stands for the last line of the input. So, in our case, ${s/\\$//;p;}
applies only to the last line while the rest (N;s/\\([^\\]*:)/\1/;P;D
) applies to all lines.
The
-n
option suppresses the default output. We need this to control the output ourselves with thep
(print) command.The
-E
option enables the use of extended regular expressions.
Let's first explain the tricky part: N;s/\\([^\\]*:)/\1/;P;D
. It is a list of 4 commands that are run for each line of the input because there is no line(s) specification before the commands.
When
sed
starts processing the input the pattern space already contains the first line (a.lo a.o: abc/util.c \
in your example). This is howsed
works: by default it puts the current line in the pattern space, applies the commands and restarts with the next line.N
appends the next input line (/usr/lib/def.h
) to the pattern space with a newline character as separator. The pattern space now contains:a.lo a.o: abc/util.c \ /usr/lib/def.h
N
also increments the current line number which becomes 2.s/\\([^\\]*:)/\1/
deletes the last\
before the first:
in the pattern space, if there is one. In our example the only\
is after the first:
. The pattern space is not modified.P
prints the first part of the pattern space, up to the first newline character. In our example what is printed is:a.lo a.o: abc/util.c \
D
deletes the first part of the pattern space, up to the first newline character (what has just been printed). The pattern space contains:/usr/lib/def.h
D
also starts a new cycle but different from the normalsed
processing, it does not read the next line and leaves the pattern space and current line number unmodified. So when restarting the pattern space contains line number 2 and the the current line number is still 2.
By induction we see that, each time sed
restarts executing the list of commands, the pattern space contains the current line, as normal. When processing line number 4 of your example it contains:
/usr/lib/toy.c \
After N
it contains:
/usr/lib/toy.c \
c.lo c.o: high/scan.c \
And there, the substitution command (s/\\([^\\]*:)/\1/
) matches and deletes the first \
:
/usr/lib/toy.c
c.lo c.o: high/scan.c \
It is thus:
/usr/lib/toy.c
that is printed and removed from the pattern space. Exactly what you want.
The last line needs a special treatment. When we start processing it the pattern space contains:
high/scan_f.c
If we don't do anything special N
does not change it (there is no next line to concatenate) and terminates the processing. The last line is never printed.
This is why another list of commands is needed, just for the last line: ${s/\\$//;p;}
. It applies only to the last line because it is preceded by a line(s) specification ($
for last line). The first command in the list (substitute s/\\$//
) removes a trailing \
, if there is one. The second (p
) prints the pattern space.
Note: if you know that the last line does not end with a trailing backslash you can simplify a bit:
sed -nE '$p;N;s/\\([^\\]*:)/\1/;P;D' input_file
Answered By - Renaud Pacalet Answer Checked By - Willingham (WPSolving Volunteer)