1

Consider a command which takes arguments like this: cmd foo bar baz [arbitrary args...]. How do you build a filter of AND patterns based on those arguments?

Something like this pipeline of greps:

grep foo | grep bar | grep baz

I have a hunch I can avoid eval and also building a dynamic pipeline (out of a vague worry about Bash word expansion).

I tried reaching for AWK like this:

awk "$(printf '/%s/ && ' "$@" | awk '{ $NF=""; print }')"
  1. printf builds up the pattern based on args.
  2. Inner awk trims off some of the trailing &&.
  3. Outer awk runs the ANDs.
  4. Aside: On the edge case of 0 args, my script accidentally does the right thing—match everything—by running awk '// '.

But I feel like my approach is clunky.

Edit: Ah! I just thought of another way which only uses two AWKs and is more explicit about the edge cases:

awk "$(echo "$@" | awk '
    NF == 0 { print "//" }
    NF > 0 { for (i = 1; i <= NF; i++)
        printf("/%s/%s", $i, i==NF ? "" : " && ") }')"
7
  • 1
    unix.stackexchange.com/a/326270/70524 builds a dynamic pipeline, no worries about word expansion as everything is quoted correctly. Commented Mar 19 at 7:10
  • The grep question seems like it's for static AND patterns; but mine is about arbitrary number of AND patterns. Commented Mar 19 at 7:18
  • It quite literally says the opposite: "I would like to use single grep because I am building arguments dynamically", and besides there's nothing static about the answer I linked Commented Mar 19 at 7:20
  • Yeah that's right, I missed the ... and dynamically piece; thanks for the link unix.stackexchange.com/a/326270/173557 is correct! Commented Mar 19 at 7:23
  • What the linked answer does provide a solution, as the poster points out it'll be very inefficient. The question there isn't about taking multiple "patterns" as arguments and it doesn't provide a good definition of a "pattern", nor does it provide sample input/output so I think there's room for this current question to be improved by adding sample input/output, defining "pattern", and then hopefully get the best answer involving using provided instead of hard-coded "patterns". Commented Mar 22 at 13:34

2 Answers 2

1

Here's a filter like that in Perl, if I gather your idea correctly:

% cat test.txt
1 asdf
2 foo
3 foo bar
4 bar

% <test.txt perl -e 'LINE: while(<STDIN>) { for $p (@ARGV) { next LINE unless /$p/ }; print }' -- foo bar
3 foo bar
0

I think what you're probably looking for is something like this, using any POSIX awk:

$ cat cmd
#!/usr/bin/env bash

numPats=$(( $# - 1 ))
printf -v pats '%s\n' "${@:1:$numPats}"
shift "$numPats"

pats="$pats" \
awk '
    BEGIN {
        split(ENVIRON["pats"], tmp, /\n/)
        for ( i in tmp ) {
            pats[tmp[i]]
        }
    }
    {
        for ( pat in pats ) {
            # For Extended Regexp partial matching:
            if ( $0 !~ pat ) {
                next
            }
            # For Literal String partial matching uncomment this instead:
            # if ( ! index($0, pat) ) {
            #     next
            # }
        }
        print
    }
' "$1"

but without sample input and expected output that's just an untested guess. The above assumes the last argument passed to cmd will be the file name you want it to search in for the preceding arguments.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.