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

时间:2013-09-13 06:15:20

标签: linux bash

我有一个非常非常长的行文件。单行不适合内存。我需要分别处理每一行,所以我想将每一行写入FIFO节点,在行之间发送EOF。这个模型非常适合我正在做的事情,并且是理想的。

这些行无法完整存储在RAM中,因此read内置函数不是一个选项。无论我使用什么命令,都需要在读取数千兆字节的源文件时直接写入 - 无缓冲 - 到FIFO。

我如何实现这一点,最好是使用纯bash和基本的Linux实用程序,而不是我必须安装的特殊程序?我尝试了sed -u 'p'之类的内容,但不幸的是,我似乎无法获得任何通用程序来在行之间发送EOF。

bash版本:

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

更新

我想避免使用读取"行号X"的实用程序/技巧。从文件。这个文件有数千条非常长的行;反复评估相同的换行时间太长了。为了在合理的时间范围内完成,无论读取行的程序是什么,都需要按顺序读取每一行,保存其先前的位置,就像read + pipe一样。

3 个答案:

答案 0 :(得分:3)

让我们考虑一下“散布不适合内存的行”的问题,因为这是一个简单的问题,只需几行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版本就像一个管道(从右到左阅读)。 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

答案 1 :(得分:1)

问题是我应该使用普通文件,而不是FIFO。我看错了。 readhead的工作方式相同:它不记得它在文件中的位置。流记得它。我不知道我在想什么。 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读取时,我不必担心后台任务或阻塞。

答案 2 :(得分:0)

哈希长行应该是完全可行的,但在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的伪代码,它显示了如何做你想做的事情的方向。

请注意,没有记忆就不可行,但我认为这不重要。这些块非常小(可以做得更小)并在它们出现时进行处理。

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