Назад | Перейти на главную страницу

Как запустить команду для частей входного файла

У меня есть файл размером ~ 40 ГБ и команда фильтра, которая по какой-то причине ломается, когда я пытаюсь запустить ее в файле (даже при передаче через канал).

Но. Это не сработает, когда я разделю входной файл на множество небольших файлов, пропущу каждый из них через фильтр и объединю выходные данные.

Итак, я ищу способ сделать:

но без предварительного полного разделения файла (я не хочу использовать столько места на диске).

Я могу написать такую ​​программу сам, но, может быть, уже есть что-то, что мне нужно?

Если вы все же решите написать это сами и говорите о текстовых файлах, вы можете использовать Perl с Галстук :: Файл модуль. Это позволяет вам по очереди работать с большими файлами. Он предназначен как раз для такого рода вещей.

Вы могли бы попробовать Связь :: Файл :: AnyData если файл тоже не текстовый.

Я предлагаю использовать 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

Изменить: только что заметил, что вы не хотите заранее разбивать файл из-за дискового пространства, это, вероятно, не сработает для вас

Используйте разделение:

$ 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.

Вы не первый, кто столкнется с этой проблемой с 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);
}

Попробуй это:

#!/bin/bash

FILE=/var/log/messages
CHUNKSIZE=100

LINE=1
TOTAL=`wc -l $FILE | cut -d ' ' -f1`
while [ $LINE -le $TOTAL ]; do
  let ENDLINE=$LINE+$CHUNKSIZE
  sed "${LINE},${ENDLINE}p" $FILE | grep -i "mark"
  let LINE=$ENDLINE+1
done

Что ж - всем предлагающим написать свое решение. Я могу. И я даже могу обойтись без многократных "сканирований" входного файла. Но проблема / вопрос: есть ли готовый инструмент?

Простейший подход, основанный на 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