Take advantage of the shell! While no shell equals find
's advanced use cases, zsh can handle many, and even bash can handle the basic ones.
The fundamental feature of find
is to traverse directories recursively. This feature is found in modern interactive shells. In bash, ksh93, zsh and fish, the glob pattern **
stands for a subdirectory at any depth. Thus echo **/*
lists the same files as find .
(except that **/*
skips dot files by default), echo **/*.txt
lists the same files as find -name '*.txt
, etc. All shells sort wildcard matches. To run a command, you simply pass it the arguments as usual: instead of find -name '*.txt' -exec ls -ld {} +
, you can just run ls -ld **/*.txt
.
In bash, the **
pattern needs to be enabled with shopt -s globstar
. Put this line in your ~/.bashrc
. In ksh93, the **
pattern needs to be enabled with set -o globstar
. In fish and zsh, **
is always enabled.
The shell syntax is more straightforward when you want to execute a command on the files. If you want to list the file names, echo
separates them with spaces. If you want one file per line instead, you can use
printf '%s\n' **/*
This is a bit cumbersome to type, but you can define an alias, e.g.
alias p='printf %s\\n'
Ksh defines additional wildcard pattern syntax that gives wildcard matching the same expressive power as regular expression, plus a shortcut for negation. In bash, these patterns can be enabled with shopt -s extglob
; in zsh, with setopt ksh_glob
. Zsh has additional operators with the same expressive power, which need to be enabled with setopt extended_glob
(do put setopt extended_glob
in your ~/.zshrc
). Here are some examples of zsh extra patterns (many requiring setopt extended_glob
):
*-<8-15>.txt
— match files with a numeric suffix between 8 and 15 followed by .txt
. For example this includes foo-8.txt
and foo-08.txt
but not foo-100.txt
or foo-a.txt
. Nothing like this in find
.
*.txt~foo.txt
— matches all .txt
files except foo.txt
. Like find -name '*.txt' ! -name foo.txt
.
*.(cc|cxx)
— match .cc
and .cxx
files. Like find \( -name '*.cc' -o -name '*.cxx' \)
.
Beware that in bash ≤4.2 and fish, **/
traverses symbolic links to directories (like find -L
). Ksh93, bash ≥4.3 and zsh are safe: **/
only traverses directories. In zsh, ***/
traverses symbolic links to directories as well.
In most shells, wildcard matching is only based on the file name. The only way to match files by type is to add /
at the end of the pattern, which restricts the matches to directories (including symbolic links). Zsh is unique in offering a way to match files by type and other metadata: glob qualifiers. You can even embed arbitrary code in glob qualifiers to run code to filter and rewrite matches, though when things get that complex, the syntax is arguably worse than find
. Here are a few examples of simple uses of glob qualifiers. Recall that this is a zsh-only feature.
**/*(.)
— all regular files, like find -type f
*(.)
— all regular files in the current directory, like find -maxdepth 1 -type f
**/*(.*)
— executable regular files, like find -type f -executable
**/*(U)
— files owned by the invoking user, like find -user $USER
**/*(.m-2)
— regular files modified less than 2 days ago, like find -type f -mtime -2
*(/^F)
— empty subdirectories of the current directory, like find -mindepth 1 -maxdepth 1 -type d -empty
You can also control how the matches are sorted with a glob qualifier. For example echo *(.m-2om)
prints the names of the files that were modified in the last 2 days from youngest to oldest.
Shells omit dot files from wildcard matches unless the leading .
is present in the pattern. This can be controlled via the FIGNORE
variable in ksh, the GLOBIGNORE
variable in bash, or the glob_dots
option in zsh. In zsh, you can use the D
glob qualifier to include dot files. Unlike other shells, zsh will never include .
or ..
in wildcard matches.
If you want to prune some directories from the traversal, in zsh, you can put slashes under a Kleene star. For example, find -name .svn -prune -o -type f
becomes (^.svn/)#*(.)
The find
command remains superior if there are very many matches, because it prints files as soon as they're found, and executes commands individually (-exec … \;
) or in batches (-exec … +
). Since the shell sorts matches, it can't start displaying them until it's gathered them all, which can take a long time. Recent versions of zsh have the Y
glob qualifier to stop after a number of matches, e.g. **/*.txt(Y1)
looks for a .txt
file anywhere under the current directory and stops once it's found one.
If you want to execute a command on the files and the shell complains that the command line is too long, you can still use xargs
. Zsh offers zargs as an alternative.
find -exec mycommand {} +
find -print0 | xargs -0 mycommand
printf %s\\0 **/* | xargs -0 mycommand
zargs -- **/* -- mycommand
I'm not aware of anything that integrates find
with locate
.
sed
, integration into a script, etc.). But thanks for your suggestion, I will have a look at that one as well.| sort
, it can even handle NUL-terminated names with--files0-from=-