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 -rl ${(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.

print -rl prints each argument raw on a separate 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.

(note that it may fail if the last file reported by locate ends in newline characters (a command substitution misfeature present in all shells)).


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).

  • can you add some info on what the "IFS=" after your while statement does? – robert Mar 27 '15 at 9:58
  • I have done so. – G-Man Mar 27 '15 at 11:51
  • backslashes will still be a problem with many echo implementations. You should use printf for arbitrary data. – Stéphane Chazelas Mar 27 '15 at 12:20
  • there may be a solution to your newlines problem by using "--null" parameter to locate and augment your read as suggested here transnum.blogspot.ie/2008/11/… – robert Mar 27 '15 at 12:33
  • @StéphaneChazelas: Good point. Fixed. – G-Man Mar 27 '15 at 12:37
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 Mar 26 '15 at 21:23
  • 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
  • 1
    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

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.

  • 3
    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
  • 2
    @StéphaneChazelas +1 for the courage to run "random codez from the internets" as root ;) (scnr) – Volker Siegel Apr 4 '15 at 2:01

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 Mar 26 '15 at 22:56
  • Sorry. My mistake. Corrected. – Tristan Storch Mar 26 '15 at 23:12

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 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.