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?


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 /
| improve this answer | |
  • 3
    @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
  • 1
    Even Busybox ls has -d and -i as mandatory features (though ls itself is optional, like everything else). – Gilles 'SO- stop being evil' Nov 9 '11 at 0:00
  • 1
    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
  • 2
    There are exceptions to this: ls with -i front-pads with spaces on at least Solaris 10 (possibly Solaris 11, I haven't checked). It looks like this was the traditional behavior going back to Unix version 7, so I suspect a lot of the corporate *nix flavors kept this behavior (I only have Solaris 10 on-hand though). So near as I can tell, if you use something that properly delineates fields in arbitrary whitespace (so, not cut, but for instance awk or just the shell's own field-splitting), it's portable to expect the first non-whitespace string to be the inode number. – mtraceur Oct 9 '16 at 5:30
  • 1
    @l0b0 Yeah. It requires masochistic dedication: a bunch of study/testing and memorization for constantly diminishing returns. It's possible, at least for some definition of "portable", but it's not a pleasant experience. – mtraceur Oct 9 '16 at 17:53

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' {} \;
| 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
| 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.

| 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.

| 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
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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