通过文件描述符改组和 /dev/fd 的两个输入管道

通过文件描述符改组和 /dev/fd 的两个输入管道

我想将两个程序合二为一。如果我的 shell 支持,我可以使用工艺替代。例如,要以无关顺序列出两个文件的公共行,我可以使用

comm -12 <(sort a) <(sort b)

然而,在 plain 中不存在进程替换sh。我可以通过创建一个完整的 POSIX 可移植性来做到这一点命名管道,但这很麻烦,因为它需要找到 FIFO 的目录并随后进行清理。一个很好的实用折衷方案是使用两个 shell 管道结构并使用文件描述符改组将一个管道移动到另一个文件描述符,然后使用/dev/fd来指定管道,这适用于大多数 Unix 变体:

sort a | { exec 3<&0; sort b | comm -12 /dev/fd/0 /dev/fd/3; }

这适用于 dash、bash、BusyBox sh 等,但不适用于 ksh93 和 mksh。为什么?

$ mksh -c 'sort a | { exec 3<&0; sort b | comm -12 /dev/fd/0 /dev/fd/3; }'
$ ksh93 -c 'sort a | { exec 3<&0; sort b | comm -12 /dev/fd/0 /dev/fd/3; }'
comm: /dev/fd/0: No such device or address

答案1

与其他命令上的重定向不同,exec当 shell 执行外部程序时,内置命令上的重定向可能会关闭。POSIX 允许这两种行为。 Ksh(ATT ksh、pdksh 和 mksh)在执行外部实用程序时关闭这些描述符(即,对于exec内置程序上的重定向,在调用dup2执行重定向后,它们FD_CLOEXEC在新描述符上设置标志)。 Bourne shell、dash、bash、zsh 和 BusyBox sh 将此重定向视为任何其他重定向。

解决双输入管道问题的一种更可移植的解决方案(假设存在/dev/fd)是对读取输入的命令执行另一次重定向,将文件描述符移动到新的文件描述符。此额外的重定向不会在新描述符上设置 close-on-exec 标志。

sort a | { exec 3<&0; sort b | comm -12 /dev/fd/0 /dev/fd/4 4<&3; }

这适用于 pdksh/mksh 和 ksh93r,但不适用于最新版本的 ksh(93s+ 2008-01-31 或 93u+ 2012-08-01)。我不明白 ksh 在那里做什么。

相关内容