是否可以用 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]
除此之外,您应该能够将 read
和 echo
包装在 while
循环中,尽管存在一些棘手的问题。 (例如,准确再现不以换行符结尾的非空文件。)
但是,从技术上讲,echo
与 cat
一样不属于 sh
;就像 cat
一样,它是一个可能不存在的实用程序(在非 Posix 系统上)。实际上,没有 echo
的环境与没有 cat
的环境的可能性差不多;如果你有 sh
,你就有合理的期望找到标准的命令行实用程序。
备注
最小的 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 兼容实用程序的存在,例如 cat
、dd
、head
和 tail
应该足以满足任何文件复制需求。
(这与@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"
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]
除此之外,您应该能够将 read
和 echo
包装在 while
循环中,尽管存在一些棘手的问题。 (例如,准确再现不以换行符结尾的非空文件。)
但是,从技术上讲,echo
与 cat
一样不属于 sh
;就像 cat
一样,它是一个可能不存在的实用程序(在非 Posix 系统上)。实际上,没有 echo
的环境与没有 cat
的环境的可能性差不多;如果你有 sh
,你就有合理的期望找到标准的命令行实用程序。
备注
最小的 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 兼容实用程序的存在,例如
cat
、dd
、head
和tail
应该足以满足任何文件复制需求。
(这与@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"