是否可以用 POSIX sh(1) 复制 cat(1)?

Is it possible to replicate cat(1) with POSIX sh(1)?

POSIX sh(1) 能够进行各种文件描述符操作(相当于 open(2)close(2)dup(2) 等。 ) 以及 read-ing 来自 STDIN 的单行。

所以我的印象是我们可以用 POSIX 兼容的 shell 脚本替换 cat(1),但我还没有想出一个实际的实现。真的有可能吗,或者,sh(1) 可能缺少 cat(1) 的什么功能? (暂时忘记 GNU 扩展)

不要问我为什么要那样做。作为智力测验,也许?

cat可以复制任意文件到stdout;该文件不需要是文本文件。例如,它可能包含 NUL,而 NUL 不能用 sh 字符串表示。因此,这肯定是 cat 的一项功能,即使不是不可能,也很难实施。 [注1]

除此之外,您应该能够将 readecho 包装在 while 循环中,尽管存在一些棘手的问题。 (例如,准确再现不以换行符结尾的非空文件。)

但是,从技术上讲,echocat 一样不属于 sh;就像 cat 一样,它是一个可能不存在的实用程序(在非 Posix 系统上)。实际上,没有 echo 的环境与没有 cat 的环境的可能性差不多;如果你有 sh,你就有合理的期望找到标准的命令行实用程序。


备注

  1. 最小的 Posix 兼容 read 接受的唯一选项是 -r。但是,如果我们有 read 的 bash 实现,我们可以逐个字符地复制文件,即使 NUL 字符实际上永远不会出现在 shell 变量中:

    while IFS= read -d '' -rn1 char; do
      if [ -z "$char" ]; then printf '[=10=]'; else printf '%s' "$char"; fi
    done < "" > ""
    

    示例:

    $ printf 'foo[=11=]bar\n\nbye' |
    > while IFS= read -d '' -rn1 char; do
    >   if [ -z "$char" ]; then printf '[=11=]'; else printf '%s' "$char"; fi
    > done |
    > hd
    00000000  66 6f 6f 00 62 61 72 0a  0a 62 79 65              |foo.bar..bye|
    0000000c
    

    该调用中 read 的完整选项集经过精心设计,以解决 bash 实现中的各种特性:

    • IFS= 避免从结果中删除尾随空白字符。
    • -n1 导致读取一个字符,直到分隔符。直觉上,-N1 会更自然,因为 -N1 会忽略分隔符。但是,read 还会从输入中删除 NUL 个字符。如果下一个字符是 NUL,目的是在 $char 中存储零个字符,我们可以通过使用 -n1 并将定界符设置为 NUL 来避免这个问题,这之所以有效,是因为分隔符检查是在 NUL 被剥离之前完成的。
    • -d '' 将行分隔符设置为 NUL。见上文。
    • -r 避免在输入流中解释 \;这是集合中唯一 Posix 兼容的选项。

    不言而喻,以上只是理论上的兴趣,或者作为 OP 的智力测验。实际上,shell 脚本应该只做协调外部实用程序的工作,以及 Posix 兼容实用程序的存在,例如 catddheadtail 应该足以满足任何文件复制需求。

(这与@rici 的回答基本相同,但有一个无法单独显示 sh 的文件的具体示例。)

cat 无法单独使用 sh 复制。这是因为 sh 没有提供任何不涉及 shell 参数的将字节从一个文件移动到另一个文件的方法,并且 shell 参数不能包含 NULL 字节。

这是一个简单的例子:

printf 'foo[=10=]bar\n' > tmp.txt  # Create a file containing a null byte
IFS= read -r line < tmp.txt    # Real that line into a variable.
echo "$line"                   # Only outputs "foo"