从文件中读取行并直接写入FIFO,无需在RAM中缓冲



我有一个文件,上面有非常非常长的行。 内存中无法容纳单行。 我需要单独处理每一行,所以我想将每一行写入 FIFO 节点,在行之间发送 EOF。 这个模型非常适合我正在做的事情,而且是理想的。

这些行不能完整存储在 RAM 中,因此无法选择内置read。 无论我使用什么命令,都需要在读取数 GB 的源文件时直接写入 FIFO(无缓冲)。

我怎样才能做到这一点,最好是使用纯 bash 和基本的 Linux 实用程序,而不必安装特殊程序? 我已经尝试过类似 sed -u 'p' ,但不幸的是,我似乎无法获得任何通用程序来在行之间发送 EOF。

bash版本:

GNU bash, version 4.2.45(1)-release (x86_64-pc-linux-gnu)

更新

我想避免从文件中读取"行号 X"的实用程序/技巧。 此文件有数千行很长的行;重复评估相同的换行符将花费太长时间。 为了在合理的时间范围内完成此操作,无论哪个程序正在读取行,都需要按顺序读取每一行,保存其先前的位置,就像read + pipe一样。

让我们考虑一下"散列不适合内存的行"这个问题,因为这是一个简单的问题,你可以用几行 Python 来解决。

import sys
import hashlib
def hash_lines(hashname, file):
    hash = hashlib.new(hashname)
    while True:
        data = file.read(1024 * 1024)
        if not data:
            break
        while True:
            i = data.find('n')
            if i >= 0:
                hash.update(data[:i]) # See note
                yield hash.hexdigest()
                data = data[i+1:]
                hash = hashlib.new(hashname)
            else:
                hash.update(data)
                break
    yield hash.hexdigest()
for h in hash_lines('md5', sys.stdin):
    print h

这有点古怪,因为大多数语言对不适合内存的对象没有很好的抽象(一个例外是 Haskell;这可能是 Haskell 的四行)。

注意:如果要在哈希中包含换行符,请使用i+1

哈斯克尔版本

Haskell版本就像一个管道(从右到左阅读)。 Haskell支持惰性IO,这意味着它只在必要时读取输入,因此整行不需要一次在内存中。

更现代的版本将使用管道而不是惰性 IO。

module Main (main) where
import Crypto.Hash.MD5
import Data.ByteString.Lazy.Char8
import Data.ByteString.Base16
import Prelude hiding (interact, lines, unlines)
main = interact $ unlines . map (fromStrict . encode . hashlazy) . lines

问题是我应该使用普通文件,而不是 FIFO。 我看错了。 read的工作方式与head相同:它不记得它在文件中的位置。 溪流会记住它。 我不知道我在想什么。 head -n 1将从流中读取一行,从流已经处于的任何位置开始。

#!/bin/bash
# So we don't leave a giant temporary file hanging around:
tmp=
trap '[ -n "$tmp" ] && rm -f "$tmp" &> /dev/null' EXIT
tmp="$(mktemp)" || exit $?
while head -n 1 > "$tmp" && [ -s "$tmp" ]; do
    # Run $tmp file through several programs, like md5sum
done < "$1"

这非常有效。 head -n 1从基本文件流中按顺序读取每一行。 从空的FIFO读取时,我也不必担心后台任务或阻塞。

散列长行应该是完全可行的,但在 bash 中可能不那么容易。

如果我可以建议使用Python,它可以像这样完成:

# open FIFO somehow. If it is stdin (as a pipe), fine. Of not, simply open it.
# I suggest f is the opened FIFO.
def read_blockwise(f, blocksize):
    while True:
        data = f.read(blocksize)
        if not data: break
        yield data
hasher = some_object_which_does_the_hashing()
for block in read_blockwise(f, 65536):
    spl = block.split('n', 1)
    hasher.update(spl[0])
    if len(spl) > 1:
        hasher.wrap_over() # or whatever you need to do if a new line comes along
        new_hasher.update(spl[1])

这只是面向Python的伪代码,它显示了如何做你似乎想做的事情的方向。

请注意,没有记忆是不可行的,但我认为这无关紧要。这些块非常小(甚至可以做得更小),并在出现时进行处理。

遇到换行符会导致终止旧行的处理并开始处理新行。

最新更新