16

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.

4

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.

| improve this answer | |
3

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

| improve this answer | |
2
locate --null --regex --basename "xfce4-keyboard-overlay$" |
  xargs -r0 sh -c 'find "$@" -prune ! -type d' sh
| improve this answer | |
  • 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
  • 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
1

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.

| improve this answer | |
  • 4
    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
0

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.

| improve this answer | |
  • 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
-1

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
| improve this answer | |
  • 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.