9

On Linux, is there a way for a shell script to check if its standard input is redirected from the null device (1, 3) *, ideally without reading anything?

The expected behavior would be:

./checkstdinnull
-> no
./checkstdinnull < /dev/null
-> yes
echo -n | ./checkstdinnull
-> no

EDIT

mknod secretunknownname c 1 3
exec 6<secretunknownname
rm secretunknownname
./checkstdinnull <&6
-> yes

I suspect I "just" need to read the maj/min number of the input device. But I can't find a way of doing that from the shell.


*No necessary just /dev/null, but any null device even if manually created with mknod.

6
  • 2
    Do you need to know if it's /dev/null, or just that it's not a tty? Nov 26, 2018 at 15:05
  • The output of { readlink -f /dev/stdin; } <&6 for the case where you used exec and removed the node is /root/secretunknownname (deleted). As it shows that the file got deleted: Isn't that enough for what you need?
    – user232326
    Nov 26, 2018 at 17:11
  • I need (actually "needed") to know if the standard input was the null device. Nov 26, 2018 at 17:19
  • 2
    I am trying to find my way in a (very poorly designed!) industrial system. And it sometimes maps input of worker utility to the null device, or, some other times, to the actual hardware device. In both cases using the same code, but with different major/minor dev numbers. We are trying to track when (and why!) it sometimes chose to use one or the other. For now, the stat solution is the only one working. Nov 26, 2018 at 17:23
  • "So, the example on your EDIT doesn't really reproduce the problem that you describe, or: does it?" It does. Input in redirected from the null device. Which is usually accessed through /dev/null, but not necessary. You can "alias" is with mknod s illustrated in my example. Nov 26, 2018 at 17:33

3 Answers 3

20

On linux, you can do it with:

stdin_is_dev_null(){ test "`stat -Lc %t:%T /dev/stdin`" = "`stat -Lc %t:%T /dev/null`"; }

On a linux without stat(1) (eg. the busybox on your router):

stdin_is_dev_null(){ ls -Ll /proc/self/fd/0 | grep -q ' 1,  *3 '; }

On *bsd:

stdin_is_dev_null(){ test "`stat -f %Z`" = "`stat -Lf %Z /dev/null`"; }

On systems like *bsd and solaris, /dev/stdin, /dev/fd/0 and /proc/PID/fd/0 are not "magical" symlinks as on linux, but character devices which will switch to the real file when opened. A stat(2) on their path will return something different than a fstat(2) on the opened file descriptor.

This means that the linux example will not work there, even with GNU coreutils installed. If the versions of GNU stat(1) is recent enough, you can use the - argument to let it do a fstat(2) on the file descriptor 0, just like the stat(1) from *bsd:

stdin_is_dev_null(){ test "`stat -Lc %t:%T -`" = "`stat -Lc %t:%T /dev/null`"; }

It's also very easy to do the check portably in any language which offers an interface to fstat(2), eg. in perl:

stdin_is_dev_null(){ perl -e 'exit((stat STDIN)[6]!=(stat "/dev/null")[6])'; }
5
  • 1
    As the device type of /dev/null is 1:3, you could test for that immediately.
    – RudiC
    Nov 26, 2018 at 15:55
  • 12
    FWIW the question was not about shell syntax. For what is concerning my issue, if the answer had only pointed me to the stat command I would have already been quite satisfied. I don't see the point in starting an editing war between ` and $( supporters. Personally, I prefer $(...) and there are some POSIX rationales in favor of that syntax (pubs.opengroup.org/onlinepubs/9699919799/xrat/… ) But I don't see the point in downvoting an otherwise correct answer for something not related with the question. Nov 26, 2018 at 17:06
  • 2
    Could someone explain why $( ... ) is preferred over backticks in the context of this answer? Nov 26, 2018 at 21:35
  • "what bug does that fix"? It doesn't fix a bug. The $(..) style nests more easily and AIUI is the POSIX preferred approach. I certainly hadn't intended to start any form of editing war, preferring to comment with a suggestion rather than change your excellent answer. Nov 27, 2018 at 9:03
  • @user1717828 see here and here and here for some clarity. Nov 27, 2018 at 9:07
15

On Linux, to determine whether standard input is redirected from /dev/null, you can check whether /proc/self/fd/0 has the same device and inode as /dev/null:

if [ /proc/self/fd/0 -ef /dev/null ]; then echo yes; else echo no; fi

You can use /dev/stdin instead of /proc/self/fd/0.

If you want to check whether standard input is redirected from the null device, you need to compare major and minor device numbers, for example using stat (see also mosvy’s answer):

if [ "$(stat -Lc %t:%T /dev/stdin)" = "$(stat -Lc %t:%T /dev/null)" ]; then echo yes; else echo no; fi

or, if you don’t care about this being Linux-specific,

if [ "$(stat -Lc %t:%T /dev/stdin)" = "1:3" ]; then echo yes; else echo no; fi
6
  • 2
    -ef, True if FILE1 and FILE2 refer to the same device and inode Nov 26, 2018 at 15:13
  • very cool. bash -c 'ls -l /proc/self/fd/0 /dev/null; [[ /proc/self/fd/0 -ef /dev/null ]] && echo dev null' then do that again with </dev/null. +1 Nov 26, 2018 at 17:20
  • 1
    The /dev/stdin being a symlink to the original file is specific to Linux, so all those solutions are Linux-specific anyway. The stat-based ones require GNU or busybox stat. With recent versions of GNU stat, you can use stat - to do a fstat() on fd 0 which would then work on non-Linux systems. Nov 26, 2018 at 18:48
  • 1
    @Stéphane the last part is exactly the situation the OP ran into, which is why I wrote up both solutions, distinguishing between /dev/null and the null device. Nov 26, 2018 at 19:25
  • Oops, missed that. Nov 26, 2018 at 20:50
4

Portably, to check that stdin is the null device (open on /dev/null or not (like a copy of /dev/null)), with zsh (whose stat builtin predates both GNU and FreeBSD stat by the way (not IRIX' though))):

zmodload zsh/stat
if [ "$(stat +rdev -f 0)" = "$(stat +rdev /dev/null)" ]; then
  echo stdin is open on the null device
fi

(note that it doesn't say if the file descriptor was open in read-only, write-only or read+write mode).

To check that it's open on the current /dev/null file specifically (not /some/chroot/dev/null for instance), on Linux only (where /dev/stdin is implemented as a symlink to the file open on fd 0 instead of a special device which when open acts like a dup(0) in other systems):

if [ /dev/stdin -ef /dev/null ]; then
  echo stdin is open on /dev/null
fi

On non-Linux, you can try:

if sh -c 'lsof -tad0 -p"$$" /dev/null' > /dev/null 2>&-; then
  echo stdin is open on /dev/null
fi

You must log in to answer this question.

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