I have a directory with files file1.c, file2.c and file3.c. The command find outputs:

$find -name "*.c"

Then I would like to use find without the quotes around .*c. For this I use set -f:

 $echo $-            # check current options
 $set -f
 $echo $-            # check that f option is set
 $find -name *.c
 $set +f             # unset f option

I tried the same commands inside a function in .bashrc:

find() {
    set -f
    eval command find $@
    set +f

but testing it gave the error:

$ . ~/.bashrc && find -name *c
find: paths must precede expression: file1.c
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression

What is the cause for this error in the function? find version: GNU 4.6.0

  • Remove eval, and use "$(command -v find)" "$@" – cuonglm Feb 12 '19 at 2:08
  • I followed your suggestion but the function does not output any text and I have to stop with Ctrl+C – sergio Feb 12 '19 at 2:15
  • ah right, command -v find still report your find function, maybe better to rename the function to something else other than find. In any case, I don't think it's a good solution, because *.c still be evaluated before passing to your function, so it won't work. – cuonglm Feb 12 '19 at 2:23

You didn't say, but you must be calling the function like this:

find -name *.c

But globbing hasn't been turned off yet, so the shell expands the *.c before the call. So the find command sees '-name' followed by three arguments, thus the error message.

You could use a backslash instead of quotes.

find -name \*.c
  • This worked but i ended up doing: alias find='set -f; find' and preexec() { 'set +f'; }, where preexec() comes from: github.com/rcaloras/bash-preexec – sergio Feb 12 '19 at 3:43
  • ... which also means that the function itself becomes useless (not needed). – Kusalananda Feb 25 '19 at 22:21

Your function disables filename globbing, but you call it with a glob that the shell expands (filename globbing is not turned off in the shell that you call it from).

In other words, the glob in your command

find -name *c

is expanded before your find function is called. This results in a call to find inside the function that the utility can't understand.

You could solve that by calling your function with a quoted argument:

find -name "*c"

but notice how now the function is totally useless as it just replicates the command that you've already typed in.

In addition to being useless, it's also wrong. Since $@ is unquoted in your code, it will split the arguments on whitespaces (by default). This means that you can't use e.g.

find -name "* *"

to find names with spaces in them.

Also note that due to the eval, the shell would perform expansions on the contents of $@. This means that

find -name '$(echo hello)'

would not find files called $(echo hello) but hello.

Another thing is that if the calling shell already uses set -f, then this will be disabled by the function.

Let's create a useful function instead. A function that looks for a number of filename patterns, for example:

myfind "*.c" "*.txt" "* *"

We'd like the above command to return the pathnames that end with .c or .txt or that contain spaces.

Here's the function:

myfind () {
    # Replace each positional parameter "$pattern"
    # with -o -name "$pattern"
    for pattern do
        set -- "$@" -o -name "$pattern"
    shift  # shift off the initial "-o"

    find . "$@"

Given the above call of the function, it will end up executing

find . -name '*.c' -o -name '*.txt' -o -name '* *'

If you are more comfortable using bash arrays for lists, and don't mind a bit more typing:

myfind () {
    local -a args

    # Build the argument list for find by adding
    # -o -name "$pattern"
    # for each positional parameter 
    for pattern do
        args+=( -o -name "$pattern" )
    args=( "${args[@]:1}" )  # remove the initial "-o"

    find . "${args[@]}"

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.