Как запустить команду на части входного файла
У меня есть файл ~ 40 ГБ и команда фильтра, которая по какой-то причине прерывается, когда я пытаюсь запустить его в файле (даже когда он передается по каналу).
Но. Это не дает сбоя, когда я делю входной файл на множество маленьких файлов, пропускаю каждый из них через фильтр и объединяю выходные данные.
Итак, я ищу способ сделать:
- разбить файл на небольшие блоки (10 МБ?)
- для каждого блока выполнить какую-то команду на нем
- объединить вывод в правильном порядке
но без предварительного разделения файла (я не хочу использовать столько места на диске).
Я могу написать такую программу сам, но, возможно, уже есть что-то, что будет делать то, что мне нужно?
6 ответов
Вы не первый, кто столкнулся с этой проблемой iconv
, Кто-то написал сценарий Perl для ее решения.
iconv
плохо обрабатывает большие файлы. Из исходного кода glibc, в iconv/iconv_prog.c
:
/* Since we have to deal with
arbitrary encodings we must read the whole text in a buffer and
process it in one step. */
Однако для вашего конкретного случая может быть лучше написать свой собственный валидатор UTF-8. Вы могли бы легко отогнать iconv -c -f utf8 -t utf8
вплоть до небольшой программы на C, с циклом, который вызывает iconv(3)
, Поскольку UTF-8 является немодальным и самосинхронизирующимся, вы можете обрабатывать его порциями.
#include <errno.h>
#include <iconv.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BUFSIZE 4096
/* Copy STDIN to STDOUT, omitting invalid UTF-8 sequences */
int main() {
char ib[BUFSIZE], ob[BUFSIZE], *ibp, *obp;
ssize_t bytes_read;
size_t iblen = 0, oblen;
unsigned long long total;
iconv_t cd;
if ((iconv_t)-1 == (cd = iconv_open("utf8", "utf8"))) {
perror("iconv_open");
return 2;
}
for (total = 0;
bytes_read = read(STDIN_FILENO, ib + iblen, sizeof(ib) - iblen);
total += bytes_read - iblen) {
if (-1 == bytes_read) { /* Handle read error */
perror("read");
return 1;
}
ibp = ib; iblen += bytes_read;
obp = ob; oblen = sizeof(ob);
if (-1 == iconv(cd, &ibp, &iblen, &obp, &oblen)) {
switch (errno) {
case EILSEQ: /* Invalid input multibyte sequence */
fprintf(stderr, "Invalid multibyte sequence at byte %llu\n",
1 + total + sizeof(ib) - iblen);
ibp++; iblen--; /* Skip the bad byte next time */
break;
case EINVAL: /* Incomplete input multibyte sequence */
break;
default:
perror("iconv");
return 2;
}
}
write(STDOUT_FILENO, ob, sizeof(ob) - oblen);
/* There are iblen bytes at the end of ib that follow an invalid UTF-8
sequence or are part of an incomplete UTF-8 sequence. Move them to
the beginning of ib. */
memmove(ib, ibp, iblen);
}
return iconv_close(cd);
}
Если вы решили написать его самостоятельно и говорите о текстовых файлах, вы можете использовать Perl с модулем Tie::File. Это позволяет вам работать с большими файлами в строке на месте. Это предназначено только для такого рода вещей.
Вы можете попробовать Tie::File::AnyData, если файл тоже не текстовый.
Попробуй это:
#! / Bin/ Баш FILE=/ вар / Журнал / сообщения CHUNKSIZE=100 ЛИНИЯ = 1 ВСЕГО =`wc -l $FILE | cut -d ' ' -f1` while [ $LINE -le $TOTAL ]; делать let ENDLINE=$LINE+$CHUNKSIZE sed "${LINE},${ENDLINE}p" $FILE | grep -i "знак" let LINE=$ENDLINE+1 сделанный
Я предлагаю использовать sed для извлечения только тех частей, которые вам нужны, и передать вывод в вашу команду:
sed -n '1,1000p' yourfile | yourcommand
направит первые 1000 строк к вашей команде
sed -n '1001,2000p' yourfile | yourcommand
будет передавать следующие 1000 строк.
и т.п.
Вы можете поместить это в цикл в скрипте, если хотите.
например
#!/bin/bash
size=1000
lines=`cat $1 | wc -l`
first=1
last=$size
while [ $last -lt $lines ] ; do
sed -n "${first},${last}p" $1 | yourcommand
first=`expr $last + 1`
last=`expr $last + $size`
done
last=$lines
sed -n "${first},${last}p" $1 | yourcommand
Ну, всем, кто предлагает написать собственное решение. Я могу. И я даже могу сделать это без многократного "сканирования" входного файла. Но проблема / вопрос: есть ли готовый инструмент?
Простейший подход на основе Perl может выглядеть так:
#!/usr/bin/perl -w
use strict;
my ( $lines, $command ) = @ARGV;
open my $out, '|-', $command;
my $i = 0;
while (<STDIN>) {
$i++;
if ($i > $lines) {
close $out;
open $out, '|-', $command;
$i = 1;
}
print $out $_;
}
close $out;
exit;
и теперь я могу:
=> seq 1 5
1
2
3
4
5
=> seq 1 5 | ./run_in_parts.pl 3 tac
3
2
1
5
4
Изменить: только что заметил, что вы не хотите разбивать файл заранее из-за дискового пространства, это, вероятно, не будет работать для вас
Использовать сплит:
$ man split
NAME
split - split a file into pieces
SYNOPSIS
split [OPTION] [INPUT [PREFIX]]
DESCRIPTION
Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default size is 1000 lines, and default PREFIX is `x'. With no INPUT, or when INPUT is -, read standard input.
Mandatory arguments to long options are mandatory for short options too.
-a, --suffix-length=N
use suffixes of length N (default 2)
-b, --bytes=SIZE
put SIZE bytes per output file
-C, --line-bytes=SIZE
put at most SIZE bytes of lines per output file
-d, --numeric-suffixes
use numeric suffixes instead of alphabetic
-l, --lines=NUMBER
put NUMBER lines per output file
--verbose
print a diagnostic to standard error just before each output file is opened
--help display this help and exit
--version
output version information and exit
SIZE may have a multiplier suffix: b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024, GB 1000*1000*1000, G 1024*1024*1024, and so on for T, P, E, Z, Y.