Below are some examples of a find command I'm trying to run. I'm looking for different ways that I might be able to use globbing to generate (as an example) a find command with predicates joined together.

Doesn't work because find needs a -name before each one and a -o between each.

find . -name \*.{sh,pl,sql}
#find . -name *.sh *.pl *.sql

Doesn't work because of trailing -o. Could pull a guaranteed to fail argument there, but not ideal. Also, my shortcut is now longer than my output.

find . `for X in {sh,pl,sql}; do echo -name \\\*.$X -o ;done`
#find . -name \*.sh -o -name \*.pl -o -name \*.sql -o

Fails because they're grouped as one argument (find: unknown predicate '-name *.sh'). Also, still exhibiting a lack of joining with -o.

find . -name\ \*.{sh,pl,sql}

Works, but doesn't involve globbing (re: non-answer):

find .  -regex '.*\(sh\|pl\|sql\)'    

The -false idea is the key, IMHO. I'm just adding to it:

find . -false $(echo "-o -name *."{sh,pl,sql})

You just quote everything to make bash repeat the whole pattern, including -o -name, and then "break" the grouping made by the quoting by returning it from a subshell. The problem with this approach is that quotes in the pattern won't work.

EDIT: See Michał Šrajer's comment for another pitfall of this solution. Note that you can't just put a backslash before the star: the command substitution returns either a *, which will get expanded, or a \*, which will get passed as-is to find (!). At least, that's how my local bash works.

This is the best I can do:

 (GLOBIGNORE='*:.*'; find . \( -false $(echo "-o -name *."{sh,pl,sql,xml}) \) -print)

Good luck :)

Btw, if you are not going to add more parameters to find, of course just use xargs; in that case it works perfectly, with quotes and everything:

echo "-o -name *."{sh,pl,sql} | xargs find . -false
  • This is awesome trick! (+1). Just note, that you need to run in in directory without any *.sh *.pl nor *.sh in PWD. – Michał Šrajer Jan 20 '12 at 22:12
  • Damn you're right! This is what happens when you try to answer a bash-related question and forget you are using zsh (it works correctly on zsh). I'll try to figure a solution for this problem later. – angus Jan 20 '12 at 22:38

find does no use a glob(). It uses fnmatch(). This is why the only meta characters available in -name predicate are *, ?, [ and ].

To resolve trailing -o I suggest to put it in brackets and add extra -false predicate:

( -name AAA -o -name BBB -o -name CCC -o -false )
  • find is my example command, but the focus is on the globbing facility of bash used to generate the find command. Adding -false would work, though. (-true just prints every damn thing). – Jeff Ferland Jan 20 '12 at 16:29
  • @JeffFerland: sure, I mean -false. Good catch. – Michał Šrajer Jan 20 '12 at 16:31
  • Turns out appending -false stops results from working as well -- nothing will show even if files do match. – Jeff Ferland Jan 20 '12 at 17:03

I'm assuming that find is an example here and you're not interested in a solution that lets you combine the patterns into one find argument (that would be -regex). There are two possible approaches: you can build a string and let the shell split it into multiple arguments wherever there's whitespace, or you can build an array.

With the string building approach, using command substitution, it doesn't come much easier than your attempt.

find . $(for X in sh pl sql; do echo "-name \\*.$X" -o; done | sed 's/-o *$//')

You could also build the string in a loop before the find command; I think that would make for a clearer script¹.

for x in sh pl sql; do
  find_options="$find_options -o -name \\*.$x"
find_options=${find_options# -o}
find . $find_options

If your shell supports arrays (i.e. it's bash or ksh or zsh), I recommend using them instead, so you won't have to insert extra quoting.

for x in sh pl sql; do
  find_options+=(-o -name "*.$x")
unset "find_options[0]"
find . "${find_options[@]}"

¹ On the command line, you don't need to worry about portability and can quote as little as needed since you don't have to worry about user input. And you can handle repetition through copy-paste.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.