93

Input file1 is:

dog 123 4335
cat 13123 23424 
deer 2131 213132
bear 2313 21313

I give the match the pattern from in other file ( like dog 123 4335 from file2).

I match the pattern of the line is dog 123 4335 and after printing all lines without match line my output is:

cat 13123 23424
deer 2131 213132
bear 2313 21313

If only use without address of line only use the pattern, for example 1s how to match and print the lines?

loganaayahee
  • 1,129

12 Answers12

67

In practice, I'd probably use Aet3miirah's answer most of the time, and alexey's answer is wonderful for navigating through the lines (also, it works with less). OTOH, I really like another approach (which is kind of the reversed Gilles' answer):

sed -n '/dog 123 4335/,$p'

When called with the -n flag, sed does not print by default the lines it processes anymore. Then we use a 2-address form that says to apply a command from the line matching /dog 123 4335/ until the end of the file (represented by $). The command in question is p, which prints the current line. So, this means "print all lines from the one matching /dog 123 4335/ until the end."

brandizzi
  • 2,864
  • 3
  • 22
  • 29
42

If you have a reasonably short file grep alone might work:

grep -A5000 -m1 -e 'dog 123 4335' animals.txt

5000 is just my guess at "reasonably short", as grep finds the first match and outputs it together with the next 5000 lines (the file doesn't need to have that many). If you don't want the match itself you'll need to cut it off, e.g.

grep -A5000 -m1 -e 'dog 123 4335' animals.txt | tail -n+2


If you do not want the first, but the last match as delimiter you could use this:
tac animals.txt | sed -e '/dog 123 4335/q' | tac

This line reads animals.txt in reverse order of lines and outputs up to and including the line with dog 123 4335 and then reverses again to restore proper order.

Again, if you don't need the match in the result, append tail. (You could also complicate the sed expression to discard its buffer before quitting.)

  • By my test, GNU grep 3.0 doesn't output more than 132 lines in after-context (regardless of specified value). – ruvim Aug 29 '17 at 09:44
  • Mine seems to max out at 1000 lines. Doesn't work at all in OS X. But at least it's more comprehensible :) – rogerdpack Feb 02 '22 at 22:00
38

Assuming you want to match the whole line with your pattern, with GNU sed, this works:

sed -n '/^dog 123 4335$/ { :a; n; p; ba; }' infile

Standard equivalent:

sed -ne '/^dog 123 4335$/{:a' -e 'n;p;ba' -e '}' infile

With the following input (infile):

cat 13123 23424 
deer 2131 213132
bear 2313 21313
dog 123 4335
cat 13123 23424 
deer 2131 213132
bear 2313 21313

The output is:

cat 13123 23424 
deer 2131 213132
bear 2313 21313

Explanation:

  • /^dog 123 4335$/ searches for the desired pattern.
  • :a; n; p; ba; is a loop that fetches a new line from input (n), prints it (p), and branches back to label a :a; ...; ba;.

Update

Here's an answer that comes closer to your needs, i.e. pattern in file2, grepping from file1:

tail -n +$(( 1 + $(grep -m1 -n -f file2 file1 | cut -d: -f1) )) file1

The embedded grep and cut find the first line containing a pattern from file2, this line number plus one is passed on to tail, the plus one is there to skip the line with the pattern.

If you want to start from the last match instead of the first match it would be:

tail -n +$(( 1 + $(grep -n -f file2 file1 | tail -n1 | cut -d: -f1) )) file1

Note that not all versions of tail support the plus-notation.

Thor
  • 17,182
  • 1
    This is the first example of the n and p commands in sed that I have seen that doesn't feel like taking sed too far. It seems (from my brief tests) that sed -n '/^dog 123 4335$/ { :a; p; n; ba; }' infile (with the p and n switched) successfully includes the line that matches as well. – Josiah Yoder Apr 03 '19 at 15:25
18
sed -e '1,/dog 123 4335/d' file1

If you need to read the pattern from a file, substitute it into the sed command. If the file contains a sed pattern:

sed -e "1,/$(cat file2)/d" file1

If the file contains a literal string to look for, quote all special characters. I assume the file contains a single line.

sed -e "1,/$(sed 's/[][\\\/^$.*]/\\&/g' file2)/d" file1

If you want the match to be the whole line, not just a substring, wrap the pattern in ^…$.

sed -e "1,/^$(sed 's/[][\\\/^$.*]/\\&/g' file2)\$/d" file1
18

$ more +/"dog 123 4335" file1

alexey
  • 189
11

With awk:

awk 'BEGIN {getline pattern < "other file"}
   NR == 1, $0 ~ pattern {next}; {print}' < "input file"
7

My answer for the question in the subject, without storing pattern in a second file. Here is my test file:

$ cat animals.txt 
cat 13123 23424 
deer 2131 213132
bear 2313 21313
dog 123 4335
cat 13123 23424 
deer 2131 213132
bear 2313 21313

GNU sed:

 $ sed '0,/^dog 123 4335$/d' animals.txt 
 cat 13123 23424 
 deer 2131 213132
 bear 2313 21313

Perl:

$ perl -ne 'print unless 1.../^dog 123 4335$/' animals.txt
cat 13123 23424 
deer 2131 213132
bear 2313 21313

Perl variant with pattern in a file:

$ cat pattern.txt 
dog 123 4335
$ perl -ne 'BEGIN{chomp($p=(<STDIN>)[0])};print unless 1../$p/;' animals.txt < pattern.txt
cat 13123 23424 
deer 2131 213132
bear 2313 21313
jbgood
  • 71
5

If the input is an lseekable regular file:

With GNU grep:

{ grep  -xFm1 'dog 123 4335' >&2
  cat; } <infile 2>/dev/null >outfile

With sed:

{ sed -n '/^dog 123 4335$/q'
  cat; } <infile >outfile

A GNU grep called w/ the -m option will quit input at the match - and it will leave its (lseekable) input fd immediately after the point it found its last match. So calling grep w/ -m1 finds the first occurrence of a pattern in a file, and leaves the input offset at precisely the right place for cat to write everything following the pattern's first match in a file to stdout.

Even without a GNU grep you can do the exact same thing w/ a POSIX compatible sed - when sed quits it is specified to leave its input offset right where it does. GNU sed is not standards-compliant in this way, though, and so the above will likely not work w/ a GNU sed unless you call it with its -u switch.

mikeserv
  • 58,310
  • note, the sed stream sharing demonstrated here is not specially (though, yes, the standard referenced does specifically example sed as a utility thus capable) of the free-form and conditionally cooperative workflow shown. notably, *all standard utilities* are meant and specified to thus cooperate and share cursor positions of input streams without failing the next reader any processing at all. grep -q should do this; quietly grep should return as soon as any match in input is found, and any remaining of input should not, by standard, be consumed by default. – mikeserv Oct 29 '18 at 10:43
5

Wth ed:

ed -s file1 <<< '/dog 123 4335/+1,$p'

This sends one print command to ed in a here-string; the print command is limited in range to one after (+1) the dog 123 4335 match until the end of the file ($).

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • Probably the most universal answer here because ed is available on just about every Unix in existence. – Jack G Jul 08 '20 at 14:35
5

One way using awk:

awk 'NR==FNR{a[$0];next}f;($0 in a){f=1}'  file2 file1

where file2 contains your search patterns. First, all the contents of file2 are stored in the array "a". When the file1 is processed, every line is checked against the array, and printed only if is not present.

Guru
  • 5,905
1

If you don't mind the creation of a temporary file, and have csplit available, this works:

sh -c 'csplit -sf"$1_" "$1" "%^$(cat "$2")%+1" && cat "${1}_00"' sh file1 file2

Note file1 is the input file and file2 is the pattern file (as stated in the question).

The long form of the above command is:

sh -c 'csplit --quiet --prefix="$1_" "$1" "%^$(cat "$2")%+1" && cat "${1}_00"' sh file1 file2

i.e.,

csplit --quiet --prefix="file1_" "file1" "%^$(cat "file2")%+1" && cat "file1_00"

csplit without the prefix flag above would create the file xx00 (prefix being xx, and suffix being 00). With the flag above it creates the file file1_00. Without the quiet flag, it prints the output file size (size of the resulting file).

0

Since awk isn't expressly disallowed, here's my offering assuming 'cat' is the match.

awk '$0 ~ /cat/ { vart = NR }{ arr[NR]=$0 } END { for (i = vart; i<=NR ; i++) print arr[i]  }' animals.txt
Tom
  • 87