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?

l0b0
  • 51,350

6 Answers6

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 /
l0b0
  • 51,350
  • @greenmang0 He mentioned in the question that it didn't work on OSX – Michael Mrozek Nov 08 '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 08 '11 at 14:51
  • 3
    @jlliagre: Please read before posting. The stat command didn't work on OS X, ls -di worked on both. – l0b0 Nov 08 '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 09 '11 at 00: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 09 '11 at 12:06
  • @jlliagre: Michael said that l0b0's question said that ls -di didn't work, but that was a misunderstanding on Michael's part. In fact, the original question merely says that stat -c %i file doesn't work on OSX; ls -di should work anywhere. – Keith Thompson Nov 09 '11 at 20:42
  • 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 00: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 09 '16 at 05:30
  • @mtraceur Interesting. Shows you just how impossible it is to write truly portable shell code. – l0b0 Oct 09 '16 at 08:51
  • 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 09 '16 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' {} \;
jlliagre
  • 61,204
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
)
jeff
  • 11
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
)
ianc
  • 1