0

How to add optional grouped argument lists to find?

For example, using an array of base arguments that define files that will always be found, no matter what other variable conditions are specified.

# args to find any files ending _count or _scan

._. fargs=( -type f -name '*_count' -o -name '*_scan' )
._. cd /sys/fs/ext4/sda1
._. find . \( "${fargs[@]}" \) 2> /dev/null
./mb_min_to_scan
./errors_count
./mb_max_to_scan
./msg_count
./warning_count

Add optional additional arguments

._. fpterm=warning
._. fpterm2=max
._. fANDargs=( -ipath "*$fpterm*" -o -ipath "*$fpterm2*" )

Apply the optional args in parenthesis to execute find with the form: ( a or b or c ) and ( d or e or f )

._. find . \( "${fargs[@]}" \) \( "${fANDargs[@]}" \) 2> /dev/null
./dm-1/mb_max_to_scan
./dm-1/warning_count
./dm-0/mb_max_to_scan
./dm-0/warning_count
./sda1/mb_max_to_scan
./sda1/warning_count

But when the optional arg array is empty, it stops other conditions being met, and no files are found at all:

._. fANDargs=()
._. find . \( "${fargs[@]}" \) \( "${fANDargs[@]}" \) 2> /dev/null
._. 

The empty parenthesis cause the problem:

._. find . \( "${fargs[@]}" \) \( \) 2> /dev/null
._.

Whereas, ordinarily, an empty arg array does not stop other conditions being met:

._. fANDargs=()   
._. echo "${fANDargs[@]}"

._. find . \( "${fargs[@]}" \) "${fANDargs[@]}" 2> /dev/null
...
./sda1/mb_min_to_scan
./sda1/errors_count
./sda1/mb_max_to_scan
./sda1/msg_count
./sda1/warning_count

So if the brackets could be added only when the optional args array was not empty, the command might work.

But how can the brackets be added conditionally?

It doesn't seem to work when you add the parenthesis to the array, for example:

._. fpterm=warning
._. fANDargs=()
._. [ ! -z $fpterm ] && fANDargs+=( '\(' -ipath "*$fpterm*" '\)' )
._. echo "${fANDargs[@]}"
\( -ipath *warning* \)
._. find . \( "${fargs[@]}" \) "${fANDargs[@]}" 2> /dev/null
._.

1 Answer 1

3
[ ! -z $fpterm ] && fANDargs+=( '\(' -ipath "*$fpterm*" '\)' )

You're double-quoting here. Try with just one layer:

fANDargs+=( \(  -ipath "*$fpterm*" \)  )   # or  
fANDargs+=( '(' -ipath "*$fpterm*" ')' )   # or with "(" and ")"

(Also you should use quotes in [ ! -z "$fpterm" ])

In general, you'd use the words in the array assignment exactly as you'd use them directly in a command. Using "${array[@]}" with the quotes (as you did) would then expand to the contents of the array without further modification.

(This is the part where people often seem to go wrong, they seem to expect the shell processes quotes on the results of expansions. It doesn't, it's more like a "normal" programming language here. Provided you use the quotes to get rid of the split+glob nuisance.)

Of course, the background here is that what find itself wants to see is just the parens as-is (as distinct args). It doesn't need to see the quotes, but the shell needs them since the parenthesis are special characters that are part of the shell syntax. So you can use somecmd { foo bar } without quotes, but you do need some quoting or escaping in somecmd \( foo bar \).


BTW, GNU find warns about an empty set of parens:

$ find . \( \)
find: invalid expression; empty parentheses are not allowed.

And, well, logically it seems odd anyway. My first instinct would be to say that the empty parens must evaluate as falsy, which would make find \( whatever \) \( \) always match nothing. But it could be interpreted as truthy too, and that seems to be what Busybox does, busybox find . \( \) -name '*.txt' gives the same as busybox find . -name '*.txt'.

2
  • What a relief to find it was just a little mistake :)
    – markling
    Commented Mar 27, 2022 at 17:14
  • I can't find the button where you add kisses.
    – markling
    Commented Mar 27, 2022 at 17:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .