20

I'd like to make a bash script output additional information to file descriptors (FDs) greater than or equal to 3, when they are open. To test whether an FD is open, I devised the following trick:

if (printf '' 1>&3) 2>&-; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi

This is sufficient for my needs, but I'm curious as to whether there is a more idiomatic way of testing if an FD is valid. I'm especially interested about whether there exists a mapping of the fcntl(1) syscall to a shell command, which would allow the retrieval of FD flags (O_WRONLY and O_RDWR to test whether the FD is writable, and O_RDONLY and O_RDWR to test whether the FD is readable).

7 Answers 7

18

In ksh (both AT&T and pdksh variants) or zsh, you can do:

if print -nu3; then
  echo fd 3 is writeable
fi

They won't write anything on that fd, but still check if the fd is writable (using fcntl(3, F_GETFL)) and report an error otherwise:

$ ksh -c 'print -nu3' 3< /dev/null
ksh: print: -u: 3: fd not open for writing

(which you can redirect to /dev/null).

With bash, I think your only option is to check if a dup() succeeds like in your approach, though that won't guarantee that the fd is writable (or call an external utility (zsh/perl...) to do the fcntl()).

Note that in bash (like most shells), if you use (...) instead of {...;}, that will fork an extra process. You can use:

if { true >&3; } 2> /dev/null

instead to avoid the fork (except in the Bourne shell where redirecting compound commands always causes a subshell). Don't use : instead of true as that's a special builtin, so would cause the shell to exit when bash is in POSIX compliance mode.

You could however shorten it to:

if { >&3; } 2> /dev/null
1
  • 5
    @mikeserve, re: your edit, what's that with <>? The shell is not going to read from its stderr, why would you want to open it in read+write? What do you mean with what happened to intrinsic?? Aug 25, 2019 at 8:46
6

In the POSIX command Application Usage description you'll find the following:

There are some advantages to suppressing the special characteristics of special built-ins on occasion. For example:

command exec > unwritable-file

does not cause a non-interactive script to abort, so that the output status can be checked by the script.

This is why you can just do:

if    command >&3
then  echo 3 is open >&3
else  ! echo 3 is not open
fi    2<>/dev/null

Or...

{ command >&3
  printf %s\\n%.0d  string "0$(($??8:0))" >&"$(($??1:3))"
} 2<>/dev/null

Which will write string followed by a \newline either to stdout or 3 and still pass on a non-zero exit status when 3 is not open because the math done on $? winds up failing to convert the octal 08 to %decimal but truncates to nothing at all the octal 00.

Or...

command exec >&3 || handle_it

But if you're using ksh93, you can just do:

fds

For a list of of open file descriptors. Add -l to see where they go.

2
  • command exec >&3 || echo $? does not echo the exit status, but command >&3 || echo $? does.
    – jarno
    Mar 31, 2021 at 17:18
  • This doesn't actually work on bash -- gives '3: Bad file descriptor' if it is not open
    – Chris Dodd
    Apr 13 at 20:13
3

Open file descriptors can be found in /proc/<pid>/fd. To list, for example, the open file descriptors of the current shell you can issue ls -l /proc/$$/fd which should give you something like:

total 0
lrwx------ 1 testuser testuser 64 jun  1 09:11 0 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 1 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 2 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:39 255 -> /dev/pts/3

When you open a file using:

touch /tmp/myfile
exec 7</tmp/myfile

It should be listed by a new ls -l /proc/$$/fd:

lr-x------ 1 testuser testuser 64 jun  1 09:11 7 -> /tmp/myfile

If you close the file descriptor again using exec 7>&- it is also not listed in /proc/$$/fd anymore.

8
  • 2
    All this is quite specific to Linux. FWIW.
    – lcd047
    Jun 1, 2015 at 9:27
  • 1
    Tested it on Linux as well as on Solaris (10 and 11). The difference is that you need to use pfiles <pid> to see which file descriptor is connected to which file while ls -l displays the connection on Linux.
    – Lambert
    Jun 1, 2015 at 9:31
  • 1
    I like the compactness of [ -e /proc/$$/fd/3 ], but I prefer not to rely on procfs, as it is deprecated in FreeBSD and possibly other un*ces as well.
    – Witiko
    Jun 1, 2015 at 9:33
  • 1
    Brings me to the alternative of using pfiles <pid> or lsof -p <pid> to see which file descriptors are open.
    – Lambert
    Jun 1, 2015 at 9:33
  • 1
    /proc doesn't exist at all on OpenBSD. On FreeBSD and NetBSD it has to be mount-ed explicitly, and /proc/<PID> don't have a subdirectory fd.
    – lcd047
    Jun 1, 2015 at 9:35
3

Your trick looks cute; but for an idiomatic way I wonder why you didn't use:

if ( exec 1>&3 ) 2>&-
6
  • This is, indeed, a cleaner way.
    – Witiko
    Jun 1, 2015 at 9:34
  • 6
    That creates a subshell though which is most shells means forking a process. That doesn't guarantee the fd is writeable. You can use { true >&3; } 2> /dev/null to avoid the fork. Or { command exec >&3; } 2> /dev/null if you want to redirect stdout to it. Jun 1, 2015 at 15:28
  • @Stephane; The subshell trick that @Witiko invented was to not affect the file descriptors of the current environment when using a redirection to obtain a redirection. - Could you elaborate on the "writable fd" you mention?
    – Janis
    Jun 1, 2015 at 15:39
  • 2
    { true >&3; } 2> /dev/null will not affect the current environment either and won't fork (except in the Bourne shell). I mean that (exec 1>&3) 2>&- will return true for a fd open in read-only mode. Jun 1, 2015 at 15:52
  • 1
    exec being a special builtin will exit the shell if it fails (for bash, only when in POSIX compliance mode). command exec prevents that. true is not a special builtin. Note that exec and command exec do affect the current environment (that's why I said if you want to redirect stdout to it). Jun 1, 2015 at 16:02
0

For Linux atleast, [[ -e /dev/fd/${FD} ]] will do, where FD is the bash-variable that stores the FD you want to test. The command succeeds when FD is open, fails otherwise.

Demo:

exec 3<>/tmp/some-file
[[ -e /dev/fd/3 ]] && echo 'FD is open' || echo 'FD is closed'
exec 3<&- ; exec 3>&-
[[ -e /dev/fd/3 ]] && echo 'FD is open' || echo 'FD is closed'
-1

If you are interested in a low forking solution so as to use it repeatdly, I would suggest this function:

checkfd() {
    exec 2>/dev/null
    if exec >&3 ; then
        exec 1>/dev/tty
        echo "fd3 OK"
    else
        echo "fd3 KO"
    fi
    exec 2>/dev/tty
}

And here is what it produces with a zsh:

$ checkfd            
fd3 KO
$ checkfd 3>/dev/null
fd3 OK
$
7
  • In most shells exec >&3 will kill the shell when 3 is not open.
    – mikeserv
    Jun 1, 2015 at 16:52
  • At least it is working on zsh and bash. Could you provide the shell on which the failing exec caused an exit?
    – dan
    Jun 1, 2015 at 16:56
  • Yeah. In bash do set -o posix and try again. In zsh... i think it's a matter of setting the env var POSIX_BUILTINS to a not-null value - but i forget offhand. In any case, zsh is not a shell which attempts to POSIX compliance, and so it is definitively non-standard. Both of those shells eschew compatibility for what some believe is convenience.
    – mikeserv
    Jun 1, 2015 at 17:01
  • It is also working on plain Bourne shell.
    – dan
    Jun 1, 2015 at 17:02
  • In bash, with set -o posix a try is successful.
    – dan
    Jun 1, 2015 at 17:05
-1

The question is quite old - but anyway - why just do not use builtins?

for i in {0..5} ; do if [ -t $i ]; then echo "$i is a valid FD"; else echo "$i is INVALID FD"; fi; done

Output:

0 is a valid FD
1 is a valid FD
2 is a valid FD
3 is INVALID FD
4 is INVALID FD
5 is INVALID FD

So, to answer the question - would suggest:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi
3
  • 2
    -t does not test if a file descriptor is valid, but if it's connected to a tty. Prepend a echo yup | to your script, and will say that 0 is INVALID FD, while in fact it's very valid fd, a pipe.
    – mosvy
    Feb 25, 2020 at 8:21
  • @mosvy According to the POSIX standard, the -t test only returns true if the file descriptor is associated with a terminal. The reference is here. Sep 23, 2021 at 14:54
  • 1
    @SiuChingPong-AsukaKenji- "associated with a terminal" is the same thing as "connected to a tty", but using different words.
    – mosvy
    Oct 16, 2021 at 9:07

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.