Вопрос или проблема
У меня есть архив tar, который содержит много zip-файлов, каждый из которых содержит xml-файлы. Я хотел бы распаковать tar, разархивировать zip и затем сделать что-то с текстом в xml-файлах. Вся эта программа пишется как пайплайн bash.
Мне нужна команда unzip, чтобы вернуть:
- несжатое содержимое файлов внутри zip
- разделитель файлов (чтобы знать, где заканчивается содержимое одного файла и начинается следующее)
- название каждого файла внутри zip
Следующая команда в пайплайне нуждается в этих 3 вещах, чтобы корректно выполнить свою работу. Содержимое файлов и разделитель должны быть в канале (stdout), названия файлов могут быть как в том же канале, так и в переменных или чем-то еще.
Вопрос: Какую реализацию unzip мне использовать и как мне это сделать?
bsdtar работает (но, насколько я знаю, не может вернуть названия файлов):
tar -xf ~/tar/0.tar --to-command 'bsdtar -xO --include="*.html" --include="*.xhtml" | iconv -f UTF-8 -t UTF-8//IGNORE | htmlq -tw'
P.S. Я выполняю распаковку tar, разархивацию и все остальное внутри пайплайна bash, а не на диске, потому что запись на диск замедляет программу в 30 раз. Каждый zip содержит много мелких файлов, и попытка найти их позже затрудняет работу из-за узкого места в операциях ввода-вывода.
P.P.S. Я понимаю, что разархивация требует чтения до конца сначала, так что теоретически пайпы не должны сильно помочь. На практике это не замедляет программу значительно (я предполагаю, что весь zip-файл хранится в RAM, что нормально).
Это должен быть комментарий, но он будет слишком длинным. Это может дать некоторое руководство.
gzip
довольно умен. Он не может предполагать, сколько памяти доступно (поэтому он не читает весь файл в память) и не знает, насколько сильно он сжал данные, пока эта фаза не завершена. (Он, конечно, использует обычное кэширование.)
При сжатии, я полагаю, он накапливает список файлов в архиве (и статистику для каждого) и добавляет их после всех данных. Затем он ловко записывает эпилог, который содержит адрес поиска начала этого списка файлов. Статистика файлов также может содержать смещение в zip-файле для начала каждого подпункта, что тоже оптимизировало бы частичные извлечения.
Таким образом, unzip с опцией -l
или -v
может перейти в конец, вернуться на размер(epilogue) и сообщить содержимое файла, не читая ничего другого. Вы можете проверить мое предположение, запустив небольшой тестовый файл под strace
.
Вы можете найти достаточно быстрым получить список названий файлов с помощью unzip -v
, разобрать его с помощью awk
и извлечь каждый файл отдельно, возможно, предварительно добавив детали файла. Это также было бы интересным упражнением под strace
, что показало бы стратегию поиска/чтения.
Вы можете обнаружить, что требуется записывать файлы на диск, но это можно оптимизировать с помощью ram-диска, так как отдельные файлы маленькие. Также, семейство zip может удалять некоторые файлы после их обработки — протестируйте в какой-то директории для разработки перед попыткой в производственной среде.
Если это производственная задача, я бы, вероятно, отключил извлечение и загрузку. Извлеките файлы в подкаталог pend
, переместите завершенные файлы в подкаталог live
, а загрузчик (возможно, параллельно) перемещает загруженные файлы в директорию done
.
Ответ или решение
Разархивация ZIP-файлов в стандартный вывод с именами файлов и разделителями
В современных системах Linux часто возникает необходимость обрабатывать архивы с помощью инструментов командной строки, таких как tar
, unzip
, и других. Если у вас есть архив tar
, содержащий ZIP-файлы с файлами XML, и вам нужно их извлечь, не записывая на диск, а обрабатывая в конвейере, это можно сделать следующим образом.
Шаги реализации
-
Использование
tar
для извлечения ZIP-файлов:tar -xf ~/tar/0.tar --to-command 'unzip -p'
Здесь
-p
позволяет извлекать содержимое файлов в стандартный вывод. -
Добавление разделителей и имен файлов:
Проблема в том, чтоunzip -p
не предоставляет имена файлов в стандартный вывод. Чтобы обойти это, вы можете использовать командуunzip -l
для получения списка файлов, а затем в цикле обрабатывать каждый файл. Давайте рассмотрим, как это можно реализовать в Bash:# Получаем список файлов внутри zip-архива и обрабатываем каждый из них for zip_file in $(tar -tf ~/tar/0.tar | grep '\.zip$'); do echo "Извлечение из файла: $zip_file" unzip -p "$zip_file" | { # Обработка содержимого/unzip и добавление разделителей while IFS= read -r line; do echo "$line" done echo "---- Конец файла: $zip_file ----" # Разделитель } done
Улучшение производительности
Запись на диск замедляет процесс, поэтому следует использовать следующий подход:
- Использование временных файлов: Временные файлы можно создать в RAM-диске, что минимизирует задержку при записях.
- Параллельность: Если ваша система поддерживает многопоточность, рассмотрите возможность параллельного извлечения файлов, особенно если их много.
Пример полного кода в Bash:
#!/bin/bash
tar -xf ~/tar/0.tar --to-command 'unzip -p' | while IFS= read -r zip_file; do
echo "Обработка файла: $zip_file"
unzip -p "$zip_file" | {
while IFS= read -r line; do
echo "$line"
done
echo "---- Конец файла: $zip_file ----"
}
done
Заключение
Таким образом, используя комбинацию tar
, unzip
, и bash-скрипта, вы можете эффективно обрабатывать ZIP-файлы из архива tar
, извлекая содержимое XML-файлов, добавляя разделители и в то же время минимизируя задержки, связанные с записью на диск. Этот подход позволяет сохранить высокую производительность обработки данных, что критично для вашего сценария.