Удалить первую строку из bigfile, используя bash - PullRequest
0 голосов
/ 10 марта 2020

У меня есть текстовый файл, и я хочу удалить первую строку (заголовок), чтобы прочитать файл без заголовка в конвейер. Это кажется тривиальным вопросом, на который много раз отвечали, но из-за размера файлов, с которыми я сталкивался, решения, которые я нашел до сих пор, не работали. Для моих тестовых прогонов я использовал echo "$(tail -n +2 "$FILE_NAME")" > "$FILE_NAME", но выполнение этого с моим большим файлом приводит к следующей ошибке: bash: xrealloc: cannot allocate 18446744071562067968 bytes (1679360 bytes allocated) Есть ли какой-либо метод, который редактирует файл на месте? Загрузка их в память не работает, некоторые из моих файлов имеют размер до 400 Гб. Спасибо за помощь!

Ответы [ 4 ]

0 голосов
/ 10 марта 2020

Я просто собираюсь затронуть часть вопроса «отредактировать файл на месте», хотя, похоже, это не совсем то, что вы искали. Вы найдете множество решений, описывающих функции, которые, как утверждают, выполняют редактирование на месте, но обычно эти решения вообще не редактируют файл. Вместо этого они записывают во временный файл, а затем перезаписывают оригинал временным файлом. (например, sed --in-place является распространенным решением, которое записывает во временный файл). Редактирование файла на месте - это то, что вы практически никогда не хотите делать, поскольку изменение файла опасно. Действительно, если вы считаете, что хотите отредактировать файл на месте, серьезно подумайте и предположите, что вы не правы. Тем не менее, если по какой-то причине вам действительно нужно это сделать, возможно, безопаснее всего это сделать:

#include <err.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>

FILE * xfopen(const char *path, const char *mode);
int is_regular(int, const char *);

int
main(int argc, char **argv)
{
        const char *rpath = argc > 1 ? argv[1] : "stdin";
        const char *wpath = argc > 1 ? argv[1] : "stdout";
        FILE *fr = argc > 1 ? xfopen(rpath, "r") : stdin;
        FILE *fw = argc > 1 ? xfopen(wpath, "r+") : stdout;
        char buf[BUFSIZ];
        int c;
        size_t rc;
        off_t length = 0;

        /* Discard the first line */
        while( (c = getc(fr)) != EOF && c != '\n' ) {
                ;
        }
        if( c != EOF) while( (rc = fread(buf, 1, BUFSIZ, fr)) > 0) {
                size_t wc;
                wc = fwrite(buf, 1, rc, fw);
                length += wc;
                if( wc!= rc) {
                        break;
                }
        }
        if( fclose(fr) ) {
                err(EXIT_FAILURE, "%s", rpath);
        }
        if( is_regular(fileno(fw), wpath) && ftruncate(fileno(fw), length)) {
                err(EXIT_FAILURE, "%s", wpath);
        }
        if( fclose(fw)) {
                err(EXIT_FAILURE, "%s", wpath);
        }
        return EXIT_SUCCESS;
}

FILE *
xfopen(const char *path, const char *mode)
{
        FILE *fp = fopen(path, mode);
        if( fp == NULL ) {
                perror(path);
                exit(EXIT_FAILURE);
        }
        return fp;
}

int
is_regular(int fd, const char *name)
{
        struct stat s;
        if( fstat(fd, &s) == -1 ) {
                perror(name);
                exit(EXIT_FAILURE);
        }
        return !!(s.st_mode & S_IFREG);
}

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

0 голосов
/ 10 марта 2020

Хвост достаточно эффективен для этой операции.

Проблема в том, что вы хотите перезаписать исходный файл.

Использование bash "$()" для отсрочки создания выходного файла означает, что bash должен хранить содержимое в памяти, отсюда и сообщение об ошибке. Для больших файлов лучше записать вывод во временный файл, а затем использовать mv, чтобы переместить его поверх оригинала.

Когда sed используется в режиме перезаписи, он делает именно это (для чего-либо более чем несколько строк).

0 голосов
/ 10 марта 2020
sed -i 1d "$FILE_NAME"

Он запускается sed со скриптом verysimple 1d, который выбирает первую строку (селектор 1) и удаляет ее (команда d). Благодаря опции на месте -i ваш файл будет перезаписан без использования промежуточного файла.

Даже если вы не беспокоитесь о промежуточном файле, sed использует свой собственный промежуточный файл для внутреннего использования. Во время этой операции использование вашего диска будет в два раза больше размера файла.

0 голосов
/ 10 марта 2020

Вы можете использовать код, подобный следующему:

awk 'NR!=1 {print}' input_file >output file

Это отправит в выходной файл все, кроме первой строки. Вы можете использовать эту конструкцию для выполнения ваших операций:

awk 'NR!=1 {print}' input_file|operation1|operation2...

Изменение команды таким образом может сделать работу:

tail -n +2 "$FILE_NAME" > "${FILE_NAME}.new"

Для этого потребуется двойное дисковое пространство

...