3

I'd like to know if it's possible to write a POSIX compliant script that builds a find command from its arguments.

The way to do it for Bash (and zsh) is explained in this other SE article but it's using facilities that are bash (or zsh) specific.

The original question was to be able to build a find command like this:

find -iname '*foo*' -o -iname '*bar*' -o -iname '*blah*'

The answer is with the following Bash script code:

findany() {
    local args=('-iname' "*$1*")
    shift

    while [ "$#" -gt 0 ]; do
        args+=('-o' '-iname' "*$1*")
        shift
    done

    find . "${args[@]}"
}

Is it possible to do something similar in a POSIX-compliant script?

2 Answers 2

5

There is only one array in POSIX shells - the array of arguments. You could use that here:

findany() {
    n=$#    # Save the original number of arguments
    if [ "$n" -gt 0 ]
    then
        for arg # Loop over function arguments
        do
            # Overwrite the arguments with existing args + additional options
            # with the currently processed argument
            set -- "$@" -o -iname "*$arg*"
        done
        # Remove the original set of arguments and the `-o` after them,
        # leaving only the `-iname ...` ones.
        shift $((n+1))
    fi
    # Pass `-iname ...` arguments, if any, to find
    find . "$@"
}

This should work just fine in bash or zsh as well.

4
  • and this works because the for loop makes a shadow copy of the list to loop over, so in a sense you have two lists! :) You could even clear $@ at the first iteration of the loop instead of the final shift (but would need a conditional to only do it once).
    – ilkkachu
    Commented Nov 28, 2024 at 8:30
  • You could do away with the if statement and the n variable and just do a shift inside the loop and one extra shift afterwards to remove the initial -o. I was going to say that -iname isn't POSIX, but I just noticed that it was added in the most recent update to the standard (along with -print0 no less).
    – Kusalananda
    Commented Nov 28, 2024 at 10:18
  • @Kusalananda I think that extra shift might have a non-zero exit status if no arguments were given (not sure, haven't tested it)
    – muru
    Commented Nov 28, 2024 at 10:40
  • It would have, yes, so you would use shift || true (or just ignore the exit status as you wouldn't be running with -e anyway, right?)
    – Kusalananda
    Commented Nov 28, 2024 at 10:46
2

Similar to @muru's answer but more canonical with for arg do; set -- ...; shift; done:

findany() {
  for arg do
    set -- "$@" -o -name "*$arg*"
    shift
  done
  [ "$#" -eq 0 ] || shift # remove first -o
  find . "$@"
}

Note that -iname is not POSIX, here replaced with case sensitive but standard -name (use findany '[fF][oO][oO]' for files whose name contains foo case insensitively).

1
  • You right to point out that -iname isn't POSIX, but I didn't find a way to replace it, even with awk...
    – Fravadona
    Commented Dec 9, 2024 at 18:36

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.