我有一个非常非常长的行文件。单行不适合内存。我需要分别处理每一行,所以我想将每一行写入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一样。
答案 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支持惰性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。我看错了。 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读取时,我不必担心后台任务或阻塞。
答案 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的伪代码,它显示了如何做你想做的事情的方向。
请注意,没有记忆就不可行,但我认为这不重要。这些块非常小(可以做得更小)并在它们出现时进行处理。
遇到换行符会导致终止旧行的处理并开始处理新行。