4

GNU's default file search utility for the command line find has a ton of options, but is still missing some - in my opinion - essential features, like sorting. It's also a hassle to use sometimes.

In the same way that ack can be seen as a modern, advanced version of grep with a cleaner interface, can anyone recommend a similar replacement for find?

(Bonus points if it also comes with functionality similar to locate, i.e. indexed search.)

  • 1
    What about GUI ? , search monkey ? – totti May 24 '15 at 6:38
  • @totti I'd prefer terminal applications because they usually allow you to quickly process the results you got further (e.g. additional filtering via sed, integration into a script, etc.). But thanks for your suggestion, I will have a look at that one as well. – pmos May 26 '15 at 19:56
  • 1
    For GUI's, catfish works ok, I'm not sure about sed filtering though. But with regular find you can always | sort, it can even handle NUL-terminated names with --files0-from=- – Xen2050 May 4 '17 at 2:19
4

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.

  • Excellent write-up, thank you very much! I've been thinking about trying zsh for some time, now I'm convinced it's a good idea. For day-to-day usage it seems like you can get to the point of what you're trying to do more quickly than in bash. – pmos May 26 '15 at 19:53

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.