Score:2

Passing multiple file patterns to grep

jp flag

I have a sequence of search patterns stored in a bash array (ptrn), which I want to pass to a grep command. How can I do it?

  ptrn=("FN" "DS")
  for fl in "$@"; do  # loop through number of files
    if [[ -f "$fl" ]]; then
      printf '\n%s%s%s\n\n' "$mgn" "==>  $flnm  <==" "$rst"
      grep --color "$ptrn" "$flnm"
    fi
  done
petep avatar
in flag
Use ${ptrn[@]} for grepping
jp flag
Does not seem to be enough because I get `grep: DS: No such file or directory`. It is thinking that the pattern is a file.
petep avatar
in flag
Try "${ptrn[@]}" instead for grep
jp flag
Same problem. Does not recognise as a search pattern.
petep avatar
in flag
How about grep "${ptrn[0]}\|${ptrn[-1]}" . First and last.
jp flag
Have done this `pa=$(printf '%s\|' "${ptrn[@]}")` followed by `grep --color -E "$pa" "$flnm"`. But nothing gets printed.
Score:3
cn flag

How about giving the patterns to grep through a sub-shell, e.g.:

grep -f <(printf "%s\n" "${ptrn[@]}") FILE...
Raffa avatar
jp flag
You mean `bash` specific process substitution syntax `<(...)` ... That is smart, processing array elements into newlines and reading by `grep` as a file of patterns ... If, however, you quote the expansion of the array i.e. `<(printf "%s\n" "${ptrn[@]}")` , then each element will be passed as a single token, which might be desirable to preserve exact spaces ... +1
hr flag
Even without process substitution, you could do `printf "%s\n" "${ptrn[@]}" | grep -f - FILE` (of course, assumes you are not grepping stdin itself)
jp flag
I confirm that quoting the expansion of the array is required.
Score:2
jp flag

If your patterns/words stored as array elements are guaranteed to not have spaces or non-escaped shell special characters in them, then you can use bash's parameter expansion to pass the array elements to grep as separate individual patterns e.g. -e FN -e DS ... like so:

ptrn=("FN" "DS")

# Prepend "-e" to each array element
ptrn2="${ptrn[@]/#/-e }"

# Don't quote "$ptrn2" or it will be passed as a single token and wouldn't work.
grep --color $ptrn2 file

Or if they might contain non-escaped shell special characters, you can build the regular expressions around |(or) (splitting on all spaces as well but not failing) and use it with something like:

ptrn=("FN" "DS")

# Translate spaces " " single or multiple including spaces between words in a single array element into "|".
ptrn2="$(echo ${ptrn[@]} | tr " " "|")"

# -E enables extended regular expressions ... needed for this to work.
grep --color -E "$ptrn2" file

Or to preserve exact spaces inside each regular expression pattern passing each array element as an individual token and building an extended regular expression of them using the |(logical or), you can do something like this:

ptrn=("FN" "DS")

# Append "|" to each array element.
ptrn2="$(printf '%s|' "${ptrn[@]}")"

# Delete the last character i.e. the extra "|".
ptrn2="${ptrn2::-1}"

# -E enables extended regular expressions ... needed for this to work.
grep --color -E "$ptrn2" file
jp flag
I want the search pattern to be any allowable regex, with exact strings only being a special case. This means that the demand of not to have spaces in the patterns is too restrictive.
jp flag
Would that not be the same as `pa=$(printf '%s\|' "${ptrn[@]}")`?
jp flag
Something specific to bash must be going on because `grep -E "## FN|## DS" ./firefly.rc` works perfectly fine.
jp flag
Got things to work by removing the first `"|"`. Therefore `pa=$(printf '|%s' "${ptrn[@]}") ; pa="${pa:1}"`.
Raffa avatar
jp flag
@Backy Well done :-) ... I added extra info to the answer.
jp flag
There also exists the capability of using the `-e` flag multiple times up to the desired number of search patterns.
jp flag
How could I possibly handle the supply of multiple `-e PATTERN` expressions ?
Raffa avatar
jp flag
@Backy Other than the example in my answer. .. IIRC `-e` can also be passed as an option with `-f` to read patterns from file
jp flag
In a loop I could do `ptrn[$i]="-e ${ptrn[$i]}"`. But I wonder whether blanks in the array element could be misinterpreted, as the `-e` would not be passed separately to the pattern.
Score:2
us flag

Two options:

  • Standards-compliant way: Join the patterns with newline and provide it as single argument:

    grep -e "$(printf "%s\n" "${ptrn[@]}")" ...
    

    (This feature is specified by the POSIX standard: "The pattern_list's value shall consist of one or more patterns separated by <newline> characters ...")

  • Non-standard, but still safe way: When using a shell with arrays, like, e.g., bash, build an array of arguments to grep:

    args=()
    for p in "${ptrn[@]}"
    do
       args+=(-e "$p")
    done
    grep "${args[@]}" ...
    

    This is safe from field-splitting and globbing, and in general is how command lines should be built from variables.

jp flag
I would that say that storing in a single string, for instance: `mptrn=$( printf -- ' -e %s' "${ptrn[@]}" ) ; grep "$mptrn" ..` introduces certain problems.
muru avatar
us flag
Yes, but that's irrelevant to my answer.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.