13

On a UNIX system, "locate" searches the database for files with chosen name or files within the folder with the chosen name. How can I use locate to output only folders, not files?

Rubén
  • 693
  • 6
  • 18
shrx
  • 434
  • 4
  • 7
  • 16
  • See also [that similar question on unix.stackexchange.com](http://unix.stackexchange.com/q/192739) – sch Apr 01 '15 at 13:10

11 Answers11

16

Actually, locate has what it takes if you use the --regexp option and you don't mind it spitting out files that have the same name as the directories you seek. The "end of line" position marker gets the job done:

locate -r '/dirname$'

locate also supports --ignore-case if that's what you want.

Frank M
  • 269
  • 2
  • 4
  • To search for all directories, `locate -r '/[^\.]*$'`. – Mateen Ulhaq Feb 17 '18 at 23:26
  • Mateen's approach doesn't filter for directories. It returns everything in the database. The OP was looking for a specific directory and seems to know the name of the directory he wants. – Frank M Apr 03 '18 at 21:33
  • 1
    Doesn't work if `dirname` is in the middle of the dir name. – CodyChan Feb 09 '21 at 18:56
  • @CodyChan: I was assuming "dirname" is the entire directory name. Can you show an example where this doesn't work? – Frank M Feb 10 '21 at 22:38
  • `string` is part of the directory name such as I need to search directory whose name contains `def` such as directory "abc_def_ghi" since most of the time I need to locate a directory containing a string, just like locating a file containing a word but it is a directory. – CodyChan Feb 12 '21 at 17:54
  • This seems wrong. If you have a file called `/foo` and a directory called `/d/foo`, command `locate -r /foo$` will match both. – sancho.s ReinstateMonicaCellio Feb 13 '21 at 01:50
  • @sancho You are correct. That's why my "answer" from 6 years ago had the condition "if...you don't mind it spitting out files that have the same name as the directories you seek." The mlocate database doesn't distinguish files and folders. Therefore, even if you had a built-in check, it would still have to refer to the actual directory entry to see if it is a directory, a file, pipe, device, or whatever. – Frank M Feb 23 '21 at 00:45
4

locate itself can't do it for you. So the UNIX way to do it is to filter the output of locate:

locate --null something | xargs -r0 sh -c 'for i do [ -d "$i" ] && printf "%s\n" "$i"; done' sh {} +
Oliver Salzburg
  • 86,445
  • 63
  • 260
  • 306
  • 2
    @sch Wow, that sucks – Oliver Salzburg Apr 02 '15 at 08:39
  • The proper way is with something like `locate --null something | xargs -r0 sh -c 'for i do [ -d "$i" ] && printf "%s\n" "$i"; done' sh {} +`. `{}` should never be embedded in the shell code, that's as bad as using `eval` with arbitrary data (hence the downvote). – sch Apr 02 '15 at 09:05
  • Note that `[ -d` also returns true for symlinks to directories. Not necessarily bad, just worth noting as it's different for instance from `find -type d`. – sch Apr 02 '15 at 09:12
  • 1
    @sch: Well, if you don't understand the consequences and if you're in the habit of having files named `$(reboot)` on your computer, maybe you shouldn't run commands you find on the internet with `sudo` ;D But, thanks for providing a better version, feel free to edit or post it as an answer, so it doesn't get lost in comments. – Oliver Salzburg Apr 02 '15 at 09:56
  • Unix systems are multi-user and /tmp is world-writable (and even on single user systems, you or a network service can be tricked into creating files named like that). Embedding that `{}` like that is a bug easily upgradable to a security vulnerability. IMO, on public Q&A websites, we should at least point out the limitations. Feel free to include that code in your answer. I'm generally not contributing to [SU], I just got there from a link on [unix.SE]. – sch Apr 02 '15 at 10:14
  • @sch: You definitely have a point, thanks for the improved code. – Oliver Salzburg Apr 02 '15 at 11:07
  • Hi @sch, how does `sh -c '…' sh {} +` differ from `sh -c '…' -- {}`? What does the plus sign do? – Arch Stanton Feb 06 '23 at 09:22
  • @ArchStanton, my bad, the `{} +` is for `-exec ... {} +`, not for xargs – sch Feb 06 '23 at 09:34
  • @sch So the command would be `locate --null something | xargs -r0 sh -c '…' sh {}`? – Arch Stanton Feb 06 '23 at 09:40
  • 1
    @ArchStanton, drop the {} as well. See man xargs and man find. I've submitted an edit suggestion. It's pending revew atm – sch Feb 06 '23 at 10:51
  • @sch So if I understood correctly your command is equivalent to `locate --null something | xargs -I{} -r0 sh -c '[ -d "$1" ] && printf "%s\n" "$1"' sh {}`, right? – Arch Stanton Feb 06 '23 at 17:20
  • 1
    @ArchStanton, close, though that would be less efficient as that would run one `sh` per file. There's also the question of `xargs` overall exit status. Using proper `if` constructs and do a `exit 255` if `printf` fails so `xargs` aborts as well may be preferable. – sch Feb 06 '23 at 17:29
  • Can I ask you one more thing @sch? What's the difference between using `xargs` and `locate --null something | while read -d $'\0' line; do …; done`? Is one preferable over the other? – Arch Stanton Feb 08 '23 at 21:22
  • @ArchStanton, that should be `while IFS= read -rd '' line`. `-d $'\0'` would make sense in zsh, not in bash where it's the same as `''`. In any case, zsh also treats `-d ''` the same as `-d $'\0'` – sch Feb 10 '23 at 09:41
  • @sch I figured out later that it was working because of an `IFS=` I had issued earlier. As far as I can tell, `while IFS= read -rd '' line` removes the newlines from the file names it gets as input: if I use it to just `echo` back the output of `locate --null`, file names with newlines are printed with a space instead of the newline. On the other hand `while IFS=; read -rd ''`, with a semicolon, preserves the newlines in the output of echo, but I'm not sure about how it works… – Arch Stanton Feb 11 '23 at 21:31
  • @ArchStanton, you probably forgot to quote some parameter expansions, like using `echo $var` instead of `printf '%s\n' "$var"`. See [Security implications of forgetting to quote a variable in bash/POSIX shells](https://unix.stackexchange.com/q/171346) and [Understanding "IFS= read -r line"](https://unix.stackexchange.com/q/209123) (also: [Why is printf better than echo?](https://unix.stackexchange.com/q/65803)) – sch Feb 13 '23 at 07:53
  • @sch > _you probably forgot to quote some parameter expansions_. You're right. Thanks man for all the help :-) – Arch Stanton Feb 15 '23 at 19:11
3

Why not use the find command ?

find . -name YOUR_SEARCH_NAME -type d
Scott C Wilson
  • 2,376
  • 4
  • 22
  • 31
  • 8
    locate is faster and I don't need it to be up to date all the time for my purpose. – shrx Apr 03 '12 at 20:30
2

find as suggested in Scott Wilson's answer is what I would have used. However, if you really need to use the locate DB, a hackish solution could be

sudo strings /var/lib/mlocate/mlocate.db | grep -E '^/.*dirname'
  • sudo since the database is not directly readable by regular users.
  • strings to strip metadata (this makes you also find directories to which you don't have read permission, which locate usually hinders).
  • /var/lib/mlocate/mlocate.db is the DB path on Ubuntu, apparently (as an example. Other distributions might have it in other places, e.g. /var/lib/slocate/slocate.db).
  • grep -E to enable regular expressions.
  • ^/.*dirname will match all lines that start with a /, which all directories in the DB happen to do, followed by any character a number of times, followed by your search word.

Positive sides of this solution:

  • it is faster than find,
  • you can use all the bells and whistles of grep (or other favourite text processing tools).

Negative sides:

  • the same as locate in general (DB must be updated),
  • you need root access.
Daniel Andersson
  • 23,895
  • 5
  • 57
  • 61
2

Putting Oliver Salzburg's neat line into your .bashrc:

# locate directories:
# -------------------
locd () {
    locate -0 -b -A "$@" | xargs -0 -I {} bash -c '[ -d "{}" ] && echo "{}"'
}

then you can type locd something everytime you want to locate just directories.


Some breakdown:

  • -0 provides better support for filenames containing spaces or newlines.

  • -b or --basename matches only the last part of the path.

  • -A matches all inputs, so if you provide multiple arguments, all must be present.

joeytwiddle
  • 1,715
  • 1
  • 15
  • 22
joharr
  • 345
  • 2
  • 16
  • For me, to handle spaces in directories better, I use the following: `locate -i --all $1 | xargs -I {} bash -c 'if [ -d "'"{}"'" ]; then echo "'"{}"'"; fi'` – user128063 Nov 09 '18 at 20:30
  • Use locate -0 and xargs -0 flags if you get the xargs unterminated quote error. – usr122212 Dec 25 '19 at 01:53
1

I know this thread is a little old but mlocate's limitations always bothered me too. I finally created my own version of slocate/mlocate called blocate that uses sqlite for indexing and lets you index anything you want using a find command. Performs really well and manages parallel indexing+searching with sqlite in just about 30 lines of bash. GPL - feedback welcome.

https://github.com/jboero/blocate

Hope this helps someone. I built a personal archive website for my backups indexing with mlocate but I got tired of searches taking forever and I didn't want to build a full database solution around it. My searches have gone from ~10s or so down to about 300ms.

BoeroBoy
  • 149
  • 4
1

With recent versions of GNU find (4.9 or above), you can do something like:

locate -0 '*/dirname' | find -files0-from - -prune -type d

That is, pass the list of files from the locate cache to find which further filters it by type.

Replace -type with -xtype to also report symlinks eventually resolving to files of type directory like ['s or [[...]]'s -d does.

With zsh, you can also do:

files=( ${(0)"$(locate -0 '*/dirname')"} )
dirs=( $^files(N/) )

Were we use the / glob qualifier to do the filtering by type (replace with -/ to also include symlinks to directories). Or in one go:

files=( ${(0)^"$(locate -0 '*/dirname')"}(N/) )

print them raw on 1 Column with:

print -rC1 -- $files

For instance.

sch
  • 347
  • 2
  • 8
  • In my system (Fedora 37, GNU findutils 4.9.0) I can only use `-files0-from -`, without the equal sign. – Arch Stanton Feb 07 '23 at 17:09
  • @ArchStanton, thanks. Fixed now. I guess I got confused with the `--files0-from=-` option of other GNU utilities like sort/wc/du. – sch Feb 08 '23 at 08:10
1

I went with this solution:

locate -i "$foldername" | while read line
        do
            if [[ -d "$line" && `echo ${line##*/} | tr [:upper:] [:lower:]` = *`echo $foldername | tr [:upper:] [:lower:]`* ]]; then
                echo "$line"
            fi
        done
shrx
  • 434
  • 4
  • 7
  • 16
  • Can you explain what the echo ${line##*/} is doing? – user128063 Nov 09 '18 at 20:24
  • @user128063 It's a bit late but anyway… It trims the string up to the last `/`. In general, `${var##pattern}` trims the longest match from the beginning in `$var`. See https://wiki.bash-hackers.org/syntax/pe#substring_removal – Arch Stanton Feb 07 '23 at 23:08
0

This answer is the inverse of this other. Use

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

Explanation:

  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 ... filters lines containing directory$
  4. awk ... removes what file ... appended
0

A small variation on others

for f in $(locate /dirname | grep /dirname$ ) ; do if [ -f $f ] ; then ls -1 $f ; fi ; done

If you want to match a pattern instead of providing the full name, there are also variations with grep.

0

Place these as last lines or where ever it fits best for you.
gedit ~/.bashrc

#system only
slocate() { locate $@ | egrep -v ˆ/home ; }

#system directories only
dslocate() { for directory in `locate $@ | egrep -v ˆ/home`; do if [ -d "$directory" ]; then echo $directory; fi; done ; }

#whole system directories only
dlocate() { for directory in `locate $@`; do if [ -d "$directory" ]; then echo $directory; fi; done ; }

#local user's only
llocate() { locate $@ | egrep ˆ/home ; }

#local user's directories only
ldlocate() { for directory in `locate $@ | egrep ˆ/home`; do if [ -d "$directory" ]; then echo $directory; fi; done ; }


hope this helps, cheers

tao
  • 1,415
  • 8
  • 11