61

How do I find out which directories are responsible for chewing up all my inodes?

Ultimately the root directory will be responsible for the largest number of inodes, so I'm not sure exactly what sort of answer I want..

Basically, I'm running out of available inodes and need to find a unneeded directory to cull.

Thanks, and sorry for the vague question.

Joel
  • 11,431
  • 17
  • 62
  • 72
  • 2
    Wow, people running out of inodes? I haven't seen that since the early days of Usenet when you had to give an argument to mkfs to make it make more inodes - because a Usenet news spool had tons of tiny little files. – Paul Tomblin Dec 07 '08 at 14:24
  • Yeah - certainly a reminder of times long past. – Jonathan Leffler Dec 07 '08 at 15:47
  • 6
    I think that's like saying, "Wow, you overflowed the stack, and there's so much memory available these days". There's often a good reason it's happening (particular script or directory) and that's what the OP is looking for. – gbarry Dec 07 '08 at 19:00
  • 2
    @PaulTomblin: Shared Hosting. – Aditya M P Jan 27 '14 at 12:20

16 Answers16

89

If you don't want to make a new file (or can't because you ran out of inodes) you can run this query:

for i in `find . -type d `; do echo `ls -a $i | wc -l` $i; done | sort -n

as insider mentioned in another answer, using a solution with find will be much quicker since recursive ls is quite slow, check below for that solution! (credit where credit due!)

Hannes
  • 1,007
  • 1
  • 7
  • 8
  • 7
    This should be the right answer as if you don't have any more inodes, you cannot create the shell script in Paul's answer... – WispyCloud Feb 07 '13 at 02:05
  • sort may create temporary files for sorting. So this may not be a bullet proof solution after all. – donatello May 22 '14 at 19:13
  • +1, but be aware that this will command will use a good chunk of memory to run - it's literally listing every file in every folder - so don't do it unless your system is otherwise healthy. – Marcus Downing Feb 04 '15 at 10:46
  • 2
    This should consume less memory. You can change "20" (minimum inode count) to anything you need: `find . -type d | while read i ; do echo \`ls -a "$i" | wc -l\` "$i" ; done | awk '{ if($1 >= 20) print $0 }' | sort -nr` – Guillermo Prandi Jul 23 '16 at 01:46
45

Provided methods with recursive ls are very slow. Just for quickly finding parent directory consuming most of inodes i used:

cd /partition_that_is_out_of_inodes
for i in *; do echo -e "$(find $i | wc -l)\t$i"; done | sort -n
insider
  • 1,818
  • 2
  • 17
  • 15
  • i found this solution worked better with odd filenames and gave a quicker response than Hannes solution. – jammin Sep 01 '14 at 08:07
  • 4
    Without the ` | sort -n` part and just using `for i in *; do echo -e "$(find $i | wc -l)\t$i"; done` I found it to be even better, as each folder's result is output as soon as it is known, rather than waiting until the end to sort the results. This gives a more interactive experience and feedback on progress of the command – BeowulfNode42 Sep 03 '14 at 06:18
  • You should quote the argument of `find` in case it contains space(s). The calculation fails, if there are some directories that belong to another filesystem. – jarno Sep 06 '16 at 22:42
  • Also the solution fails, when there are newline in filename, but who uses newlines in filenames? – jarno Sep 07 '16 at 21:04
  • 2
    put quotes around `$i` to handle directory names with spaces. e.g. `for i in *; do echo -e "$(find "$i" | wc -l)\t$i"; done | sort -n` – kevincasey Jul 19 '18 at 18:04
21

So basically you're looking for which directories have a lot of files? Here's a first stab at it:

find . -type d -print0 | xargs -0 -n1 count_files | sort -n

where "count_files" is a shell script that does (thanks Jonathan)

echo $(ls -a "$1" | wc -l) $1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • 1
    no need for the '-l' parameter to ls - it will default to one-line-per-file output if stdout is not a tty. – Alnitak Dec 07 '08 at 15:30
  • If the hidden files start with '.', this won't find them. I'd probably use - echo $1 $(ls -a "$1" | wc -l) - to generate directory name and count on one line (and I might well reverse the order to list count first). Note the careful use of quotes around the file name where it matters. – Jonathan Leffler Dec 07 '08 at 15:53
  • 1
    How do you plan to save a "count_files" shell script, when you've already exhausted your inodes? – AnrDaemon Sep 26 '16 at 22:15
  • @AnrDaemon there's a reason why server setups usually use different partitions/disks for different uses. For instance, Usenet news spools used to consume a lot of inodes for the amount of space, so it was usually a different partition. And /home should always be a different partition so it doesn't accidentally get wiped during an upgrade. – Paul Tomblin Sep 27 '16 at 12:24
  • That's just wishful thinking. The situation in the OP is most often encountered on VPS, where limited disk space and large block size cause rapid degradation of a filesystem in case many small files are saved on a partition. I.e. multiple kernel headers installed and not purged when it is due. – AnrDaemon Sep 27 '16 at 13:11
  • That is slow, if there are many directories. @AnrDaemon with [my solution](https://stackoverflow.com/a/39379847/4414935) you do not need to create additional file, besides it is much faster (I mean at least the `gawk` solution is) and gives you more information with configurable directory depth. – jarno Apr 11 '20 at 10:17
  • `find / -xdev -type d -size +100k` is fast. Actually, it's lightning fast. But it may not be what you are looking for. Your solution is cumbersome and hard to remember. – AnrDaemon Apr 13 '20 at 07:59
  • @AnrDaemon yes, it is not what I am looking for and I think it is not what OP is looking for either. You could wrap my solution in a shell function or in a script and pass the depth as an argument. – jarno Apr 13 '20 at 09:31
  • Except you can't save script when all inodes are used up. We've discussed this already years ago. – AnrDaemon Apr 13 '20 at 11:01
  • @AnrDaemon but you can copy it to the command line or to e.g. .bashrc, if you use Bash shell. Also, maybe you are able to remove a file so that you can create new script file, or save it in another partition. – jarno Apr 13 '20 at 12:03
18

I used the following to work out (with a bit of help from my colleague James) that we had a massive number of PHP session files which needed to be deleted on one machine:

1. How many inodes have I got in use?

 root@polo:/# df -i
 Filesystem     Inodes  IUsed  IFree IUse% Mounted on
 /dev/xvda1     524288 427294  96994   81% /
 none           256054      2 256052    1% /sys/fs/cgroup
 udev           254757    404 254353    1% /dev
 tmpfs          256054    332 255722    1% /run
 none           256054      3 256051    1% /run/lock
 none           256054      1 256053    1% /run/shm
 none           256054      3 256051    1% /run/user

2. Where are all those inodes?

 root@polo:/# find / -xdev -printf '%h\n' | sort | uniq -c | sort -k 1 -n
 [...]
    1088 /usr/src/linux-headers-3.13.0-39/include/linux
    1375 /usr/src/linux-headers-3.13.0-29-generic/include/config
    1377 /usr/src/linux-headers-3.13.0-39-generic/include/config
    2727 /var/lib/dpkg/info
    2834 /usr/share/man/man3
  416811 /var/lib/php5/session
 root@polo:/#

That's a lot of PHP session files on the last line.

3. How to delete all those files?

Delete all files in the directory which are older than 1440 minutes (24 hours):

root@polo:/var/lib/php5/session# find ./ -cmin +1440 | xargs rm
root@polo:/var/lib/php5/session#

4. Has it worked?

 root@polo:~# find / -xdev -printf '%h\n' | sort | uniq -c | sort -k 1 -n
 [...]
    1088 /usr/src/linux-headers-3.13.0-39/include/linux
    1375 /usr/src/linux-headers-3.13.0-29-generic/include/config
    1377 /usr/src/linux-headers-3.13.0-39-generic/include/config
    2727 /var/lib/dpkg/info
    2834 /usr/share/man/man3
    2886 /var/lib/php5/session
 root@polo:~# df -i
 Filesystem     Inodes  IUsed  IFree IUse% Mounted on
 /dev/xvda1     524288 166420 357868   32% /
 none           256054      2 256052    1% /sys/fs/cgroup
 udev           254757    404 254353    1% /dev
 tmpfs          256054    332 255722    1% /run
 none           256054      3 256051    1% /run/lock
 none           256054      1 256053    1% /run/shm
 none           256054      3 256051    1% /run/user
 root@polo:~#

Luckily we had a sensu alert emailing us that our inodes were almost used up.

Sam Critchley
  • 3,388
  • 1
  • 25
  • 28
  • 1
    This is a better answer than the accepted one. The find command is way quicker and it is more thorough. Thank you! – SirVer Feb 20 '16 at 11:52
  • 1
    Just in case you face my same problem where the 'sort' command can't be executed due to no free space left, you can use -T option to specify a new path for temp to let sort command run. so the command would be: find / -xdev -printf '%h\n' | sort -T /path_with_free_space/temp_test | uniq -c | sort -k 1 -n -T /path_with_free_space/temp_test – myh34d Nov 14 '16 at 16:24
11

This is my take on it. It's not so different from others, but the output is pretty and I think it counts more valid inodes than others (directories and symlinks). This counts the number of files in each subdirectory of the working directory; it sorts and formats the output into two columns; and it prints a grand total (shown as ".", the working directory). This will not follow symlinks but will count files and directories that begin with a dot. This does not count device nodes and special files like named pipes. Just remove the "-type l -o -type d -o -type f" test if you want to count those, too. Because this command is split up into two find commands it cannot correctly discriminate against directories mounted on other filesystems (the -mount option will not work). For example, this should really ignore "/proc" and "/sys" directories. You can see that in the case of running this command in "/" that including "/proc" and "/sys" grossly skews the grand total count.

for ii in $(find . -maxdepth 1 -type d); do 
    echo -e "${ii}\t$(find "${ii}" -type l -o -type d -o -type f | wc -l)"
done | sort -n -k 2 | column -t

Example:

# cd /
# for ii in $(find -maxdepth 1 -type d); do echo -e "${ii}\t$(find "${ii}" -type l -o -type d -o -type f | wc -l)"; done | sort -n -k 2 | column -t
./boot        1
./lost+found  1
./media       1
./mnt         1
./opt         1
./srv         1
./lib64       2
./tmp         5
./bin         107
./sbin        109
./home        146
./root        169
./dev         188
./run         226
./etc         1545
./var         3611
./sys         12421
./lib         17219
./proc        20824
./usr         56628
.             113207
tantrix
  • 1,248
  • 12
  • 14
Noah Spurrier
  • 508
  • 5
  • 8
6

Here's a simple Perl script that'll do it:

#!/usr/bin/perl -w

use strict;

sub count_inodes($);
sub count_inodes($)
{
  my $dir = shift;
  if (opendir(my $dh, $dir)) {
    my $count = 0;
    while (defined(my $file = readdir($dh))) {
      next if ($file eq '.' || $file eq '..');
      $count++;
      my $path = $dir . '/' . $file;
      count_inodes($path) if (-d $path);
    }
    closedir($dh);
    printf "%7d\t%s\n", $count, $dir;
  } else {
    warn "couldn't open $dir - $!\n";
  }
}

push(@ARGV, '.') unless (@ARGV);
while (@ARGV) {
  count_inodes(shift);
}

If you want it to work like du (where each directory count also includes the recursive count of the subdirectory) then change the recursive function to return $count and then at the recursion point say:

$count += count_inodes($path) if (-d $path);
Alnitak
  • 334,560
  • 70
  • 407
  • 495
3

An actually functional one-liner (GNU find, for other kinds of find you'd need your own equivalent of -xdev to stay on the same FS.)

find / -xdev -type d | while read -r i; do printf "%d %s\n" $(ls -a "$i" | wc -l) "$i"; done | sort -nr | head -10

The tail is, obviously, customizable.

As with many other suggestions here, this will only show you amount of entries in each directory, non-recursively.

P.S.

Fast, but imprecise one-liner (detect by directory node size):

find / -xdev -type d -size +100k

AnrDaemon
  • 302
  • 2
  • 9
  • Thank you, very much! Your script result is concise and works in the case of 100% of used inodes. – MockerTim Mar 02 '17 at 09:04
  • At least the "P.S." solution does not answer to the question "where are all my inodes being used". For example, in my Ubuntu Linux system it finds only /usr/share/man/man3 (which has about 2000 files) under /usr even if I have about 270000 files under /usr. – jarno Apr 13 '20 at 09:27
  • You should understand the filesystem structure before using any functions to analyze it. The "size" of a directory is a product of the amount of entries located directly inside it. Thus, straight check for "directory size" will only show you directories that have too many entries directly inside them. Which is surprisingly efficient in many cases (flooded session storage, stale cache directory, etc.). – AnrDaemon Apr 13 '20 at 11:06
3

There's no need for complex for/ls constructions. You can get 10 fattest (in terms of inode usage) directories with:

du --inodes --separate-dirs --one-file-system | sort -rh | head

which equals to:

du --inodes -Sx | sort -rh | head

--one-file-system parameter is optional.

SergeiMinaev
  • 194
  • 3
  • 8
2

Just wanted to mention that you could also search indirectly using the directory size, for example:

find /path -type d -size +500k

Where 500k could be increased if you have a lot of large directories.

Note that this method is not recursive. This will only help you if you have a lot of files in one single directory, but not if the files are evenly distributed across its descendants.

Romuald Brunet
  • 5,595
  • 4
  • 38
  • 34
1
for i in dir.[01]
do
    find $i -printf "%i\n"|sort -u|wc -l|xargs echo $i --
done

dir.0 -- 27913
dir.1 -- 27913

1

use

ncdu -x <path>

then press Shitf+c to sort by items count where the item is file

LPby
  • 529
  • 7
  • 12
1

When searching for folder consuming most disk space, I used to work with du top to bottom like this:

du -hs /*

This is listing file consumption per top-level folder. Afterwards, you can descend into either folder by extending given pattern:

du -hs /var/*

and so on ...

Now, when it comes to inodes, the same tool can be used with slightly different arguments:

du -s --inodes /*

There is a caching improving follow-up invocations of this tool in same folder which is beneficial under normal circumstances. However, when you've run out of inodes I assume this will turn into the opposite.

Thomas Urban
  • 4,649
  • 26
  • 32
  • AFAIU the directory consumes an inode itself, e.g. https://en.wikipedia.org/wiki/Inode states: "The inode (index node) is a data structure in a Unix-style file system that describes a file-system object such as a file **or a directory**." – Thomas Urban Jun 07 '20 at 08:05
0

The perl script is good, but beware symlinks- recurse only when -l filetest returns false or you will at best over-count, at worst recurse indefinitely (which could- minor concern- invoke Satan's 1000-year reign).

The whole idea of counting inodes in a file system tree falls apart when there are multiple links to more than a small percentage of the files.

stinkoid
  • 41
  • 3
0

Just a note, when you finally find some mail spool directory and want to delete all the junk that's in there, rm * will not work if there are too many files, you can run the following command to quickly delete everything in that directory:

* WARNING * THIS WILL DELETE ALL FILES QUICKLY FOR CASES WHEN rm DOESN'T WORK

find . -type f -delete
0

Unfortunately not a POSIX solution but... This counts files under current directory. This is supposed to work even if filenames contain newlines. It uses GNU Awk. Change the value of d (from 2) to the wanted maximum separated path depths. 0 means unlimited depth. In the deepest level files in sub-directories are counted recursively.

d=2; find . -mount -not -path . -print0 | gawk '
BEGIN{RS="\0";FS="/";SUBSEP="/";ORS="\0"}
{
    s="./"
    for(i=2;i!=d+1 && i<NF;i++){s=s $i "/"}
    ++n[s]
}
END{for(val in n){print n[val] "\t" val "\n"}}' d="$d" \
 | sort -gz -k 1,1

Same by Bash 4; give depth as an argument for the script. This is significantly slower in my experience:

#!/bin/bash
d=$1
declare -A n

while IFS=/ read -d $'\0' -r -a a; do
  s="./"
  for ((i=2; i!=$((d+1)) && i<${#a[*]}; i++)); do
    s+="${a[$((i-1))]}/"
  done
  ((++n[\$s]))
done < <(find . -mount -not -path . -print0)

for j in "${!n[@]}"; do
    printf '%i\t%s\n\0' "${n[$j]}" "$j"
done | sort -gz -k 1,1 
jarno
  • 787
  • 10
  • 21
-2

This command works in highly unlikely cases where your directory structure is identical to mine:

find / -type f | grep -oP '^/([^/]+/){3}' | sort | uniq -c | sort -n

Yaron
  • 1,199
  • 1
  • 15
  • 35