I was just thinking yesterday that I'd appreciate a more modern alternative to find(1). Not a simplified one, though!

The original problem that led me to thinking about this, was that I wanted to do a search that would include all files below the current directory, except if they were within a specific subdirectory.

(Why not just `grep -v` for that directory? Because the subdir contained millions of files — in part, I was trying to avoid the time it would take find(1) just to do all the system calls required to list the directory!)

And yes, this is possible to specify in find(1). But no, it's not ergonomic:

find . -path ./excluded_dir -prune -o -type f -print

You can add parentheses, if you like. (But you don't have to. It seems find(1) has implicit Pratt parsing going on):

find . \( -path ./excluded_dir -prune \) -o \( -type f -print \)

This incantation entirely changed my perspective on find(1) as a tool. Until I learned this, I had never understood exactly why find(1) nags you to put its arguments in a specific order.

But this makes "what find(1) does under the covers" very clear: it's assembling your expression into a little program that it executes in the context of each dirent it encounters on its depth-first traversal. This program has filtering instructions, like `-path`, and side-effect instructions, like `-print`. You can chain as many of each as you like, and nest them arbitrarily within (implicit or explicit) logical operators.

After some further experimentation, I learned that find's programs are expression-based; that every expression implicitly evaluates to either true or false; and that any false return-value anywhere in a sub-expression, is short-circuiting for the rest of that sub-expression. (You could think of every instruction as being chained together with an implicit &&.) This means that this won't print anything:

find . -false -print

In other words, my original expression above:

find . -path ./excluded_dir -prune -o -type f -print

...is actually this, when translated to a C-like syntax:

(path("./excluded_dir") && prune()) || (type("f") && print())

As soon as I realized this, I suddenly became very annoyed with find(1)'s actual syntax. Why is find(1) demanding that I write in this weird syntax with leading dashes, various backslash-escaped brackets, etc? Why can't I "script" find(1) on the command-line, the same way I'd "script" Ruby or Perl on the command-line? Or heck, given that this whole thing is an expression — why not Lisp?

find '(or (and (path "./excluded_dir") prune) (and (type f) print))'

I do understand why find(1) was designed the way it was — it's to allow for shell variable interpolation, and to rely on shell argument tokenization.

But it's pretty easy to work around this need — just push both of these concerns "to the edge" (i.e. outside the expression itself. Like SQL statement binding!)

• Support $VAR syntax for plain references to exported env-vars.

• Support positional binding syntax (?1 ?2 ?3) for positional arguments passed after the script.

• Support named binding syntax (?foo) for positional arguments after the script that match the pattern of a variable-assignment (ala make(1) arguments):

find '(or (and (path ?excluded_dir) prune) (and (type f) (iname ?1) print))' foo excluded_dir="${bar[1]}"

I don't know about you, but I personally find this grammar 10x easier to remember than what find(1) itself has going on.