4

In the question "Testing if a file descriptor is valid", a test is sought for testing whether a file descriptor is opened or not.

The answers all focus on testing whether the file descriptor is opened for output, but how may one test whether the file descriptor is opened for input?

This came up in a comment thread for an answer to another question, where the answer said, paraphrasing,

if [ -n "$1" ]; then
    # read input from file "$1" (we're assuming it exists)
elif [ ! -t 0 ]; then
    # read input from standard input (from pipe or redirection)
else
    # no input given (we don't want to read from the terminal)
fi

The problem with [ ! -t 0 ] is that the -t test is true if the file descriptor is open and associated with a terminal. If the test is false, then the descriptor is either closed, or not associated with a terminal (i.e. we're reading from a pipe or redirection). Testing with [ ! -t 0 ] is therefore not a guarantee that the file descriptor is even valid.

How to determine whether it's valid (so that read would not complain) or whether it's closed?

4

The check is easy to do in C with either read(fd, 0, 0) or (fcntl(fd, F_GETFL) & O_WRONLY) == 0. I wasn't able to trick any standard utility into doing just that, so here are some possible workarounds.

On linux, you can use /proc/PID/fdinfo/FD:

if [ ! -d "/proc/$$/fd/$fd" ] && grep -sq '^flags.*[02]$' "/proc/$$/fdinfo/$fd"; then
    echo "$fd" is valid for input
fi

On OpenBSD, NetBSD and Solaris, you can use /dev/fd/FD and dd with a 0 count:

if dd if=/dev/fd/"$fd" count=0 2>/dev/null; then
    echo "$fd" is valid for input
fi

On FreeBSD, only the first 3 fds are provided by default in /dev/fd; you should either mount fdescfs(5) on /dev/fd or:

if (dd if=/dev/fd/0 count=0 <&"$fd") 2>/dev/null; then
    echo "$fd" is valid for input
fi

Notes:

On some systems, bash does its emulation of /dev/fd/FD, and so cat </dev/fd/7 may work completely different from cat /dev/fd/7. Same caveat applies to gawk.

A read(2) with length 0 (or an open(2) without O_TRUNC in its flags) will not update the access time or any other timestamps.

On linux, a read(2) will always fail on a directory, even if it was opened without the O_DIRECTORY flag. On other Unix systems, a directory can be read just like another file.

  • It's the open() that fails on BSDs, so you migh as well do if (true < /dev/fd/0) 2> /dev/null <&"$fd" (assuming fd is not 2). – Stéphane Chazelas Nov 29 '18 at 14:29
  • Thanks, but that would return true if the shell is bash, which does its own implementation of /dev/fd. Example: bash -c '(true </dev/fd/0) <&7 && echo fine' 7>/dev/null – mosvy Nov 29 '18 at 15:08
  • Ah! Good catch. – Stéphane Chazelas Nov 29 '18 at 16:20
  • One could implement this using ctypes.sh ;-). – Stephen Kitt Jan 2 at 16:55

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.