我正在研究一些在启动时运行的脚本rc.local
,我注意到输出重定向的工作方式非常奇怪。
如果我写类似的东西echo "foo" >&1
,它最终会出现在系统日志中,一切都好。
但是当我写echo "foo" >>/dev/stdout
, 或 时echo "foo" >>/proc/self/fd/1
,我收到错误消息,说没有这样的设备或地址。
进一步调查发现这/proc/self/fd/1
实际上是一个套接字。ls -l /proc/self/fd
打印内容包括rc.local
以下内容:
lr-x------. 1 root root 64 Jul 14 05:28 0 -> /dev/null
lrwx------. 1 root root 64 Jul 14 05:28 1 -> socket:[18485]
lrwx------. 1 root root 64 Jul 14 05:28 2 -> socket:[18485]
lr-x------. 1 root root 64 Jul 14 05:28 255 -> /etc/rc.d/rc.local
可以使用 轻松重现所描述的行为nc
。首先,找出哪个文件描述符绑定到套接字。您可以通过nc -l -p 25566 -c "ls -l /proc/self/fd"
在第一个终端中发出然后telnet localhost 25566
在其他终端中发出来完成此操作。就我而言,它是第五个描述符。
好的。然后,要在第一个终端中重现该问题:
nc -l -p 25566 -c "echo 'hello' >&5"
在第二个航站楼:
telnet localhost 25566
您将在第二个终端输出中看到有关建立和关闭连接的 telnet 消息之间的“hello”。
现在的情况是文件描述符伪文件来自/proc
:
第一个终端:
nc -l -p 25566 -c "echo 'hello' >/proc/self/fd/5"
第二个航站楼:
telnet localhost 25566
现在,第二个终端仅包含有关已建立连接并立即关闭的 telnet 消息,第一个终端显示错误:sh: /proc/self/fd/5: No such device or address
。
编辑:操作系统是Fedora 23 i686服务器
那么,问题来了。>&1
和 和有什么区别>/proc/self/fd/1
?是否有一些通用且可靠的方法将输出重定向到某个文件,该文件与当前的标准输出完全对应?
谢谢。
编辑2:
为了清楚起见,上述情况的 Fedora 23 i686 的准确输入/输出:
Terminal 1 | Terminal 2
$ nc -l -p 25566 -c 'ls -go /proc/self/fd' |
| $ telnet localhost 25566
| Trying ::1...
| Connected to localhost.
| Escape character is '^]'.
| total 0
| lr-x------ 1 64 Jul 14 08:56 0 -> pipe:[19687]
| l-wx------ 1 64 Jul 14 08:56 1 -> pipe:[19688]
| lrwx------ 1 64 Jul 14 08:56 2 -> /dev/tty2
| lr-x------ 1 64 Jul 14 08:56 3 -> pipe:[19687]
| lr-x------ 1 64 Jul 14 08:56 4 -> /proc/1285/fd
| lrwx------ 1 64 Jul 14 08:56 5 -> socket:[19686]
| l-wx------ 1 64 Jul 14 08:56 7 -> /pipe:[19688]
| Connection closed by foreign host.
$ nc -l -p 25566 -c "echo 'hi' >&5" |
| $ telnet localhost 25566
| Trying ::1...
| Connected to localhost.
| Escape character is '^]'.
| hi
| Connection closed by foreign host.
$ nc -l -p 25566 -c "echo 'hi' >/proc/self/fd/5" |
| $ telnet localhost 25566
| Trying ::1...
| Connected to localhost.
sh: /proc/self/fd/5: No such device or address | Escape character is '^]'.
| Connection closed by foreign host.
$ | $
答案1
使用>&N
。它是便携式的,正如您所看到的,它实际上可以与套接字一起使用。
您使用的唯一原因/proc/self/fd
是您正在运行一个需要文件名的程序,并且无法被告知使用已经打开的文件描述符。例如,<(cmd...)
重定向使用它,因为几乎所有命令行实用程序都可以打开按名称指向的文件,但并非所有命令行实用程序都直接支持预先打开的文件描述符。
不过,您的 shell 可以使用预先存在的文件描述符,因此无需经过/proc
.
此外,/proc/NNN/fd/
它是特定于 Linux 的,并且要求您已安装/proc
.在我的 Linux 机器上,/dev/stdout
、/dev/fd/*
和其他都是指向/proc/self/fd/*
等的符号链接,所以它们/proc
也需要。在其他 Unix 上,它们可能有所不同。根据一个旧问题的答案 /dev/stdout
被明确列为 POSIX 外部。
至于为什么重定向不能像您尝试的那样工作:尝试使用strace
,两者之间的区别在于,对于>&N
重定向,bash
调用dup()
文件描述符,并且>/proc/self/fd/N
它只是尝试使用 .txt 将其作为普通文件打开open()
。显然proc
不支持像这样打开新的套接字副本,因此调用失败。流套接字几乎是点对点链接,因此禁止打开新副本似乎并不太不自然。但为什么它适用于管道或与 a dup
,我不知道。
$ ls -l /proc/self/fd/3
lrwx------ 1 itvirta itvirta 64 Jul 14 18:24 /proc/self/fd/3 -> socket:[168157]
$ strace bash -c "echo foo >>/proc/self/fd/3" 2>&1 | grep open.*proc/self
open("/proc/self/fd/3", O_WRONLY|O_CREAT|O_APPEND, 0666) = -1 ENXIO (No such device or address)
还这个答案有一些关于可移植性的信息/proc/NNN/fd
答案2
类似于3>&1
重复现有的重定向文件描述符:这需要相同的打开文件(相同的文件、相同的标志、相同的位置等)并将其插入程序的另一个“输出端口”(另一个文件描述符编号)。 (更准确地说,这会创建一个新的文件描述符,该描述符指向相同的文件描述符)文件描述,但我们没有)
>&1
将描述符复制到自身上,一些 shell 将其完全优化掉。
>/proc/$pid/fd/1
像打开文件这样的重定向/proc/$pid/fd/1
。这将创建一个新的文件描述符。中的文件/proc/*/fd
很特殊,打开它们主要是从现有文件描述符中复制数据。虽然这些文件是符号链接,但它们是“神奇的”;例如,删除的文件或管道显示为损坏的符号链接,但仍然可以像现有文件一样打开,因为内核包含处理/proc/*/fd
条目的特殊代码。所以在大多数情况下>&1
和>/proc/self/fd/1
是等价的。然而,套接字得到不同的待遇。
/*
* In theory you can't get an open on this inode, but /proc provides
* a back door. Remember to keep it shut otherwise you'll let the
* creepy crawlies in.
*/
static int sock_no_open(struct inode *irrelevant, struct file *dontcare)
{
return -ENXIO;
}
(最近版本对代码进行了重新组织,但是即使通过 仍然无法打开套接字/proc/PID/fd/NUM
。)
您无法像大多数其他文件类型一样打开套接字的原因是套接字附加了更多信息,您必须说明如何打开它。例如,打开 TCP 套接字会分配源端口。尽管在某些情况下这可能有意义,但 Linux 内核不允许打开另一个进程的套接字。在进程相同的情况下(/proc/self/fd/NUM
,而不是/proc/OTHERPID/fd/NUM
),它可以将调用转换open
为通常的文件描述符重复,但打开/proc/self/fd
首先是一件不寻常的事情,并且通常不进行套接字重定向因为它通常不起作用,所以内核并未设计为支持这种明智但无用的异常。