Take the 2-minute tour ×

At first I used stat -c %i file (to help detect the presence of a jail), which seemed to work on any Linux distribution under the sun. On OS X' I had to use ls -i file | cut -d ' ' -f 1.

Is there some way to find the inode number of a file in a shell script which is portable across *nix platforms and does not depend on the notoriously capricious ls?

share|improve this question
You may be interested in, or have better answers for, How do I tell I'm running in a chroot?. –  Gilles Nov 8 '11 at 23:56
Can you elaborate about the "notoriously capricious ls" ? –  jlliagre Nov 10 '11 at 0:34
@jlliagre: Others have done it better already. –  l0b0 Nov 10 '11 at 13:32
Okay, for such files, see my reply. –  jlliagre Nov 11 '11 at 14:46
add comment

6 Answers

up vote 7 down vote accepted

Possible solution: The POSIX spec for ls specifies -i, so maybe it's portable. Does anyone know of a popular implementation of ls which does not support this, or prints it in a different way from the following example:

$ ls -di /
2 /
share|improve this answer
@greenmang0 He mentioned in the question that it didn't work on OSX –  Michael Mrozek Nov 8 '11 at 14:07
ls -di should just work on OSX just like it should (and likely does) on every Unix/Unix like OS. –  jlliagre Nov 8 '11 at 14:51
@jlliagre: Please read before posting. The stat command didn't work on OS X, ls -di worked on both. –  l0b0 Nov 8 '11 at 14:54
Even Busybox ls has -d and -i as mandatory features (though ls itself is optional, like everything else). –  Gilles Nov 9 '11 at 0:00
@l0b0: I don't get your reply. I was commenting on Michael Mrozek own comment which states ls -di didn't work unless I'm misunderstanding it. –  jlliagre Nov 9 '11 at 12:06
show 2 more comments

To increase portability you may also implement a platform-specific wrapper function (here called statinode()) around the stat command that can be based on the output of uname -s (see uname).

ls would be needed as a fallback option only.

shopt -s nocasematch nullglob    # using Bash
case "$(uname -s)" in
   # nocasematch alternative
   #[Ll][Ii][Ni][Uu][Xx]   )  statinode() { stat -c '%i' "$@"; return 0; };;
   "Linux"   )      statinode() { stat -c '%i' "$@"; return 0; };;
   "Darwin"  )      statinode() { stat -f '%i' "$@"; return 0; };;
           * )      statinode() { ls -id "$@" | cut -d ' ' -f 1; return 0; };;
#export -f statinode
statinode / / / /
shopt -u nocasematch nullglob
share|improve this answer
add comment

This should be portable and work with file names containing spaces, newlines or other odd characters leading to the notoriously capricious ls behavior.

filename="whatever file name"
find . -name "$filename" -exec sh -c 'ls -di "$0" | head -1' {} \;
share|improve this answer
add comment

stat is part of the GNU Coreutils package. OSX uses a different stat implementation (presumably a BSD-based one) which doesn't take the same command-line arguments.

You could always install GNU Coreutils on OSX. Of course that doesn't help if you need a solution that works on OSX systems that don't have GNU Coreutils.

Or, if I'm reading the OSX stat(1) man page correctly, stat -f %i file on OSX behaves like stat -c %i file using the Coreutils version. (Determining which version of stat you have is another matter; you could try stat --version >/dev/null; if it succeeds, you have the GNU Coreutils version.)

The ls -di solution is more portable and less trouble, but this is an alternative.

share|improve this answer
add comment

Another solution:


use strict;
use warnings;

die "Usage: $0 filename\n" if scalar @ARGV != 1;
my $file = $ARGV[0];
my @stat = stat $file;
die "$file: $!\n" if not @stat;
print "$stat[1]\n";

You can probably safely assume that Perl is installed.

share|improve this answer
add comment

Similar to jeff's approach, stat could be tested directly as well.

if (stat -c '%i' / 1>/dev/null 2>&1; exit $?); then
elif (stat -f '%i' / 1>/dev/null 2>&1; exit $?); then
elif test -n "$(exec 2>/dev/null; ls -id / | cut -d ' ' -f 1)"; then
   echo 'Could not create statinode(). Exiting ...' && exit 1
# export -f statinode
statinode / / / /
declare -f statinode
share|improve this answer
add comment

Your Answer


