Using the regexp string, how can I remove all the lines before the first line that contains a match? e.g How can I change this:
lost load linux loan linux
into this:
linux loan linux
I tried:
echo "lost load linux loan linux" | sed -e 's/.*^li.*$//g'
but it returns this, not changing anything:
lost load linux loan linux
I’d like to make it work so that it won’t output anything when there’s no match.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
One way, POSIXly:
$ echo "lost
load
linux
loan
linux" | sed -e/linux/{ -e:1 -en;b1 -e} -ed
or shorter:
sed -n '/linux/,$p'
or even shorter:
sed '/linux/,$!d'
For readers who wonder why I prefer the longer over the shorter version, the longer version will only perform i/o over the rest of file, while using ranges can affect the performance if the 2nd address is a regex, and the regexes are trying to be matched more than is necessary.
Consider:
$ time seq 1000000 | sed -ne '/^1$/{' -e:1 -en;b1 -e}
=====
JOB sed -e '/^1$/,$d'
87% cpu
0.11s real
0.10s user
0.00s sys
with:
$ time seq 1000000 | sed -e '/^1$/,/1000000/d' ===== JOB sed -e '/^1$/,/1000000/d' 96% cpu 0.24s real 0.23s user 0.00s sys
you can see the different between two versions. With complex regex, it’s will be big difference.
Method 2
This is easy to do clearly in awk:
echo "lost
load
linux
loan
linux" | awk '
/^li/ { found = 1 }
found { print }'
Here found is a variable,
with an arbitrarily chosen, self-explanatory name.
It gets set when the program encounters an input line
that matches the regexp.
(Variables initially default to null,
which is functionally equivalent to 0 or FALSE.)
So input lines are printed after the ^li pattern is matched,
and not before.
The third line of the input (the first linux line) is printed
because the conditional print statement comes after
the statement that looks for the pattern and sets the flag.
If you want to start printing with the fourth line
(the line after first linux line),
just reverse the order of the two statements.
If no input line matches the regexp,
the flag never gets set, and nothing is printed.
As I said, the name of the flag variable is arbitrary;
you can use something shorter (e.g., f) if you want.
And { print } is the default action, so you can leave it out.
So, if you don’t care about clarity, you can shorten the above to
echo "lost
load
linux
loan
linux" | awk '/^li/{f=1}f'
Method 3
Two other awk solutions:
They both just set a found flag when seeing the first regex match and print when that flag is set.
echo "lost
load
linux
loan
linux" | awk 'BEGIN {found = 0} {if (found || $0 ~ /linux/) {found = 1; print}}'
This one is a little longer but doesn’t set the found flag again.
echo "lost
load
linux
loan
linux" | awk 'BEGIN {found = 0} {if (found) {print} else if ($0 ~ /linux/) {found = 1; print}}'
Method 4
You can use ex in batch mode to directly edit the file.
(If you want to see what the output file would be before actually
changing the file, replace the x by %p.)
printf '%sn' 'a' 'linux' '.' '1,/linux/-1d' '$d' 'x' | ex -s file
a,linux,.writes adds alinuxline to the end.1,/linux/-1ddeletes the lines in the interval [first line of the file, line just before firstlinux];$ddeletes the artificialy inserted line in step 1.xwrites the changes and quits.
The more direct approach (see 1st version in edit history) would leave the file untouched
if there were no match. This one empties the file, as required (that is the reason for the queer step 1).
$ cat file1 lost load linux loan linux $ printf '%sn' a linux . 1,/linux/-1d '$d' x | ex -s file1 $ cat file1 linux loan linux
$ cat file2 lost load loan $ printf '%sn' a linux . 1,/linux/-1d '$d' x | ex -s file2 $ cat file2 #file2 is empty
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0