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

时间:2016-02-28 03:12:05

标签: linux bash shell posix sh

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

所以我得到的印象是我们可以用符合POSIX标准的shell脚本替换cat(1),但我还没有想出实际的实现。它是否真的可能,或者cat(1)可能缺少sh(1)的哪些功能? (暂时忘掉GNU扩展)

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

2 个答案:

答案 0 :(得分:6)

cat可以将任何文件复制到stdout;该文件不需要是文本文件。例如,它可能包含NUL个,NUL无法在sh字符串中表示。所以这肯定是cat的一个特征,即使不是不可能,也很难实现。 [注1]

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

但是,从技术上讲,echo不再是sh而是cat的一部分;就像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 '\0'; else printf '%s' "$char"; fi
    done < "$1" > "$2"
    

    示例:

    $ printf 'foo\0bar\n\nbye' |
    > while IFS= read -d '' -rn1 char; do
    >   if [ -z "$char" ]; then printf '\0'; 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个字符。由于如果下一个字符是$char,目的是在NUL中存储零个字符,我们可以使用-n1并将分隔符设置为NUL来避免此问题,因为分隔符检查是在NUL被剥离之前完成的。
    • -d ''将行分隔符设置为NUL。见上文。
    • -r避免在输入流中解释 \ ;这是套装中唯一与Posix兼容的选项。

    不言而喻,以上只是理论上的兴趣,或者作为OP的智力测验。在实践中,shell脚本应该只是协调外部实用程序的工作,以及与catddhead和{{1}相关的Posix兼容实用程序的存在应该足以满足任何文件复制需求。

答案 1 :(得分:1)

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

仅使用cat无法复制

sh。这是因为sh没有提供任何方法将字节从一个文件移动到另一个不涉及shell参数的文件,而shell参数不能包含NULL字节。

这是一个简单的例子:

printf 'foo\0bar\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"