10

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?

4

6 Answers 6

11

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 /
10
  • 3
    @jlliagre: Please read before posting. The stat command didn't work on OS X, ls -di worked on both.
    – l0b0
    Commented Nov 8, 2011 at 14:54
  • 1
    Even Busybox ls has -d and -i as mandatory features (though ls itself is optional, like everything else). Commented Nov 9, 2011 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
    Commented Nov 10, 2011 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
    Commented Oct 9, 2016 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
    Commented Oct 9, 2016 at 17:53
2

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

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; };;
esac
#export -f statinode
statinode / / / /
shopt -u nocasematch nullglob
)
0

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.

0

Another solution:

#!/usr/bin/perl

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.

0

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; }
else
   echo 'Could not create statinode(). Exiting ...' && exit 1
fi
# export -f statinode
statinode / / / /
declare -f statinode
)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .