测试文件描述符是否有效


12

我想让bash脚本在打开时将附加信息输出到大于或等于3的文件描述符(FD)。为了测试FD是否打开,我设计了以下技巧:

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

这足以满足我的需求,但是我很好奇是否有一种惯用的方法来测试FD是否有效。我特别是约是否存在所述的映射感兴趣fcntl(1)系统调用到外壳命令,这将允许FD标志的检索(O_WRONLYO_RDWR测试的FD是否可写,和O_RDONLYO_RDWR测试的FD是否可读)。

Answers:


12

ksh(AT&T和pdksh变体)或中zsh,您可以执行以下操作:

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

他们不会在该fd上写任何东西,但仍会检查fd是否可写(使用fcntl(3, F_GETFL)),否则报告错误:

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

(您可以将其重定向到/dev/null)。

使用bash,我认为您唯一的选择是检查dup()您的方法是否成功,尽管这不能保证fd是可写的(或调用外部实用程序(zsh/ perl...)来执行fcntl())。

请注意,在in bash(与大多数shell一样)中,如果使用(...)代替{...;},则会产生一个额外的过程。您可以使用:

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

而是避免进行分叉(在Bourne shell中,重定向复合命令始终会导致子shell的情况除外)。不要使用:代替,true因为这是一个特殊的内置函数,因此当bash处于POSIX遵从模式时,将导致外壳退出。

但是,您可以将其缩短为:

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

@mikeserve,回复:您的编辑是什么意思<>?Shell不会从其stderr中读取数据,为什么要以读写方式打开它?你对内在发生什么意思
斯特凡·查泽拉斯

7

在“ POSIX 应用程序使用情况”描述中,您将找到以下内容:command

有时抑制特殊内置插件的特殊特性有一些优点。例如:

command exec > unwritable-file

不会导致非交互式脚本中止,因此脚本可以检查输出状态。

这就是为什么您可以这样做:

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

要么...

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

当未打开3时,它将在字符串后跟一个\newline的字符串写入stdout或3,并且仍然传递非零退出状态,因为结束时完成的数学运算$?无法将八进制08转换为%decimal,但被截断为零八进制00

要么...

command exec >&3 || handle_it

但是,如果您使用ksh93,则可以执行以下操作:

fds

有关打开文件描述符的列表。添加-l以查看他们的去向。


3

可以在中找到打开的文件描述符/proc/<pid>/fd。例如,要列出当前外壳程序的打开文件描述符,您可以发出以下ls -l /proc/$$/fd内容:

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

当您使用以下方式打开文件时:

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

应该用新的列出ls -l /proc/$$/fd

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

如果再次使用关闭文件描述符exec 7>&-,则也不再列出/proc/$$/fd


2
所有这些都是Linux特有的。FWIW。
lcd047

1
在Linux和Solaris(10和11)上对其进行了测试。区别在于您需要在Linux上显示连接时使用pfiles <pid>查看哪个文件描述符连接到哪个文件ls -l
Lambert

我喜欢的紧凑性[ -e /proc/$$/fd/3 ],但是我不希望依赖于procfs,因为FreeBSD和其他可能的un * ces中不推荐使用它。
Witiko 2015年

1
带给我使用pfiles <pid>lsof -p <pid>查看打开了哪些文件描述符的替代方法。
Lambert

1
/proc在OpenBSD上根本不存在。在FreeBSD和NetBSD上,必须mount显式地-ed,并且/proc/<PID>没有子目录fd
lcd047

3

你的把戏看起来很可爱;但以一种惯用的方式,我想知道为什么您不使用:

if ( exec 1>&3 ) 2>&-

确实,这是一种更清洁的方法。
Witiko 2015年

5
这将创建一个子外壳,尽管大多数外壳意味着分叉一个进程。那不能保证fd是可写的。您可以使用{ true >&3; } 2> /dev/null以避免叉。或者,{ command exec >&3; } 2> /dev/null如果您要将标准输出重定向到它。
斯特凡Chazelas

@Stephane; @Witiko发明的subshel​​l技巧是在使用重定向获取重定向时不影响当前环境的文件描述符。-您能详细说明您提到的“可写fd”吗?
Janis 2015年

2
{ true >&3; } 2> /dev/null也不会影响当前环境,也不会分叉(Bourne shell中除外)。我的意思是,(exec 1>&3) 2>&-对于以只读模式打开的fd ,它将返回true。
斯特凡Chazelas

1
exec如果是特殊的内置插件,它将在失败时退出外壳(对于bash,仅当处于POSIX遵从模式时)。command exec防止这种情况。true不是特殊的内置函数。请注意,execcommand exec确实会影响当前的环境(这就是为什么我说,如果你想标准输出重定向到它)。
斯特凡Chazelas

-1

如果您对低分叉的解决方案感兴趣以便可以重复使用,建议您使用以下功能:

checkfd(){
    exec 2> / dev / null
    如果exec>&3; 然后
        exec 1> / dev / tty
        回声“ fd3 OK”
    其他
        回声“ fd3 KO”
    科幻
    exec 2> / dev / tty
}

这是它使用产生的结果zsh

$ checkfd            
fd3 KO
$ checkfd 3> / dev / null
fd3好
$

在大多数情况下exec >&3,当3未打开时,它们会杀死该外壳。
mikeserv

至少它正在zsh和上工作bash。您能提供故障exec导致外壳的原因exit吗?

是的 在bash执行,set -o posix然后重试。在zsh...我认为这是将env var设置POSIX_BUILTINS为非null值的问题-但我忘了用完了。无论如何,zsh不是试图符合POSIX的外壳,因此它绝对是非标准的。这些外壳都避开了兼容性,因为有人认为方便。
mikeserv

它还在普通的Bourne外壳上工作。
2015年

在bash中,set -o posix尝试成功。
2015年

-1

这似乎超级简单(请参阅评论):

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

额外的... [-r文件]测试不会指示是否实际上正在等待读取任何数据(/ dev / null通过了此测试(请参见注释))。

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

超时参数(read -t)需要一些较小的数字,否则可能会丢失需要一些计算的数据。需要可读性测试([-r file]),否则,如果文件不可读,则read命令会炸弹。由于字节数为零(读-N 0),因此实际上将不会读取任何数据。


如果您打算使用Linux系统,则不妨看看/proc/<pid>/fdinfo/<fd>,其中列出了所有打开文件模式flags:-参见此处。关于第二部分的原因(即使解决了明显的错误):read -t .1 -N0 <&4也不会告诉您是否需要在fd 4上读取数据:只需尝试使用4</dev/null
mosvy

当然,[ -r /proc/$$/fd/$FD ]它不会告诉您文件描述符$FD是否可读,但是如果打开了该文件描述符,则可以使用另一个文件描述符再次打开该文件描述符,以供阅读:exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
mosvy

-1

这个问题已经很老了-但无论如何-为什么不使用内建函数?

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

输出:

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

因此,要回答这个问题-建议:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi

-t不会测试文件描述符是否有效,但是不会连接到tty。前置一个echo yup |到你的脚本,并会说0 is INVALID FD,而实际上这是非常有效的FD,一管。
mosvy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.