Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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
up vote 9 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
Michael misunderstanding was precisely what I was commenting. It doesn't worth a quite rude and undeserved "read before posting" comment. – jlliagre Nov 10 '11 at 0:32

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

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; };;
   "FreeBSD" )      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

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

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

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

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

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.