Search code examples
bashcommand-line-argumentsvariadic-functions

How to pass to bash function 2 variadic argument lists?


I'm looking for a way to abstract common command:

find src app -name "*.hs" -exec grep -H "foo" \{\} \;

to a bash/zsh function like:

find $1 -exec grep -H $2 \{\} \;

Above is not a solution as I'd like to pass lists of arguments for example with a given syntax: findgrep "src -name '.hs'" "foo". Here we could say that argument to grep is ever really going to be a single word and then pass the rest of argument list to find but there ought to be a more general way? Is there a way to sort of partial apply a bash function?

Thanks in advance!


Solution

  • In general, you cannot do that, as every individual argument is just a regular string; the shell has no distinct list type.

    To pass two "groups" of arguments, you'll need to define some sort of "sentinel" that your function can recognize as such. For example,

    findgrep () {
        args=()
        for x; do
            shift
            [[ $x == ::: ]] && break
            args+=("$x")
        done
        find "${args[@]}" -exec grep -H "$@" {} \;
    }
    
    findgrep src -name ".hs" ::: foo
    

    findgrep will add arguments to the array args until it sees the argument :::, at which point it will ignore ::: but stop iterating. Now you can use args as the first group of arguments to find, and whatever is left in $@ as the second group.

    Finding a valid sentinel could be difficult, as there's really no string that couldn't, in theory, also be a valid argument to find. An alternative would be to use an agreed-upon global array to store one or both sets.

    findgrep () {
        find "${findgrepargs[@]}" -exec grep -H "$@" {} \;
    }
    
    findgrepargs=(src -name .hs)
    findgrep foo
    

    or to use a nameref to pass the name of an aribitary array to the function.

    findgrep () {
        declare -n findargs=$1
        shift
        findgrep "${findargs[@]}" -exec grep -H "$@" {} \;
    }
    
    x=(src -name .hs)
    findgrep x foo