Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I am trying to call find with a few variables. So far I had this:

DIRECTORY="./ "
FILENAME=-regex" .*test.*"
find $DIRECTORY $FILENAME

Which works fine. If I change the filename to :

FILENAME=-regex" .*"

to list all the files in the directory, I get an error:

find: paths must precede expression: ..

What am I doing wrong?

share|improve this question

3 Answers 3

up vote 1 down vote accepted

The content of the FILENAME variable is

-regex .*test.*

The quotes are not part of the value of the variable, they are part of the shell syntax. The shell parsed the line, interpreting the quotes, before it even saw that it was an assignment.

When you then run the line find $DIRECTORY $FILENAME, $FILENAME is first replaced by its value. Then the shell performs two expansion steps on the value of the variable: it splits it into separate words where the variable contains whitespace (which you happen to want), and it performs globbing (i.e. wildcard expansion) on each word, so .* is expanded to the list of file names in the current directory that begin with a dot.

You didn't run into problems with your first snippet because the pattern .*test.* happened not to match any file, so it expanded to itself.

The robust solution is to use an array variable instead of a string variable when you need to store multiple words.

DIRECTORY="./"
EXPRESSION=("-regex" ".*test.*")
find "$DIRECTORY" "${EXPRESSION[@]}"

or if you want to allow multiple directories:

DIRECTORIES=("./")
EXPRESSION=("-regex" ".*test.*")
find "${DIRECTORIES[@]}" "${EXPRESSION[@]}"

"${EXPRESSION[@]}" expands to the list of elements in the array, in separate words.

Note that you should always use double quotes around variable substitutions "$foo" and command susbtitutions "$(foo)", unless you know you want word splitting and globbing to happen to the values.

Here, since you're using bash, an array is the right solution. In a shell that doesn't have arrays, there are several possibilities. You can use the positional parameters, which are effectively an array variable, but that's only a possibility if you aren't using them for something else:

set -- "./"                  # start with the directory
set -- "$@" -regex '.*'      # add the expression
find "$@"                    # go

Another method is to properly quote the content of the variable to prepare it for later word splitting and globbing. You can use a backslash before a wildcard character (?*[) to make it be interpreted literally.

DIRECTORY=./
EXPRESSION='-regex .\*'
find "$DIRECTORY" $EXPRESSION

Another method is to disable globbing with set -f. before executing the find line. Be sure to turn it back on with set +f if you need it afterwards. Note that since you're using word splitting, you won't be able to put whitespace characters as part of an argument to a find predicate.

DIRECTORY=./
EXPRESSION='-regex .*'
set -f
find "$DIRECTORY" $EXPRESSION

See also I'm trying to put a command in a variable, but the complex cases always fail! in the bash FAQ.

share|improve this answer

Either

directory=.
match=(-regex '.*test.*')
find "$directory" "${match[@]}"

Or (here using "," just to make a point):

directory=.
match='-regex,.*test.*'
IFS=,
set -f
find "$directory" $match

Or:

directory=.
match="-regex '.*test.*'"
eval 'find "$directory" '"$match"
share|improve this answer

You are not quoting the variables. Therefore, what the shell sees is

find ./  -regex .*

And it expands .* to . ...

share|improve this answer
1  
so how should it be? –  Bartlomiej Lewandowski Oct 27 '12 at 13:40
    
For example: DIRECTORY='./' ; FILENAME='-regex ".*/p.*"' ; eval find $DIRECTORY $FILENAME. –  choroba Oct 27 '12 at 13:45

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.