简体   繁体   中英

Bash parameter expansion in brackets not working as expected

I am writing a script that wraps the find command to search for specific source file types under a given directory. A sample invocation would be :

./find_them.sh --java --flex --xml dir1

The above command would search for .java, .as and .xml files under dir1.

To do this manually I came up with the following find command :

find dir1 -type f -a \( -name "*.java" -o -name "*.as" -o -name "*.xml" \) 

As I am doing this in a script where I want to be able specify different file sets to search for you end up with the following structure :

find_cmd_file_sets=$(decode_file_sets) # Assume this creates a string with the file sets e.g. -name "*.java" -o -name "*.as" etc
dirs=$(get_search_dirs) # assume this gives you the list of dirs to search, defaulting to the current directory

for dir in $dirs 
do
    find $dir -type f -a \( $find_cmd_file_sets \)
done

The above script doesn't behave as expected, you execute the script and the find command churns for a while before returning no results. I'm certain the equivalents of decode_file_sets and get_search_dirs I've created are generating the correct results.

A simpler example if to execute the following directly in a bash shell

file_sets=' -name "*.java" -o -name "*.as" '
find dir -type f -a \( $file_sets \) # Returns no result
# Executing result of below command directly in the shell returns correct result
echo find dir -type f -a \\\( $file_sets \\\) 

I don't understand why variable expansion in brackets of the find command would change the result. If it makes any difference I am using git-bash under Windows.

This is really frustrating. Any help would be much appreciated. Most importantly I would like to understand why the variable expansion of $file_sets is behaving as it is.

Hope this will work, Its tested on bash.

file_sets=' -name "*.java" -o -name "*.as" '
command=`echo "find $dir -type f -a \( $file_sets \)"`

eval $command

TLDR: Don't use quotes in find_cmd_file_sets variable and disable pathname expansion ( set -f ) before calling find .

When you have "special" character in a variable content and then you try to expand that variable without quotes than bash will surround each word with "special" character with single quotes, eg:

#!/usr/bin/env bash
set -x
VAR='abc "def"'
echo $VAR

The output is:

+ VAR='abc "def"'
+ echo abc '"def"'
abc "def"

As you can see, bash surrounded "def" with single quotes. In your case, the call to find command becomes:

find ... -name '"*.java"' ...

So it tries to find files which start with " and end with .java"

To prevent that behavior, the only thing you can do (which I'm aware of) is to use double quotes when expanding the variable, eg:

#!/usr/bin/env bash
set -x
VAR='abc "def"'
echo "$VAR"

The output is:

+ VAR='abc "def"'
+ echo 'abc "def"'
abc "def"

The only problem, as you probably noticed already, is that now the whole variable is in quotes and is treated as single argument. So this won't work in your find command.

The only option left is to not use quotes, neither in variable content nor when expanding the variable. But then, of course, you have a problem with pathname expansion:

#!/usr/bin/env bash
set -x
VAR='abc *.java'
echo $VAR

The output is:

+ VAR='abc *.java'
+ echo abc file1.java file2.java
abc file1.java file2.java

Fortunately you can disable pathname expansion using set -f :

#!/usr/bin/env bash
set -x
VAR='abc *.java'
set -f
echo $VAR

The output is:

+ VAR='abc *.java'
+ set -f
+ echo abc '*.java'
abc *.java

To sum up, the following should work:

#!/usr/bin/env bash

pattern='-name *.java'
dir="my_project"
set -f
find "$dir" -type f -a \( $pattern \)

bash arrays were introduced to allow this kind of nested quoting:

file_sets=( -name "*.java" -o -name "*.as" )
find dir -type f -a \( "${file_sets[@]}" \)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM