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