Ed *_*ton 14 bash text-processing head
给定以下 3 个脚本:
printf 'a\nb\nc\n' > file && { head -n 1; cat; } < fileprintf 'a\nb\nc\n' | { head -n 1; cat; }{ head -n 1; cat; } < <(printf 'a\nb\nc\n')我期望每个的输出是:
a
b
c
Run Code Online (Sandbox Code Playgroud)
但对于其中一些系统,在某些系统上,情况并非如此。例如,在 cygwin 上:
$ printf 'a\nb\nc\n' > file && { head -n 1; cat; } < file
a
b
c
Run Code Online (Sandbox Code Playgroud)
$ printf 'a\nb\nc\n' | { head -n 1; cat; }
a
Run Code Online (Sandbox Code Playgroud)
$ { head -n 1; cat; } < <(printf 'a\nb\nc\n')
a
Run Code Online (Sandbox Code Playgroud)
是什么导致这些脚本的输出不同?
附加信息 - 这显然不仅仅是一个head问题:
$ printf 'a\nb\nc\n' | { sed '1q'; cat; }
a
$ printf 'a\nb\nc\n' | { awk '1;{exit}'; cat; }
a
$ { sed '1q'; cat; } < <(printf 'a\nb\nc\n')
a
$ { awk '1;{exit}'; cat; } < <(printf 'a\nb\nc\n')
a
Run Code Online (Sandbox Code Playgroud)
shell 中的一种健壮的 POSIX 方式(即不只是调用 awk 或类似的一次来完成所有操作)从输入中读取一些行并将其余行留给不同的命令,无论输入是否来自管道或一份文件?
Gil*_*il' 14
head 可以读取其整个输入。它必须至少读取它输出的内容(否则逻辑上不可能实现),但它可能会读取更多。
通常head要求操作系统读取固定大小的缓冲区(通过调用read系统调用或类似方法)。然后,它在该缓冲区中查找换行符,并打印输出,直到达到所需的行数。
所有符合 POSIX 标准head的调用实现lseek都将输入上的文件位置重置为紧接在已复制到输出的部分末尾之后。然而,只有当文件可查找时,这才是可能的:这包括普通文件,但不包括管道。如果输入是管道,则head读取的任何内容都会从管道中丢弃并且无法放回。<file这解释了您在(常规文件)和|或<()(管道)之间观察到的差异。
上述标准的相关部分是:
当标准实用程序读取可查找输入文件并在到达文件末尾之前无错误地终止时,该实用程序应确保打开的文件描述中的文件偏移量正确定位在该实用程序处理的最后一个字节之后。对于不可查找的文件,该文件的打开文件描述中的文件偏移量状态是未指定的。符合要求的应用程序不应假设以下三个命令是等效的:
Run Code Online (Sandbox Code Playgroud)tail -n +2 file (sed -n 1q; cat) < file cat file | (sed -n 1q; cat)仅当文件可查找时,第二个命令才与第一个命令等效。第三个命令将打开文件描述中的文件偏移量保留为未指定状态。其他实用程序(例如 head、read 和 sh)具有类似的属性。
一些head实现,例如headksh93 的内置实现(在 后启用builtin head,并在构建时包含它)也尝试在输入不可查找时不要将光标保留在其输出的最后一行上,在 ksh93 的情况下,通过读取一次输入一个字节(就像 shellread内置程序通常所做的那样),或者在有这种可能性的系统(不是 Linux)上读取管道内容之前先查看管道的内容。但这些都是例外,因为性能会受到严重影响。