14

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

15

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
| improve this answer | |
  • 1
    @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?? – Stéphane Chazelas Aug 25 '19 at 8:46
7

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.

| improve this answer | |
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.

| improve this answer | |
  • 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>&-
| improve this answer | |
  • 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
$
| improve this answer | |
  • 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
-1

This seems super easy (see comments):

[ -r /proc/$$/fd/$FD ] && echo "File descriptor $FD is readable"
[ -w /proc/$$/fd/$FD ] && echo "File descriptor $FD is writable"

As an extra... The [ -r file ] test does not indicate if any data is actually waiting to be read (/dev/null passes this test (see comments)).

[ -r /proc/$$/fd/4 ] \
  && [ read -t 0.0001 -N 0 <&4 ] \
  && echo "Data is waiting to be read from file descriptor 4"

Some small number for the timeout argument (read -t) is required or data which needs some calculation might be missed. The readable test ([ -r file ]) is required or the read command will bomb if file is not readable. This will not actually read any data because the byte count is zero (read -N 0).

| improve this answer | |
  • if you're going to assume a Linux system, you may just as well have a look at /proc/<pid>/fdinfo/<fd>, which lists all of the open files mode under flags: -- see here. For why your 2nd part (even after fixing the glaring mistake): read -t .1 -N0 <&4 will not tell if there's data to be read on fd 4: just try with 4</dev/null. – mosvy Dec 17 '19 at 2:58
  • 1
    And of course, [ -r /proc/$$/fd/$FD ] does not tell you whether the file descriptor $FD is readable, but if the file it was open from could be open again, with another file descriptor, for reading: exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7 – mosvy Dec 17 '19 at 3:49
-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
| improve this answer | |
  • 1
    -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 at 8:21

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.