Bash – Single-Quotes Parameter with Globbing Value

bashshell-scriptwildcards

I'm trying to write a code search script, with configurable directories and files that are excluded. The main part of this script is the following line:

out=$(grep -TInr$C$W --color=always \
    --exclude={translations} \
    --exclude-dir={build,tmp} \
    "$SEARCH")

The $C and $W variables are set by script parameters to configure case insensitivity and exact word matching, and $SEARCH is simply the remaining parameters, which will be the search string. However, my implementation to ignore certain files does not quite work yet.

To exclude files from search, I'm trying to use a ~/.codesearch_config file, which looks like this:

#!/bin/bash

if [[ $PWD == "$HOME/project"* ]]; then
    exclude_directories={.git,build,tmp}
    exclude_files={translations}
fi

The idea here, of course, is that depending on your current working directory, a certain set of excludes will be loaded. But when trying to add these to the script as such:

--exclude=$exclude_files

the whole parameter will be put in single quotes by bash (tested with the -x option to debug) like this:

grep -TInrw --color=always '--exclude={translations}' '--exclude-dir={build,tmp}' search_term

What I want it to do is expand that to --exclude-dir=build --exclude-dir=tmp. If I manually add the values of those $exclude_ variables to the command, it expands just fine; The issue is just that the single quotes are placed around my parameter and glob. How can I prevent this?

Best Answer

Try using arrays for your exclusions, and expand them into --exclude-dir and --exclude options.

e.g. in your ~/.codesearch_config script (presumably this is sourced by your main script?):

#! /bin/bash

# temporary array variables
declare -a __exclude_directories
declare -a __exclude_files

if [[ "$PWD" == "$HOME/project"* ]]; then
    __exclude_directories=(.git build tmp)
    __exclude_files=(translations)
elif [[ "$PWD" == "/some/where/else" ]]; then
    __exclude_directories=(foo bar)
    __exclude_files=(abc.txt def.txt xyz.txt)
fi

exclude_directories=''
exclude_files=''

for i in "${__exclude_directories[@]}" ; do
    exclude_directories+=" --exclude-dir '$i'"
done

for i in "${__exclude_files[@]}" ; do
    exclude_files+=" --exclude '$i'"
done

unset __exclude_directories
unset __exclude_files

# comment out or delete the next two lines after you've verified
# that the output is correct.
echo $exclude_directories 
echo $exclude_files

Later, you would use them like this:

out=$(grep -TInr$C$W --color=always \
      $exclude_files \
      $exclude_directories \
      "$SEARCH")

Note: there are no quotes around the $exclude_* variables here, otherwise they will be treated as a single argument each rather than multiple --exclude and --exclude-dir arguments. This is one of the very few situations where you do not want to and should not double-quote your variables (i.e. when you are constructing a command line in a variable or variables).

In almost all other cases, you should, as a matter of deeply-ingrained habit, double-quote your variables.

Related Question