Find

From Segfault
Jump to: navigation, search

Find files with special permissions

Create test files & directories with all available permission bits set:

mkdir test && cd $_
for x in {0..7}; do
   echo "x: $x"
   for w in {0..7}; do
      for r in {0..7}; do
         for s in {0..7}; do
            MODE=${s}${r}${w}${x}
            touch file_$MODE
            mkdir -p dir_$MODE
            chmod $MODE file_$MODE dir_$MODE
         done
      done
   done
done

Find files with the sticky bit set:

$ find . -perm -1000 -exec ls -dgo '{}' +
dr-xr-xr-t 2 4096 Jun 14 16:32 ./dir_1555
-r--r--r-T 1    0 Jun 14 16:32 ./file_1444
[...]

Find SUID or SGID files:

$ find . -type f \( -perm -04000 -o -perm -02000 \) -exec ls -dgo '{}' +
dr-sr--r-- 2 4096 Jun 14 16:32 ./dir_4544
-r-Sr--r-- 1    0 Jun 14 16:32 ./file_4444
[...]                                            # Note: Solaris will also return objects with 
                                                 # the mandatory locking bit[1] set.

Find world-writable files:

$ find . -perm -2 ! -type l -exec ls -dgo '{}' +
dr-xr---w- 2 4096 Jun 14 16:32 ./dir_0542
-r--r---w- 1    0 Jun 14 16:32 ./file_0442
[...]

Find readable or writable files for a specific user (needs GNU/find 4.3[2] or later):

$ sudo -u alice find /tmp/ -xdev -writable -ls 2>/dev/null 
   2913    0 drwxrwxrwt  16 root   root    200 Sep  6 16:09 /tmp/
4519452    0 drwx------   2 alice alice     80 Sep  6 16:01 /tmp/.vbox-alice-ipc
4519455    0 srwx------   1 alice alice      0 Sep  6 16:01 /tmp/agent.1593
4519453    4 -rw-------   1 alice alice      6 Sep  6 16:01 /tmp/tmperr

Find files with an unknown user and/or group:

$ sudo chown 123:456 file_0001
$ find . \( -nouser -o -nogroup \) -ls
525558    0 ---------x   1 123      456             0 Jun 14 16:32 ./file_0001

Find files with exactly the permission bits set:

$ find . -perm 0644 -exec ls -dgo '{}' + 
drw-r--r--   2       2 Jun 17 21:47 ./dir_0644
drw-r--r--   2       2 Jun 17 21:47 ./dir_2644
-rw-r--r--   1       0 Jun 17 21:47 ./file_0644
-rw-r--r--   1       0 Jun 17 21:47 ./file_1644

Find files with all of the permission bits set (not portable):

$ find . -perm -7774 -exec ls -dgo '{}' +
drwsrwsr-T 2 4096 Jun 14 16:32 ./dir_7774
drwsrwsr-t 2 4096 Jun 14 16:32 ./dir_7775
drwsrwsrwT 2 4096 Jun 14 16:32 ./dir_7776
drwsrwsrwt 2 4096 Jun 14 16:32 ./dir_7777
-rwsrwsr-T 1    0 Jun 14 16:32 ./file_7774
-rwsrwsr-t 1    0 Jun 14 16:32 ./file_7775
-rwsrwsrwT 1    0 Jun 14 16:32 ./file_7776
-rwsrwsrwt 1    0 Jun 14 16:32 ./file_7777

Find files with any of the permission bits set (not portable):

$ find . -perm /0444 -exec ls -dgo '{}' +
d------r--    2   4096 Jun 14 16:32 ./dir_0004
----r-----    1      0 Jun 14 16:32 ./file_0040
[...]

Find files with ACLs set:

$ ls -go
-rw-r-----  1 0 Jan 27 13:32 file1
-rw-r-----+ 1 0 Jan 27 13:32 file2
-rw-r-----  1 0 Jan 27 13:32 file3

$ getfacl -Rps . | awk -F'file: ' '/file:/ {print $NF}'
./file2

Find files with EAs set:

$ getfattr -R --absolute-names . | awk -F'file: ' '/file:/ {print $NF}'
./file2
./file3

Find broken symlinks

find -L . -type l -ls

The -L option makes find(1) to follow symbolic links. Combined with "-type l", it tries to find symlinks. Because -L followed all symlinks before, "-ls" reports only the broken ones, because it could not be followed to an actual file.

Find symlinks pointing to an absolute target

In backups, symlinks to absolute targets may sometimes not be desirable. For example, backing up the another machine's /etc/mtab symlink may then look like this:

$ find . -type l
lrwxrwxrwx 12 19 Apr 24 10:54 ../backups/alice/etc/mtab -> /proc/self/mounts

Subsequent backups of this directory will backup the host's version of /proc/self/mounts, thus needlessly backing up files. Let's see if we can find (and remove) symlinks, pointing to an absolute target:

$ find . -type l | while read l; do readlink -- "$l" | grep -q ^/ && ls -go -- "$l" && echo rm -v "$f"; done 
lrwxrwxrwx 12 33 Apr 24 10:33 etc/localtime -> /usr/share/zoneinfo/Europe/Berlin
rm -fv etc/localtime
[...]

If our find(1) version understands -lname, we can try:[3]

$ find . -type l -lname '/*' -exec ls -go '{}' +
lrwxrwxrwx 12 33 Apr 24 10:33 etc/localtime -> /usr/share/zoneinfo/Europe/Berlin

Find sparse files

An sparse file is a file of certain size, but its data blocks are only allocated once it's being written to.

$ dd if=/dev/zero of=file   bs=1 count=1024k            2>/dev/null
$ dd if=/dev/zero of=sparse bs=1 count=0     seek=1024k 2>/dev/null        # Use "mkfile -n 1m sparse" for Solaris

$ du -k file sparse
1024    file
0       sparse

By using the -s option of ls(1), we can print the allocated size of each file[4], in blocks (1024 byte):

$ ls -gos file sparse 
1024 -rw-r--r--. 1 1048576 Aug 29 02:56 file
   4 -rw-r--r--. 1 1048576 Aug 29 02:57 sparse

Let's use GNU/find to search for sparse files:[5]

$ find . -type f -printf "%S\t%i\t%p\n" 2>/dev/null | awk '{if ($1 < 1.0) print}'
0.00390625      48880   ./sparse
|               |       |
|               |       |-- file name
|               |---------- inode number
|-------------------------- BLOCKSIZE * st_blocks / st_size. Sparse files are usually below 1.0

Find long file names

mkdir dir
touch {,dir/}0{,1{,2{,3{,4{,5{,6{,7{,8{,9}}}}}}}}}

Find path names of at least 14 characters:

$ find . -regextype posix-extended -regex '.{14,}'
./dir/01234567                                                   # 14 characters, including the leading "./"
./dir/012345678
./dir/0123456789

Find file names of at least 8 characters:

$ find . -regextype posix-extended -regex './[^/]{9,}.*'
./012345678                                                      # 9 characters, exluding the leading "./"
./0123456789

If GNU/find is not available, we can use awk too:

$ find . | awk 'length >= 12 {print length, $0}' | sort -n
12 ./0123456789                                                  # 12 characters, including the leading "./"
12 ./dir/012345
13 ./dir/0123456
14 ./dir/01234567
15 ./dir/012345678
16 ./dir/0123456789

Or, to find file names of specific length:

$ find . -type f | awk -F/ 'length($NF) <= 3 {print length($NF), $0}' | sort -n
1 ./0
1 ./dir/0
2 ./01
2 ./dir/01
3 ./012
3 ./dir/012

Find directory with the most objects

Sometimes we need to know where all inodes are being used:[6]

find . -xdev -printf '%h\n' | sort | uniq -c | sort -nk1

From find(1):

%h     Leading directories of file's name (all but the last element). If the file name contains no 
       slashes (since it is in the current directory) the %h specifier expands to ".".

A similar result can be achieved with GNU/du (available in GNU/coreutils v8.22)[7][8]

du --inodes -xS .                           # We use -S to not include the size of subdirectories

Find empty directories

Apparently, -empty is quite common[9], although it's not portable[10]:

$ mkdir -p dir1/dir_{a,b}
$ touch dir1/dir_a/foo                                                                                                                                                   
$ find dir1/ -depth -type d -empty
dir1/dir_b

Without -type d, it would also list the file dir1/dir_a/foo, for being empty:

$ man find | grep -- -empty\ F
    -empty File is empty and is either a regular file or a directory.

Find devices

While find(1) doesn't have an option to search for major/minor number[11], we can still use it to seach /sys and examine the udev information:

$ awk -F= '/DEVNAME/ {print $NF}' $(find /sys/dev/ -name 8:64)/uevent
sde

Or, with udevadm:

$ udevadm info -rq name $(find /sys/dev/ -name 8:64)
/dev/sde

Note: this is not portable but only works on Linux. For Solaris (and probably other Unix systems too), I found no other way than to grep:

$ find -L /dev/ -ls | grep '33, ' | grep ' 64 '
17301636    0 crw-r-----   1 root     sys       33,  64 Jun 17 21:25 /dev/rsd8a
17301635    0 brw-r-----   1 root     sys       33,  64 Jun 17 21:25 /dev/dsk/c1t0d0s0
17301636    0 crw-r-----   1 root     sys       33,  64 Jun 17 21:25 /dev/rdsk/c1t0d0s0
17301635    0 brw-r-----   1 root     sys       33,  64 Jun 17 21:25 /dev/sd8a

The results all point to different representations of the same device. Here, '33' is the major and '64' is the minor device number.[12]

Find non-ASCII file names

If the -iregex option is supported:

$ ls
bär  baß  foo

$ find . -type f ! -iregex '[a-zA-Z0-9\ -\.\,]*'          # This may need to be extended to match other 
                                                          # characters of the ASCII character set
./bär
./baß

Find most recent files

Find (sort) the most recent modified (%T) file within a directory structure:

$ mkdir -p dir/b && touch dir/a && touch -r /bin/ls dir/b/c

$ LC_ALL=C find dir/ -type f -printf "%T@ %Tc %h/%f\n" | sort -n 
1510336586.0000000000 Fri Nov 10 09:56:26 2017 dir/b/c
1515811677.2218102000 Fri Jan 12 18:47:57 2018 dir/a

If find doesn't support printf, the formattings gets more complicated and now needs stat(1) and a version of awk that supports strftime. It also doesn't seem to work when objects have spaces:

$ find dir/ -type f -exec stat -r '{}' + | sort -nk10 | awk '{print strftime("%c", $10), $NF}'
Fri Nov 10 09:56:26 2017 dir/b/c
Fri Jan 12 18:47:57 2018 dir/a

An easier way to check for "recent" files within a directory structure might be to use a reference file:

$ find dir/ -type f -newer .cshrc -exec ls -ghtrd '{}' +
-rw-------  1 wheel    0B Nov 10 09:56 dir/b/c
-rw-------  1 wheel    0B Jan 12 18:47 dir/a

Find files from the future

While we can find files and directories from the past, it was not as easy to find objects with timestamps in the future[13][14]. Find v4.3.3 implements -newerXY[15] to do just that:

$ touch -d "+20 years" foo
$ find . -newermt "1 day" | xargs ls -go
-rw-r--r--. 1 0 May 18  2038 ./foo

Exclude directories

Exclude directories from our search:

find .            -xdev \( -path ./dir1 -o -path ./dir2 \) -prune -o -type f
find / /home/bob/ -xdev \( -path /home/.ecryptfs -o -path /var/cache/fscache/cache \) -prune -o -type f

Note the leading ./ in front of those directories, as we need the full (but not absolute) path name here.

Links

References