高效地跳过流的前 N ​​个字节

高效地跳过流的前 N ​​个字节

我知道使用tail -c +N,但它非常慢,并且固定了一个 CPU 核心:

 leijurvs-MacBook-Pro:Downloads leijurv$ time cat /dev/zero | head -c 100000000 | shasum -a 256
a993f8c574e0fea8c1cdcbcd9408d9e2e107ee6e4d120edcfa11decd53fa0cae  -
cat /dev/zero  0.00s user 0.02s system 4% cpu 0.471 total
head -c 100000000  0.01s user 0.03s system 9% cpu 0.470 total
shasum -a 256  0.45s user 0.02s system 99% cpu 0.469 total
 leijurvs-MacBook-Pro:Downloads leijurv$ time cat /dev/zero | head -c 100000000 | tail -c +2 | shasum -a 256
f4be792b71a024a60d77b3ac4c1c2b88ac51480fa25f88d10865827f8c086506  -
cat /dev/zero  0.01s user 0.03s system 0% cpu 7.241 total
head -c 100000000  0.02s user 0.03s system 0% cpu 7.240 total
tail -c +2  7.20s user 0.03s system 99% cpu 7.247 total
shasum -a 256  0.51s user 0.04s system 7% cpu 7.247 total
 leijurvs-MacBook-Pro:Downloads leijurv$ 

head很好。我使用 shasum 获取前 100 MB 的零head:需要 0.47 秒。

我过去常常tail -c +2跳过第一个字节,突然需要 7.2 秒。

tail在此期间固定一个CPU核心。

我怎么能够表现地跳过流的前 N ​​个字节?

答案1

如果您只想在输入之前跳过文件的第一个字节shasum,您可以这样做(此处使用zsh语法,因为time输出格式表明这就是您正在使用的 shell):

time cat /dev/zero | head -c 100000000 |
   (LC_ALL=C read -u0 -k1 && shasum -a 256)

然后,这意味着没有额外的过程,第一个字节只是从管道中读取read在开始之前shasum

LC_ALL=C read -u0 -k1reading1字符(k此处用于钥匙最初read -k是从终端读取按键),这里的字符是单字节LC_ALL=C,这要归功于文件描述符unit 编号0(stdin;这里为了明确我们是从流读取而不是从终端读取)。

有了bash外壳,相当于read命令是LC_ALL=C IFS= read -rd '' -n1.

zsh 的等价物read -k通常是read -N,但这对于包含 NUL 字节的输入不起作用,它bash只是read条带(另外-N,从 ksh93 复制的是一个相对较新的添加,在 macos 上找到的古老版本的 bash 中不可用)。通过将delimiter 设置为 NUL 字节(此处表示为空字符串),我们可以避免这种情况。是从第一个 NUL 分隔记录中-n1读取一个字符(再次通过 生成字节)。LC_ALL=C然而,这意味着它不能像 一样适应不同数量的字节-rd '' -n2,如果第一个字节为 0,我们只会跳过一个字节。

对于其他 shell,您可以将该read命令替换为dd bs=1 count=1 > /dev/null 2>&1(change count,不要bs跳过超过一个字节)。head -c 1 > /dev/null也将与一些那些head支持非标准-c选项的实现,但不是全部(特别是,不是 FreeBSD 的,所以可能也不是 macOS 的),因为有些实现会以固定大小的块读取输入,即使请求输出更少的字节也是如此。但请注意,与上述相反read,当它们无法读取该一个字节时,它们不会报告失败退出状态,因此shasum在任何情况下都会运行。

当校验和是常规文件而不是管道时,您可以执行以下操作跳绳更有效(假设需要跳过多个字节)寻求在文件中,而不是读取并丢弃要跳过的部分(仍然zsh语法):

zmodload zsh/system
{ sysseek 1234567 && shasum -a 256; } < some-big-file

跳过前 1234567 个字节。

或者使用 ksh93:

shasum -a 256 < some-big-file <#((1234567))

与其他贝壳和一些dd(我不知道 macOS 的实现),你可以这样做:

{ dd bs=1 skip=1234567 count=0 2> /dev/null; shasum -a 256; } < some-big-file

然而使用count=0并不便携。当计数为 0 时,并非所有dd实现都会执行此处的操作。有些甚至会将其理解为。lseek()count=infinity

答案2

Mac OSXtail速度很慢。

brew install coreutils然后切换来gtail解决问题。

 leijurvs-MacBook-Pro:~ leijurv$ time cat /dev/zero | head -c 100000000 | tail -c +2 | shasum -a 256
f4be792b71a024a60d77b3ac4c1c2b88ac51480fa25f88d10865827f8c086506  -
cat /dev/zero  0.01s user 0.03s system 0% cpu 7.153 total
head -c 100000000  0.02s user 0.03s system 0% cpu 7.152 total
tail -c +2  7.07s user 0.03s system 99% cpu 7.159 total
shasum -a 256  0.51s user 0.06s system 7% cpu 7.154 total
 leijurvs-MacBook-Pro:~ leijurv$ time cat /dev/zero | head -c 100000000 | gtail -c +2 | shasum -a 256
f4be792b71a024a60d77b3ac4c1c2b88ac51480fa25f88d10865827f8c086506  -
cat /dev/zero  0.00s user 0.02s system 4% cpu 0.497 total
head -c 100000000  0.02s user 0.08s system 18% cpu 0.496 total
gtail -c +2  0.05s user 0.10s system 30% cpu 0.496 total
shasum -a 256  0.47s user 0.02s system 99% cpu 0.496 total
 leijurvs-MacBook-Pro:~ leijurv$ 

相关内容