在 bash 中编写 /dev/fd/X 的可移植方法

在 bash 中编写 /dev/fd/X 的可移植方法

我想在使用文件之前将其删除,因此在本示例中我想在paste启动之前取消链接 col1、col2 ... col1000。

exec 3< col1 4< col2 ... 1002< col1000
rm col1 col2 ... col1000
paste /dev/fd/3 /dev/fd/4 ... /dev/fd/1002

这在 GNU/Linux+bash 下工作得很好。但/dev/fd/X不可移植到其他系统。

我可以使用bash的命令替换:

exec 3< col1 4< col2 ... 1002< col1000
rm col1 col2 ... col1000
paste <(cat <&3) <(cat <&4) ... <(cat <&1002)

但这会让我每个文件花费一个进程。

我可以以适用于任何系统上的 Bash 的方式编写上述内容吗?也许有一些特殊的 Bash 语法?

答案1

最简单的可移植解决方案是替换为一个以文件描述符编号作为参数的函数,并使用带有文件描述符重定向的内置paste函数循环它们:read

paste()
{
    at_least_one_read_succeeded=true
    while $at_least_one_read_succeeded
    do
        at_least_one_read_succeeded=false
        first_fd=true
        for fd in "$@"
        do
            if ! $first_fd
            then
                printf '\t'
            fi
            if IFS= read -r line <&"$fd"
            then
                at_least_one_read_succeeded=true
            fi
            printf '%s' "$line"
            first_fd=false
        done
        printf '\n'
    done
}

最大的缺点是这会慢几个数量级,因为 shell 的read内置函数别无选择,只能进行单独的read系统调用对于每个字节。类似地,该printf命令的每次调用都必须至少执行一次write系统调用(理论上,有人可以编写一个 shell,通过使用优化编译器和 JIT VM 所做的相同分析,能够在这种情况下优化 I/O) ,但目前不存在这样的 shell)。在许多情况下,在现代硬件上,差异仍然可以忽略不计,但由于您的示例包含一千个文件,因此您实际上可能会看到性能下降,这在您的用例中很重要。

当然,除了尊重您的时间和精力之外,没有什么可以阻止您在 shell 中滚动自己的缓冲 I/O。例如,您可以dd bs=4096 count=1一次将 4096 个字节读入每个 FD 的单独变量中,然后从中抓取行,直到用完换行符为止。例如,您可以创建一个名为 的 bash 数组buffers,使用 读入其中buffers[$fd]=$(dd bs=4096 count=1 <$&fd),使用 抓取其中的第一行${buffers[$fd]%%'$\n'*}(用删除替换从后面贪婪地匹配换行符后跟任何字符的子字符串),等等。老实说,如果您正在考虑这一点,并且如果不将这些col文件保留在磁盘上是如此重要,您甚至可以考虑将这些列文件的全部内容读取到bash变量中 - 我不知道您的数据大小,但如果您的系统允许bash分配那么多,如果bash它自己处理它,那么如果它是真的防止这些文件保留在磁盘上很重要,也许根本不让这些内容命中指定文件就足够重要了。


但退后一步,也许这已经足够好了?

paste col1 col2 ... col1000 &  # background `paste`
# optionally, put a tiny sleep here
rm col1 col2 ... col1000
wait  # wait for `paste` to finish

因为老实说,总有一个时间窗口,在此期间col*文件可能无法清理(如果rm在您的代码中甚至在rm之前运行的代码中断电怎么办paste?)。如果该窗口在启动并通过系统调用命中paste其所有参数时长 <=1 秒(甚至在现代硬件上甚至少于一毫秒可能就足够了),那么情况是否真的更糟?open


另一件需要考虑的事情是使用临时文件系统。许多操作系统在某些路径上提供内存中文件系统。例如,在几乎每个 Linux 发行版上,/run都有一个tmpfs挂载点,每个用户通常都会在此处获得一个私有目录/run/$(id -u):如果您断电或以其他方式重新启动或关闭盒子,则其中的任何内容tmpfs都将无法保存。我不知道您需要在哪些其他系统上运行,但值得检查这些系统是否有临时内存文件系统或保证在重新启动时被操作系统目录擦除。

相关内容