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$"

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.


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.


This is about as inelegant as the other answers, but maybe less inefficient:

locate --regex --basename "xfce4-keyboard-overlay$" | 
        while IFS= read -r f; do [ -f "$f" ] && printf "%s\n" "$f"; done

(broken into two lines for readability).  The above will handle names containing spaces.  The IFS= seems to be necessary to handle names with trailing spaces, and, of course, the -r lets you handle backslashes.

The “let’s pipe locate into something” approach may be doomed to fail if pathnames containing newlines are present.

For more information on IFS, read sh(1) or bash(1) (by typing man sh or man bash into a *nix system, and/or reading it here, here, here, and/or here).  Then read Understanding IFS and Bash: read line by line, with IFS on Stack Exchange (focus on the answers with more than 5 votes), and, if you still haven’t had enough, check out IFS on Greg’s Wiki and IFS search results on the Bash Hackers Wiki (not on Stack Exchange).


xargs will repeat command for each line if you specify -L 1 or -i parameter.

See here

$ locate --regex --basename "xfce4-keyboard-overlay$" | xargs -i bash -c '(test -d "{}" && echo "{}")'

Admittedly, it is kicking of a new shell for each file, but it does have the benefit of being nice and compact.

EDIT: I wasn't quite happy with that answer because it was kicking of a new shell for each file. This should only have two processes:

$ locate --regex --basename "xfce4-keyboard-overlay$" | xargs -i echo 'test -d "{}" && echo "{}"' | bash

Of course it would be nice if we could avoid kicking of an interpreter altogether, but xargs seems to be hamstrung in its ability to chain commands.

  • 5
    That one just rebooted my machine (there was a file called /home/evil/$(reboot)/xfce4-keyboard-overlay and I foolishly ran that as root). – Stéphane Chazelas Mar 27 '15 at 12:25
  • 3
    @StéphaneChazelas +1 for the courage to run "random codez from the internets" as root ;) (scnr) – Volker Siegel Apr 4 '15 at 2:01
locate --null --regex --basename "xfce4-keyboard-overlay$" |
  xargs -r0 sh -c 'find "$@" -prune ! -type d' sh
  • Actually, it's even more dirty than it looks... but a good inspitation. Let's pretend it's pseudocode, then it's helpful :) – Volker Siegel Mar 26 '15 at 20:41
  • 1
    @Volker: I agree that it's bad: it will list /usr/local/share/xfce4-keyboard-overlay and every subdirectory thereof, in your example. Adding -maxdepth 0 helps. – G-Man Says 'Reinstate Monica' Mar 26 '15 at 21:23
  • 1
    It goes even better... :D locate --regex --basename "xfce4-keyboard-overlay$" | xargs -I % sh -c "test -d % && echo %" – FloHimself Mar 26 '15 at 22:10
  • 2
    Using xargs with find was a good idea, I've edited it to make it robust. Hope you don't mind. – Stéphane Chazelas Mar 27 '15 at 12:34

My two cents:

while IFS= read i; \
do \
  if [ -f "$i" ]; \
  then \
    echo "$i"; \
  fi; \
done < <(locate --regex --basename "xfce4-keyboard-overlay$")

This is more or less the way G-Man did it combined with process substitution.

  • Actually, this is more or less the way I did it, combined with process substitution, minus the ability to handle filenames containing backslashes or having trailing white space. Also, note that the question title says "exclude directories", and this answer includes only directories. – G-Man Says 'Reinstate Monica' Mar 26 '15 at 22:56
  • Sorry. My mistake. Corrected. – Tristan Storch Mar 26 '15 at 23:12


file $(locate -r 'xfce4-keyboard-overlay$') | grep -v directory$ | awk -F: '{ $(NF--)=""; print }'


  1. locate ... finds all files and directories
  2. file $(...) appends a message at the end of each line with the type of file
  3. grep -v ... reverse-filters lines containing directory$
  4. awk ... removes what file ... appended

What if you combine locate with file and grep?...

$ for f in `locate --regex --basename "xfce4-keyboard-overlay$"`; do file $f; done | grep -vi directory
  • I did not test, but I think that may be slow, because it creates a process for file for every single path. Note that there are often many lines of results for locate. My current test is searching for "gnome", giving about 73000 paths to test. – Volker Siegel Mar 26 '15 at 21:08
  • 2
    @Volker: It's worse than that: for every $f that is a file, the file program will open that file and read from it. This is grossly expensive when all you need to do is a stat(). … … … … Also, this will give wrong results for files that contain "directory" in their names (such as "phone_directory"). … … … … … (Also, the for f in `…`; do … syntax cannot handle names containing spaces.) – G-Man Says 'Reinstate Monica' Mar 26 '15 at 21:28

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.