2

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

$find -name "*.c"
./file1.c
./file2.c
./file3.c

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

 $echo $-            # check current options
 himBHs
 $set -f
 $echo $-            # check that f option is set
 fhimBHs
 $find -name *.c
 ./file1.c
 ./file2.c
 ./file3.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 02:08
  • I followed your suggestion but the function does not output any text and I have to stop with Ctrl+C – builder-7000 Feb 12 '19 at 02: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 02:23

2 Answers2

2

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: https://github.com/rcaloras/bash-preexec – builder-7000 Feb 12 '19 at 03:43
  • ... which also means that the function itself becomes useless (not needed). – Kusalananda Feb 25 '19 at 22:21
1

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
    done
    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" )
    done
    args=( "${args[@]:1}" )  # remove the initial "-o"

    find . "${args[@]}"
}
Kusalananda
  • 333,661