Exclude directories in locate search

directoryfilesfindlocate

A search with locate finds paths in the filesystem.
Often, you know a priori you are interested in either files only, or directories only.
A 'locate' search often returns many results. It would be useful to include only one of the types in the result, because it helps shorten the output.

But there is a more interesting argument to leave out either files or directories: because the list of result paths can be ambiguous – not only in theory.

The example below is a real world case, and not unusual:

$ locate --regex --basename "xfce4-keyboard-overlay$"
/usr/local/bin/xfce4-keyboard-overlay
/usr/local/share/xfce4-keyboard-overlay

Ok, we found something! But… files, or directories?

$ file /usr/local/bin/xfce4-keyboard-overlay 
/usr/local/bin/xfce4-keyboard-overlay:   bash script

So that is a file…

$ file /usr/local/share/xfce4-keyboard-overlay
/usr/local/share/xfce4-keyboard-overlay: directory

while the second is not.

This ambiguity is making long lists of paths hard to read, so it would be really nice to filter directories out, for example using a comman line option for locate.

Does something like this exist? Even if the filter for directories is separate from locate?

At least, one could use a script to iterate all the file names to check – which may be slow.

Best Answer

With zsh:

print -rC1 ${(0)^"$(locate -0 ...)"}(N.)

(0) is a parameter expansion flag that splits on NUL characters (as we use locate -0), short for (ps:\0:).

With ^, instead of adding (N.) at the end of the array, we add it to each element.

(N.) is a glob qualifier, . to match only regular files, N to remove the element if it doesn't match (doesn't exist or is not a regular file, or we can't check). You can also use ^/ instead of . to match non-directories instead of only regular files. Or -. to determine the type of the file after symlink resolution (include symlinks to regular files in the match).

print -rC1 prints each argument raw on 1 Column (same as -l to write one per line, except in the case where there's nothing to print in which case print -C1 outputs nothing while print -l prints one empty line).

You can use any zsh glob qualifiers, but note that the ordering ones won't have any effect, since we're expanding one glob per file here, so there's only one file to sort for each.

To better identify which files are executable/directory/symlink/socket..., you can also pass the resulting files as arguments to ls -F to append some */@=... suffixes

Here assuming GNU tools and a Bourne-like shell:

elocate() {
  locate -0e "$@" |
    sort -z |
    xargs -r0 ls --quoting-style=shell --color=always -Fd1U |
    less -FIXR
}

Would define a elocate function that gives a colorized and paged version of locate with file names quoted in shell style to avoid ambiguities, and with suffixes appended to give indications of type.

Related Question