With GNU `grep`:

<!-- language: shell -->

 { grep -m1 pattern && 
 cat || cat ./infile
 } <./infile

That requires a regular, *`lseek()`*-able *`./infile`*. Provided you have that, the above script is probably the quickest way you might do it with shell tools. It only has to read `*./infile`* twice if `grep` manages to scan completely through input and find no match. Otherwise `grep` only tries for the one match, prints it if found, and then `cat` takes over - in either case.

To leave the pattern out...

 { >/dev/null \
 grep -m1 pattern && 
 cat || cat ./infile
 } <./infile

...where `grep`'s only line of output is written to `/dev/null`.

Here's an example:

<!-- language: shell -->

 seq 100 >nums
 only_after()({
 >/dev/null \
 grep -m1 "$2" &&
 cat || cat "$1"
 } <"$1")
 only_after nums '[89]\{2\}'

---

 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100

You see, `grep` and `cat` share the same in-stream. `grep` will only consume as much of it as is needed to find a single `-m`atch, and then `cat` is left to pick up where `grep` left off. And `grep` will only return true if it can find a match - but if it doesn't it will consume the whole stream looking for it, and so the second `||cat` will just print the whole file.

If you want to replace *`./infile`* - in other words *edit in-place* - then you can *actually* write it over by buffering it to a temp file first:

 { g=$(grep -m1 pattern) &&
 cut -c2- <<IN >./infile
 $( printf " %s\n" "$g" &&
 paste -d\ /dev/null - )
 IN
 } <./infile

...which will not take any action at all if the pattern cannot be found - and so *never* reads *`./infile`* more than the one time - but for a successful match always fully buffers the processed tail of *`./infile`* out to a temp file before writing that over *`./infile`*.

I wrote a little program to do this...

<!-- language: shell -->

 allor()(
 	set -f;	unset	 _o o i m
 	z=moi	OPTIND=1 IFS="
 ";	op()	while	getopts :i:o:m:	op
				do		case	$op:$OPTARG		in
						([!"$z"]*|m:*[!0-9]*|"") ! : ;;
						(o:[-+]) z=${z#*o}${z%o*}
								 _o=${OPTARG#-};;
						([oim]*) z=${z#*$op}${z%$op*}
								 eval $op=\$OPTARG
						esac||	 break
				done
		op "$@"	&&	[ -f "$i" ]	&& {
				shift	$((OPTIND-!!${m:=1}))
				set --	$(grep -m$m "$@"|sed -ne'$=;$p')
				! case	$((${1:+(m==$1)+}0))${o=${_o+$i}} in 
						(0"$i") ;; (0*) eval "cat $i${o:+>\$o}";;
						(1*) ! 	eval "	cut -c2-${o:+>\$o}"
 			esac <<-i
				$(		[ $m = "$1" ]	 &&
						printf \ %s\\n $2 &&
						paste -d\ /dev/null -)
						i
		}		<"$i"
 )

...which adds some option parsing and whatnot. Basically you can pass any argument to `grep` that you might like to do - all arguments are passed through verbatim *except* the first of either `-i` or `-o` or `-m`.

You use `-i` and its argument to specify the input file. You can use `-o-` to write to stdout - which is the default behavior anyway - or `-o+` to edit the `-i` file in-place, or `-o` and any writable pathname. You use `-m` to specify a match count - which is to say that you can get all of a file after *`-m`* count matches, or just all of the file if that many matches cannot be found in input. All arguments from the first which isn't a valid `-[iom]` switch, or from the second occurring `-[io]` are handed directly to `grep`.

It tests whether the requested matches were successful and where output should go before attempting to write it. For example, if the matches were not successful and output is directed anywhere but back over *`./infile`* it will not do anything at all and leave *`./infile`* alone. But if the matches were successful and the outfile and infile are the same, it will shorten infile. But if the matches are not successful and output is directed anywhere else it will just `cat` input to output.

A little demo:

 seq 20 >nums
 allor -inums -m2 5

---

<!-- language: none -->

 15
 16
 17
 18
 19
 20

...and...

 seq 10 >nums
 allor -inums -m2 5; echo return: "$?"

---

<!-- language: none -->

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 return: 1

...and...

 seq 20000 >nums
 allor -m1999 -inums -o+ 5$; cat nums

---

<!-- language: none -->

 19985
 19986
 19987
 19988
 19989
 19990
 19991
 19992
 19993
 19994
 19995
 19996
 19997
 19998
 19999
 20000