Let's suppose I pipe the output of a command and want to filter lines with grep but also keep the first one which is a header. I saw someone type something akin to this:
the command | (read l; echo$l) | grep bla | less
and it extracted the first line (header), then grepped the rest of the file for the lines matching bla and the output of that went to less
for inspection. Of course the above command doesn't work but that's the idea, what part of it is wrong?
To Display Line Numbers with grep Matches Append the -n operator to any grep command to show the line numbers. We will search for Phoenix in the current directory, show two lines before and after the matches along with their line numbers.
The grep command searches through the file, looking for matches to the pattern specified. To use it type grep , then the pattern we're searching for and finally the name of the file (or files) we're searching in. The output is the three lines in the file that contain the letters 'not'.
The grep command syntax is simply grep followed by any arguments, then the string we wish to search for and then finally the location in which to search. 1. Search test1 for the string steve using grep. The search criteria is case sensitive so ensure that you're searching correctly.
The grep command searches a text file based on a series of options and search string and returns the lines of the text file which contain the matching search string.
With awk:
command | awk 'NR==1||/bla/'
Thanks to @doubleDown for pointing out that {print}
is unnecessary since it is the default action.
With perl:
command | perl -ne 'print if $.==1 or /bla/'
(If you need perl irregular expressions, perl is probably available :) )
sed
flavor:
command | sed -ne '1p' -ne '/bla/p'
grep
everything but the first lineThe following will mostly get what you want, but it has several flaws (see the end of this post):
the command | (read l; echo $l; grep blah) | less
Instead, I recommend creating and using the following function:
grep1 () (
IFS= read -r line
printf %s\\n "${line}"
grep "$@"
)
Here is how you would use it:
the command | grep1 blah | less
Example of it in action:
$ ps -ef | grep1 firefox
UID PID PPID C STIME TTY TIME CMD
rhansen 3654 3311 4 13:33 ? 00:07:59 /usr/lib/firefox/firefox
read
consumes the first line of input from command
and assigns it (unmodified) to the variable line
printf
outputs the value of line
(unmodified)grep
The first line never passes through grep
, so there's no opportunity for it to filter it out.
( ... )
instead of { ... }
because I don't want variable assignments inside the body to affect the caller's environment (the parentheses cause it to be run in a subshell, which isolates any changes from the caller).IFS=
prevents read
from stripping leading and trailing whitespace-r
argument to read
prevents it from processing backslashes (the first line is perfectly preserved in the variable line
)printf %s\\n
instead of echo
because echo
might process backslashes, possibly causing the first line of output to be different from the original first lineThe above function has a minor problem: If given empty input it will print a blank line. The following avoids that problem:
grep1_better() (
IFS= read -r line && printf %s\\n "${line}"
grep "$@"
)
This works because read
returns a non-zero return code if it encounters the end of input. If there's no input, read
will "fail" (return non-zero) and the &&
will skip the printf
.
But, now there's a new problem: If there is input, but there aren't any newlines at all (for example, printf %s foo
), the function will output nothing. This is because read
will encounter the end of input and "fail" even though there was some input. Here's how that can be fixed:
grep1_even_better() (
IFS= read -r line || [ -n "${line}" ] && printf %s\\n "${line}"
grep "$@"
)
In English, the above says, "Read a line of input. If the end of input wasn't encountered, or if something was read, then print what was read. Then run grep
."
A further improvement would be to detect when the function is being called with one or more filename arguments and react accordingly (read from the file(s) instead of standard input).
The following code doesn't work:
the command | (read l; echo $l) | grep bla | less
There are two major problems:
grep
, so grep
could still filter it out.the command
" command never gets an opportunity to output the remaining lines (modulo buffering) because nobody in the second stage is waiting to read them.)In addition, there are a handful of minor problems:
IFS
is not set to the empty string before calling read
, read
will strip the first line's leading and trailing whitespace before assigning the variable l
.-r
is not passed to read
, read
will attempt to interpret backslashes in the first input line. This could corrupt the first line.echo
is not enclosed in double quotes, tabs and multiple consecutive whitespace will be converted to a single space. If the first line contains column headings, this will break the alignment with the following rows.echo
might process backslashes in its arguments, the first line may be corrupted.-
, echo
might interpret the string as an option, not something to be printed.These minor problems are also present in the command | (read l; echo $l; grep blah) | less
, which is why I recommended the grep1()
function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With