10

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).

10

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
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.

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.

  • 2
    All this is quite specific to Linux. FWIW. – lcd047 Jun 1 '15 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 '15 at 9:31
  • 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 '15 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 '15 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 '15 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>&-
  • This is, indeed, a cleaner way. – Witiko Jun 1 '15 at 9:34
  • 5
    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. – Stéphane Chazelas Jun 1 '15 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 '15 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. – Stéphane Chazelas Jun 1 '15 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). – Stéphane Chazelas Jun 1 '15 at 16:02
-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
$
  • In most shells exec >&3 will kill the shell when 3 is not open. – mikeserv Jun 1 '15 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 '15 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 '15 at 17:01
  • It is also working on plain Bourne shell. – dan Jun 1 '15 at 17:02
  • In bash, with set -o posix a try is successful. – dan Jun 1 '15 at 17:05

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.