1

I'm trying to colourize my find command so I've added this alias function to my .bashrc.

# liberate your find
function find
{
    command find $@ -exec ls --color=auto -d {} \;
}

But there is unexpected behavior using this code. It drops my quotes.

GNU bash, version 4.4.23(1)-release (x86_64-pc-linux-gnu)

Use my function:

find ./ -name '*.pl' -or -name '*.pm'

Result:

./lib/cover.pm
./lib/db.pm

Using the same find function but built-in:

command find ./ -name '*.pl' -or -name '*.pm'

Result:

./auth.pl
./index.pl
./title.pl
./lib/cover.pm
./lib/db.pm
./fs2db.pl

So the second variant didn't eat my quotes and works as it should.

2 Answers2

2

For reproducing the problem I created all the files as shown in the longer Result in the question.

When I define the function(*) as

function find
{
    command find $@ -exec ls --color=auto -d {} \;
}

and execute

find ./ -name '*.pl' -or -name '*.pm'

I get an error message

find: paths must precede expression: fs2db.pl
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

because *.pl gets expanded to auth.pl fs2db.pl index.pl title.pl by the shell.

I had to change the function to

function find
{
    command find "$@" -exec ls --color=auto -d {} \;
}

to reproduce your problem. (Maybe this depends on the shell. I tested with bash 4.4.19(3)-release)

After set -x you can see what the shell does when executing your function:

$ find ./ -name '*.pl' -or -name '*.pm'
+ find ./ -name '*.pl' -or -name '*.pm'
+ command find ./ -name '*.pl' -or -name '*.pm' -exec ls --color=auto -d '{}' ';'
+ find ./ -name '*.pl' -or -name '*.pm' -exec ls --color=auto -d '{}' ';'
./lib/cover.pm
./lib/db.pm

The difference between executing your function and executing the find command directly is that your function appends an -exec action with the implicit -a (AND) operator. Without an explicit action, find prints all matching results.

You see a result of the operator precedence -a (AND) higher than -o (=-or, OR)

You can compare the output of these 3 commands

command find ./ -name '*.pl' -or -name '*.pm'
command find ./ -name '*.pl' -or -name '*.pm' -print
command find ./ \( -name '*.pl' -or -name '*.pm' \) -print

see http://man7.org/linux/man-pages/man1/find.1.html#NON-BUGS

You can call your function as

find ./ \( -name '*.pl' -or -name '*.pm' \)

to avoid the problem.


(*) This function definition is copied from the question.
You should use the portable POSIX style find() { ... } instead, unless there is a specific requirement for the Korn shell style function find { ... }.

Bodo
  • 9,287
  • 1
  • 13
  • 29
1

As written, the -exec primary only applies to the code on the right of the -or operator. You need to parenthesize your arguments so that -exec applies to everything that matches. You also need to extract the path from the other arguments (which gets messy if you want to specify multiple paths, as your function would have to decide where to put the parentheses; distinguishing between paths and other expressions would amount to reimplementing a good chunk of find's parsing. I'll assume you are only passing a single path here).

find ()
{
    path=$1
    shift
    command find "$path" \( "$@" \) -exec ls --color=auto -d {} \;
}

Alternatively, you can put the parentheses in the command line, with no change to your current definition.

find ./ \( -name '*.pl' -or -name '*.pm' \)

Your original function runs

find ./ -name '*.pl' -or -name '*.pm' -exec ls --color=auto -d {} \;

which is equivalent to

find ./ -name '*.pl' -or \( -name '*.pm' -exec ls --color=auto -d {} \; \)

with no implicit -print.

chepner
  • 497,756
  • 71
  • 530
  • 681