7

У меня большой текстовый файл gzip. Я хотел бы что-то вроде:

zcat BIGFILE.GZ | \
    awk (snag 10,000 lines and redirect to...) | \
    gzip -9 smallerPartFile.gz

часть awk, я хочу, чтобы она взяла 10000 строк и отправила их в gzip, а затем повторяла до тех пор, пока все строки в исходном входном файле не будут использованы. Я нашел скрипт, который утверждает, что делает это, но когда я запускаю его на своих файлах, а затем сравниваю оригинал с теми, которые были разделены, а затем объединены, строки отсутствуют. Итак, что-то не так с частью awk, и я не уверен, какая часть сломана.

Цель:

  • Прочитайте исходный файл один раз за всю операцию
  • Разделите источник на более мелкие части, разделенные символом новой строки. Скажем, 10000 строк на файл
  • Сожмите целевые файлы, созданные в результате действия разделения, и сделайте это без дополнительного шага после обработки этого сценария.

Вот код Может кто-нибудь сказать мне, почему это не приводит к файлу, который может быть разделен и объединен и затем успешно преобразован в оригинал?

# Generate files part0.dat.gz, part1.dat.gz, etc.
# restore with: zcat foo* | gzip -9 > restoredFoo.sql.gz (or something like that)
prefix="foo"
count=0
suffix=".sql"

lines=10000 # Split every 10000 line.

zcat /home/foo/foo.sql.gz |
while true; do
  partname=${prefix}${count}${suffix}

  # Use awk to read the required number of lines from the input stream.
  awk -v lines=${lines} 'NR <= lines {print} NR == lines {exit}' >${partname}

  if [[ -s ${partname} ]]; then
    # Compress this part file.
    gzip -9 ${partname}
    (( ++count ))
  else
    # Last file generated is empty, delete it.
    rm -f ${partname}
    break
  fi
done

5 ответов5

5

Я бы предложил вести всю домашнюю работу внутри awk , здесь это работает с GNU awk:

BEGIN { file = "1" }

{ print | "gzip -9 > " file ".gz" }

NR % 10000 == 0 {
  close("gzip -9 > " file ".gz")
  file = file + 1
}

Это сохранит 10000 строк в 1.gz , следующие 10000 в 2.gz и т.д. Используйте sprintf если вы хотите больше гибкости при генерации имени файла.

Обновлено с тестом

Используемые тестовые данные - простые числа до 300 КБ, найденные здесь.

wc -lc primes; md5sum primes

Выход:

25997 196958 primes
547d527ec50c2799fa6ce96dba3c26c0  primes

Теперь, если приведенная выше программа awk была сохранена в split.awk и запущена следующим образом (с GNU awk):

awk -f split.awk primes

Создаются три файла (1.gz, 2.gz и 3.gz). Тестирование этих файлов:

for f in {1..3}; do gzip -dc $f.gz >> foo; done

Тестовое задание:

diff source.file foo

Вывод должен быть ничего, если файлы одинаковы.

И те же тесты, что и выше:

gzip -dc [1-3].gz | tee >(wc -lc) >(md5sum) > /dev/null

Выход:

25997  196958
547d527ec50c2799fa6ce96dba3c26c0  -

Это показывает, что содержимое одинаково, а файлы разделены, как и ожидалось.

3

Краткий ответ: awk читает входные данные (в данном случае канал из zcat ) блок за раз (где блок составляет 512 байт или их кратно, в зависимости от вашей ОС).  Таким образом, к тому времени, когда он имеет 10000-й символ новой строки (маркер конца строки) в памяти, он также имеет 10001-ю строку, 10002-ю, и, вполне вероятно, больше (или, возможно, меньше) в памяти.  Это проблема, потому что это означает, что эти символы были прочитаны из канала и больше не доступны для следующей итерации awk для чтения.

3

Краткий (и более полезный) ответ: вы смотрели на команду Unix split ?

3

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

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

#!/bin/bash
GZIP_FILE=$1
SPLIT_LINES=$2
TOTAL_LINES=`zcat $GZIP_FILE|wc -l`
START=0
NEXT_START=0
while [ $NEXT_START -lt $TOTAL_LINES ]; do
        NEXT_START=$(( $NEXT_START + $SPLIT_LINES ))
        echo .
        zcat $GZIP_FILE|sed -n ${START},${NEXT_START}p |gzip -9 > ${GZIP_FILE}.lines-${START}-${NEXT_START}.gz
        START=$NEXT_START
done

Это создаст в том же каталоге для каждой части файл с именем gzip-файла и добавлением ".lines- $ startline- $ endline.gz"

Надеюсь, вы нормально тратите процессор :)

1

У вас есть альтернатива awk. Вот как вы можете сделать это с помощью GNU split или GNU параллельно.

В GNU split есть опция --filter и кое-что очень близкое к тому, что вы пытаетесь сделать, описано в руководстве:

`--filter=COMMAND'
     With this option, rather than simply writing to each output file,
     write through a pipe to the specified shell COMMAND for each
     output file.  COMMAND should use the $FILE environment variable,
     which is set to a different output file name for each invocation
     of the command.  For example, imagine that you have a 1TiB
     compressed file that, if uncompressed, would be too large to
     reside on disk, yet you must split it into individually-compressed
     pieces of a more manageable size.  To do that, you might run this
     command:

          xz -dc BIG.xz | split -b200G --filter='xz > $FILE.xz' - big-

     Assuming a 10:1 compression ratio, that would create about fifty
     20GiB files with names `big-xaa.xz', `big-xab.xz', `big-xac.xz',
     etc.

Так что в вашем случае вы могли бы сделать:

zcat bigfile.gz | split -l 10000 --filter='gzip -9 > $FILE.gz' - big-

Хорошей альтернативой split будет использование параллельной GNU, это позволит вам распараллелить сжатие:

zcat bigfile.gz | parallel --pipe -N 10000 'gzip > {#}.gz'

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .