Search for multiple patterns from a file using find command [duplicate]

There is an option to use grep -f MY_FILE to let it search for patterns taken from a file, instead of being specified directly on the command line.

Is there an option to do something similar with the find command and let it read the patterns to search from an input file?


Solution 1:

find doesn't seem to have such capabilities built in, but you can use xargs to construct multiple find commands using arguments from a file, like:

xargs -a patterns.txt -I% find Pictures/ -name %

where patterns.txt would be a list of patterns suitable for the -name filter, one pattern per line. Pay attention that you don't have leading/trailing spaces in there, as they would be included in the pattern. An example:

*.jpg
2018-06-*
*foo*
unicorn.png

Note: While this answer looks quite easy and elegant, it got correctly pointed out in the comments that it has a few downsides:

Performance is not too great for large folders or many patterns, as it will run find once per pattern in your file, causing it to repeatedly scan the whole search folder.

Because of that, also if you have multiple patterns which could potentially overlap (like *.jpg and *foo*), files matching more than one pattern will appear that many times in the result. If you're only printing the names anyway, you could pipe the output through sort -u to remove duplicates, but if you e.g. remove those results or run any -exec commands on them, this may be more undesirable.

If any of these downsides are a problem for your use case, maybe better go for one of the alternative answers.

Explanation of the command:

  • xargs will read a list of arguments and use them to construct and run a new command line.
  • -a patterns.txt tells it to read from that file instead of the standard input.
  • -I% tells it to not simply append the arguments it read to the end of the command line, but to replace the % character in the command line you supplied with one argument. This implies creating and running one separate command per input argument.
  • find Pictures/ -name % is the command line into which we want to insert the arguments, replacing the %. You don't need quoting here, because xargs will take care that each argument it inserts will be treated as single token and not get split on its own. You can of course replace the Pictures/ with your own search directory and also use a different and/or more filters other than just -name. Because we use the insert option, you can also append actions like -exec ... to the end of the command.

Solution 2:

You can simply create a regex from the contents of your file using paste -sd'|' bar.

find ~/foo -regextype egrep -regex "^.*/($(paste -sd'|' bar))$"

The regex will be "^.*/(a|b)$"

Solution 3:

Not too recently, I've made an answer that combines multiple patterns using -regex flag in find. Based on that, we can make a small script or function to do that same job, but build up the list of patterns from file.

#!/bin/bash

read_file(){
    local full_pattern=""
    while IFS= read -r pattern; do
        if [ -z "$full_pattern"  ];then
            full_pattern="$pattern"
            continue
        fi
        full_pattern="$full_pattern\|$pattern"
    done < "$1"
    echo "$full_pattern"
}

fp=$(read_file "$1" )
find "$2" -type f -regex ".*\($fp\).*$" 

What this does:

  • we call script as findf.sh input.txt /etc, where first positional parameter is the file with patters, and second - directory where to search. GNU find assumes . directory if directory argument is omitted , so $2 isn't erequired.
  • The function read_file reads the input file that is first positional parameter to script. This builds up a pattern for -regex flag.
  • This pattern is echoed back to "main" block of the script, and saved to fp variable, and that gets passed into the find command.