-1

I have two variables, passed as command line arguments to a bash function, containing exclude and include information on file type extension, using comma as delimiter.

excl="el"
excl="el,htm"
incl="texi,org"

I want to use excl and incl to parse the exclude and include options for grep.

excl="el,htm"
incl="texi,org"
grep -hir --exclude=\*.{el,htm} --include=\*.{org,texi} "$@"

excl="el"
incl="texi,org"
grep -hir --exclude=\*.el --include=\*.{org,texi} "$@"
3
  • You can pass what you want to exclude and include by specifying in excl and incl. To reflect excl="el,htm" we would then want to have --exclude=\*.{el,htm}.
    – Pietru
    Jul 24 at 12:25
  • Specifying both include and exclude doesn't make sense here, as it appears both are being to limited to filename "extensions" by prepending *. (e.g. a filename excluded by matching *.el would never be included by matching *.org).
    – rowboat
    Jul 24 at 14:14
  • I can see your reasoning that it has to be one or the other.
    – Pietru
    Jul 24 at 14:19
2

You really need to use arrays to keep track of strings that are separate. Using a single string to hold multiple values makes it impossible to use the code with suffixes that have the delimiter embedded (for example the suffix ,v, which CVS and RCS files have).

exclude=( .el .htm )
include=( .texi .org )

opts=( -h -i -r )

for ext in "${exclude[@]}"; do
    opts+=( --exclude="*$ext" )
done
for ext in "${include[@]}"; do
    opts+=( --include="*$ext" )
done

grep "${opts[@]}" "$@"

This stores your filename suffixes in two arrays, exclude and include. It then iterates over the elements of both arrays, adding the appropriate options to a third array called opts. This third array is then used in the call to grep.

The double quoting used when expanding an array, as in e.g. "${opts[@]}", ensures that each individual array element is double quoted and not further split or globbed by the shell.


As a function that takes the lists of included and excluded filename suffixes as two separate arguments:

call_grep () {
    local -n include="$1"
    local -n exclude="$2"

    shift 2

    local opts=( -h -i -r )
    local ext

    for ext in "${exclude[@]}"; do
        opts+=( --exclude="*$ext" )
    done
    for ext in "${include[@]}"; do
        opts+=( --include="*$ext" )
    done
    
    grep "${opts[@]}" "$@"
}

The main part of the script:

excl=( .el .htm )
incl=( .texi .org )

call_grep incl excl more arguments here

This sets up the function call_grep to take the names of two arrays. The first array is the array of filename suffixes to include and the second one is the array of suffixes to exclude. The function receives the names of these arrays and uses them to set up two local name-reference variables. The third argument onwards are passed to grep as is.


Again, but using real command line parsing in call_grep:

call_grep () {
        OPTIND=1

        local ext opt
        local opts=( -h -i -r )

        while getopts 'i:e:' opt; do
                case $opt in
                        i)
                                local -n include="$OPTARG"
                                for ext in "${include[@]}"; do
                                        opts+=( --include="*$ext" )
                                done
                                ;;
                        e)
                                local -n exclude="$OPTARG"
                                for ext in "${exclude[@]}"; do
                                        opts+=( --exclude="*$ext" )
                                done
                                ;;
                        *)
                                echo 'Error in option parsing' >&2
                                exit 1
                esac
        done

        shift "$(( OPTIND - 1 ))"

        grep "${opts[@]}" "$@"
}

The function now takes a -i and a -e argument (both are optional). The option argument to each should be the name of an array containing filename suffixes to include or exclude.

You would use this as

excl=( .el .htm )
incl=( .texi .org )

call_grep -i incl -e excl -- more arguments here

You would need to use -- to delimit the function's arguments from those that should be passed directly to grep.


If all you want is a simplified way of calling grep, which does not mention shell patterns or long options:

call_grep () {
        OPTIND=1

        while getopts 'i:e:' opt; do
                case $opt in
                        i)      opts+=( --include="*$OPTARG" ) ;;
                        e)      opts+=( --exclude="*$OPTARG" ) ;;
                        *)      echo 'error' >&2; exit 1
                esac
        done

        shift "$(( OPTIND - 1 ))"

        grep "${opts[@]}" "$@"
}

You'd use -i suffix repeatedly to include multiple suffixes, and similarly for excluding suffixes. For example,

call_grep -i .texi -e .el -e .htm -i .org -- other arguments for grep here

or

call_grep -i{.texi,.org} -e{.htm,.el} -- more here for grep
25
  • 1
    Consider adding a -- after opts and before $@?
    – Jeff Schaller
    Jul 24 at 14:17
  • Using array is fair enough. But encl and excl are being passed from bash function arguments.
    – Pietru
    Jul 24 at 14:22
  • 1
    @JeffSchaller What if $@ contains further options?
    – Kusalananda
    Jul 24 at 14:25
  • @Pietru Uh? You might want to update your question, because it does not mention anything about that.
    – Kusalananda
    Jul 24 at 14:26
  • @Pietru Could the array be built when parsing the arguments? Your function would then require multiple options to be given for multiple suffixes.
    – rowboat
    Jul 24 at 14:30
0

If you want to take the list of extensions in the form el,htm, you might be tempted to do something like this to use brace expansion:

eval 'echo grep -hir --exclude=*.{'"$excl"'} "$@"'

but apart from the usual caveats with eval, like the fact that an unquoted semicolon in $excl would mess up the syntax by terminating the grep command, it also wouldn't work if $excl contained only one extension, or none at all, since {foo} and {} are not processed as brace expansions. So, let's forget about the brace expansion.


We're really going to end up building the list of arguments in an array, like in Kusalananda's answer above, and in Conditionally pass params to a script

Keeping with the comma as separator, the simple way to split on the comma is with read -a. It produces an array and we need another to build the list of options in.

excl="el,htm"
IFS=, read -r -a exts <<< "$excl"
opts=()
for ext in "${exts[@]}"; do 
     opts+=(--exclude="*.$ext")
done
grep -hir "${opts[@]}" "$@"

As you noticed in comments, extensions like RCS's ,v would be a problem here, and so would anything else that doesn't start with a dot. If you still want to give the extensions as one string, you could switch to accepting e.g. a colon, semicolon or a space as the delimiter, and require to user to enter the dot explicitly if they want it, so e.g. with : as the delimiter:

excl=".html:,v"
IFS=: read -r -a exts <<< "$excl"
opts=()
for ext in "${exts[@]}"; do 
     opts+=(--exclude="*$ext")
done
grep -hir "${opts[@]}" "$@"

Of course, what ever character you choose to use as separator, can't be part of an extension, but semicolons and colons are probably rarer still than commas.

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.