1

I often find myself doing something like this a lot:

something | grep cat | grep bat | grep rat

when all I recall is that those three words must have occurred somewhere, in some order, in the output of something...Now, i could do something like this:

something | grep '.*cat.*bat.*rat.*'

but that implies ordering (bat appears after cat). As such, I was thinking of adding a bash function to my environment called mgrep which would turn:

mgrep cat bat rat

into

grep cat | grep bat | grep rat

but I'm not quite sure how to do it (or whether there is an alternative?). One idea would be to for loop over the parameters like so:

while (($#)); do
    grep $1 some_thing > some_thing
    shift
done
cat some_thing

where some_thing is possibly some fifo like when one does >(cmd) in bash but I'm not sure. How would one proceed?

Palace Chan
  • 8,845
  • 11
  • 41
  • 93

3 Answers3

2

I believe you could generate a pipeline one command at a time, by redirecting stdin at each step. But it's much simpler and cleaner to generate your pipeline as a string and execute it with eval, like this:

CMD="grep '$1' "  # consume the first argument
shift

for arg in "$@"   # Add the rest in a pipeline
do
  CMD="$CMD | grep '$arg'"
done
eval $CMD

This will generate a pipeline of greps that always reads from standard input, as in your model. Note that it protects spaces in quoted arguments, so that it works correctly if you write:

mgrep 'the cat' 'the bat' 'the rat'
alexis
  • 48,685
  • 16
  • 101
  • 161
2

Thanks to Alexis, this is what I did:

function mgrep() #grep multiple keywords
{
    CMD=''
    while (($#)); do
        CMD="$CMD grep \"$1\" | "
        shift
    done
    eval ${CMD%| }
}
rawr
  • 20,481
  • 4
  • 44
  • 78
Palace Chan
  • 8,845
  • 11
  • 41
  • 93
  • Nice, I didn't know about the `${X%}` syntax. But think about quoting `$1` like I did-- sooner or later you'll want to include a shell metacharacter in your search, or to search for a phrase, and your function will break. And it's just better to write robust code when it's so easy! – alexis Sep 08 '12 at 21:39
1

You can write a recursive function; I'm not happy with the base case, but I can't think of a better one. It seems a waste to need to call cat just to pass standard input to standard output, and the while loop is a bit inelegant:

mgrep () {
    local e=$1;
    # shift && grep "$e" | mgrep "$@" || while read -r; do echo "$REPLY"; done
    shift && grep "$e" | mgrep "$@" || cat

    # Maybe?
    # shift && grep "$e" | mgrep "$@" || echo "$(</dev/stdin)"
}
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I like this! Clever. How/why does it terminate though? – Palace Chan Sep 09 '12 at 04:43
  • Ah, good question. `shift` has a nonzero exit if its implied argument of 1 is greater than the number of positional arguments. In that case, `cat` is called instead of the recursive `grep`/`mgrep` pipeline. – chepner Sep 09 '12 at 12:42