Làm thế nào để giải nén một phần tệp văn bản thuần tuý nén?


19

Tôi có một tệp zip với kích thước 1,5 GB.

Nội dung của nó là một tệp văn bản lớn đơn giản (60 GB) và hiện tại tôi không còn đủ dung lượng trên đĩa để trích xuất tất cả và tôi cũng không muốn trích xuất tất cả, ngay cả khi tôi có.

Đối với trường hợp sử dụng của tôi, sẽ đủ nếu tôi có thể kiểm tra các phần của nội dung.

Do đó tôi muốn giải nén tệp dưới dạng luồng và truy cập vào một phạm vi của tệp (giống như có thể thông qua đầu và đuôi trên tệp văn bản thông thường).

Bằng bộ nhớ (ví dụ: trích xuất tối đa 100kb bắt đầu từ dấu 32 GB) hoặc theo dòng (cung cấp cho tôi các dòng văn bản đơn giản 3700-3900).

Có cách nào để đạt được điều đó?


1
Thật không may, không thể tìm kiếm trên một tệp riêng lẻ trong một zip. Vì vậy, bất kỳ giải pháp nào cũng sẽ liên quan đến việc đọc qua tệp cho đến thời điểm bạn quan tâm.
plugwash

5
@plugwash Theo tôi hiểu câu hỏi, mục tiêu không phải là tránh đọc qua tệp zip (hoặc thậm chí là tệp được giải nén), mà đơn giản là để tránh lưu trữ toàn bộ tệp được giải nén trong bộ nhớ hoặc trên đĩa. Về cơ bản, coi tập tin giải nén là một luồng .
ShreevatsaR

Câu trả lời:


28

Lưu ý rằng gzipcó thể trích xuất ziptệp (ít nhất là mục đầu tiên trong ziptệp). Vì vậy, nếu chỉ có một tệp lớn trong kho lưu trữ đó, bạn có thể thực hiện:

gunzip < file.zip | tail -n +3000 | head -n 20

Ví dụ, để trích xuất 20 dòng bắt đầu với dòng thứ 3000.

Hoặc là:

gunzip < file.zip | tail -c +3000 | head -c 20

Đối với điều tương tự với byte (giả sử một headtriển khai hỗ trợ -c).

Đối với bất kỳ thành viên tùy ý trong kho lưu trữ, theo cách Unixy:

bsdtar xOf file.zip file-to-extract | tail... | head...

Với phần headdựng sẵn của ksh93(như khi nào /opt/ast/binở phía trước $PATH), bạn cũng có thể làm:

.... | head     -s 2999      -c 20
.... | head --skip=2999 --bytes=20

Lưu ý rằng trong mọi trường hợp gzip/ bsdtar/ unzipsẽ luôn cần giải nén (và loại bỏ ở đây) toàn bộ phần của tệp dẫn đến phần bạn muốn giải nén. Đó là cách mà thuật toán nén hoạt động.


Nếu gzipcó thể xử lý nó, sẽ "z biết" khác tiện ích ( zcat, zless, vv) cũng làm việc?
ivanivan

@ivanivan, trên các hệ thống mà chúng dựa trên gzip(nói chung là đúng zless, không nhất thiết là zcattrên một số hệ thống vẫn chỉ đọc .Ztệp), vâng.
Stéphane Chazelas

14

Một giải pháp sử dụng giải nén -p và dd, ví dụ để trích xuất 10kb với 1000 blocs bù:

$ unzip -p my.zip | dd ibs=1024 count=10 skip=1000 > /tmp/out

Lưu ý: Tôi đã không thử điều này với dữ liệu thực sự lớn ...


Trong trường hợp chung có nhiều hơn một lần tệp trong một tệp lưu trữ, người ta có thể sử dụng unzip -l ARCHIVEđể liệt kê nội dung lưu trữ và unzip -p ARCHIVE PATHtrích xuất nội dung của một đối tượng PATHvào thiết bị xuất chuẩn.
David Foerster

3
Nói chung, việc sử dụng ddtrên các đường ống có số lượng hoặc bỏ qua là không đáng tin cậy vì nó sẽ làm được nhiều read()s đến tối đa 1024 byte. Vì vậy, nó chỉ được đảm bảo hoạt động chính xác nếu unzipghi vào đường ống trong các đoạn có kích thước là bội số của 1024.
Stéphane Chazelas

4

Nếu bạn có quyền kiểm soát việc tạo tệp zip lớn đó, tại sao không xem xét sử dụng kết hợp gzipzless?

Điều này sẽ cho phép bạn sử dụng zlessnhư một máy nhắn tin và xem nội dung của tệp mà không phải bận tâm đến việc trích xuất.

Nếu bạn không thể thay đổi định dạng nén thì điều này rõ ràng sẽ không hoạt động. Nếu vậy, tôi cảm thấy như zlesslà khá thuận tiện.


1
Tôi không. Tôi đang tải xuống tệp nén được cung cấp bởi một công ty bên ngoài.
k0pernikus

3

Để xem các dòng cụ thể của tệp, chuyển đầu ra sang trình soạn thảo luồng Unix, sed . Điều này có thể xử lý các luồng dữ liệu lớn tùy ý, do đó bạn thậm chí có thể sử dụng nó để thay đổi dữ liệu. Để xem các dòng 3700-3900 như bạn đã hỏi, hãy chạy như sau.

unzip -p file.zip | sed -n 3700,3900p

7
sed -n 3700,3900psẽ tiếp tục đọc cho đến khi kết thúc tập tin. Tốt hơn hết là sử dụng sed '3700,$!d;3900q'để tránh điều đó, hoặc thậm chí nói chung hiệu quả hơn:tail -n +3700 | head -n 201
Stéphane Chazelas

3

Tôi tự hỏi liệu có thể làm bất cứ điều gì hiệu quả hơn là giải nén từ khi bắt đầu tập tin cho đến khi. Có vẻ như câu trả lời là không. Tuy nhiên, trên một số CPU (Skylake) zcat | tailkhông tăng tốc CPU lên tới tốc độ xung nhịp hoàn toàn. Xem bên dưới. Một bộ giải mã tùy chỉnh có thể tránh được vấn đề đó và lưu các cuộc gọi hệ thống ghi đường ống, và có thể nhanh hơn ~ 10%. (Hoặc nhanh hơn ~ 60% trên Skylake nếu bạn không điều chỉnh cài đặt quản lý năng lượng).


Điều tốt nhất bạn có thể làm với một zlib tùy chỉnh với skipbyteschức năng là phân tích các ký hiệu trong một khối nén để đi đến cuối cùng mà không thực hiện công việc thực sự tái tạo lại khối giải nén. Điều này có thể nhanh hơn đáng kể (có thể ít nhất là gấp 2 lần) so với việc gọi hàm giải mã thông thường của zlib để ghi đè lên cùng bộ đệm và di chuyển về phía trước trong tệp. Nhưng tôi không biết có ai viết một chức năng như vậy không. (Và tôi nghĩ rằng điều này không thực sự hoạt động trừ khi tệp được viết đặc biệt để cho phép bộ giải mã khởi động lại ở một khối nhất định).

Tôi đã hy vọng có một cách để bỏ qua các khối Deflate mà không giải mã chúng, bởi vì điều đó sẽ nhanh hơn nhiều . Cây Huffman được gửi ở đầu mỗi khối, vì vậy bạn có thể giải mã từ đầu của bất kỳ khối nào (tôi nghĩ). Ồ, tôi nghĩ rằng trạng thái bộ giải mã nhiều hơn cây Huffman, nó cũng là 32kiB dữ liệu được giải mã trước đó và điều này không được đặt lại / quên theo ranh giới khối theo mặc định. Các byte giống nhau có thể tiếp tục được tham chiếu nhiều lần, do đó, chỉ có thể xuất hiện một lần theo nghĩa đen trong một tệp nén khổng lồ. (ví dụ: trong tệp nhật ký, tên máy chủ có thể vẫn "nóng" trong từ điển nén toàn bộ thời gian và mọi phiên bản của nó đều tham chiếu đến tệp trước đó, không phải tên đầu tiên).

Các zlibnhãn hiệu nói rằng bạn phải sử dụng Z_FULL_FLUSHkhi gọi deflatenếu bạn muốn dòng nén được seekable đến thời điểm đó. Nó "đặt lại trạng thái nén", vì vậy tôi nghĩ rằng không có điều đó, các tham chiếu ngược có thể đi vào (các) khối trước đó. Vì vậy, trừ khi tệp zip của bạn được viết với các khối hoàn toàn không thường xuyên (như mọi 1G hoặc thứ gì đó sẽ có tác động không đáng kể đến việc nén), tôi nghĩ bạn sẽ phải thực hiện nhiều công việc giải mã đến điểm bạn muốn hơn ban đầu Suy nghĩ. Tôi đoán có lẽ bạn không thể bắt đầu khi bắt đầu bất kỳ khối nào.


Phần còn lại của điều này đã được viết trong khi tôi nghĩ rằng có thể chỉ cần tìm phần bắt đầu của khối chứa byte đầu tiên bạn muốn và giải mã từ đó.

Nhưng thật không may, sự khởi đầu của một khối Deflate không cho biết thời gian của nó là bao lâu . Dữ liệu không nén được có thể được mã hóa bằng loại khối không nén có kích thước 16 bit tính theo byte ở phía trước, nhưng các khối được nén không: RFC 1951 mô tả định dạng khá dễ đọc . Các khối có mã hóa Huffman động có cây ở phía trước khối (vì vậy bộ giải nén không phải tìm trong luồng), vì vậy máy nén phải giữ toàn bộ khối (đã nén) trong bộ nhớ trước khi ghi.

Khoảng cách tham chiếu ngược tối đa chỉ là 32kiB, do đó, máy nén không cần giữ nhiều dữ liệu không nén trong bộ nhớ, nhưng điều đó không giới hạn kích thước khối. Các khối có thể dài nhiều megabyte. (Điều này đủ lớn để đĩa tìm kiếm giá trị ngay cả trên ổ đĩa từ, so với đọc tuần tự vào bộ nhớ và chỉ bỏ qua dữ liệu trong RAM, nếu có thể tìm thấy phần cuối của khối hiện tại mà không cần phân tích cú pháp qua nó).

zlib tạo các khối càng lâu càng tốt: Theo Marc Adler , zlib chỉ bắt đầu một khối mới khi bộ đệm biểu tượng lấp đầy, với cài đặt mặc định là 16.383 ký hiệu (bằng chữ hoặc khớp)


Tôi đã nén đầu ra của seq(rất dư thừa và do đó có thể không phải là một thử nghiệm tuyệt vời), nhưng pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -ctrên đó chỉ chạy với ~ 62 MiB / s dữ liệu nén trên Skylake i7-6700k ở tốc độ 3.9 GHz, với RAM DDR4-2666. Đó là dữ liệu được giải nén là 246MiB / s, là thay đổi lớn so với memcpytốc độ ~ 12 GiB / s đối với kích thước khối quá lớn để phù hợp với bộ đệm.

(Được energy_performance_preferenceđặt thành mặc định balance_powerthay vì balance_performance, bộ điều khiển CPU bên trong của Skylake quyết định chỉ chạy ở tốc độ 2,7 GHz, ~ 43 MiB / giây dữ liệu nén. Tôi sử dụng sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'để điều chỉnh nó. Có lẽ các cuộc gọi hệ thống thường xuyên như vậy không giống như giới hạn CPU thực làm việc cho đơn vị quản lý điện năng.)

TL: DR: zcat | tail -clà CPU bị ràng buộc ngay cả trên CPU nhanh, trừ khi bạn có đĩa rất chậm. gzip đã sử dụng 100% CPU mà nó chạy (và chạy 1,81 lệnh trên mỗi đồng hồ, theo perf) và tailsử dụng 0,162 CPU mà nó chạy trên (0,58 IPC). Hệ thống chủ yếu là không hoạt động.

Tôi đang sử dụng Linux 4.14.11-1-ARCH, có KPTI được bật theo mặc định để hoạt động xung quanh Meltdown, vì vậy tất cả các writecuộc gọi hệ thống đó gzipđều đắt hơn so với trước đây: /


Việc tìm kiếm tích hợp sẵn unziphoặc zcat(nhưng vẫn sử dụng zlibchức năng giải mã thông thường ) sẽ lưu tất cả các ghi đường ống đó và sẽ khiến CPU Skylake chạy ở tốc độ xung nhịp hoàn toàn. (Việc ép xung này đối với một số loại tải là duy nhất đối với Intel Skylake và sau này, đã giảm tải việc ra quyết định tần số CPU từ HĐH, vì chúng có nhiều dữ liệu hơn về những gì CPU đang làm và có thể tăng / giảm nhanh hơn. Đây là thông thường tốt, nhưng ở đây dẫn đến Skylake không tăng tốc tối đa với một thiết lập thống đốc bảo thủ hơn).

Không có cuộc gọi hệ thống, chỉ cần viết lại bộ đệm phù hợp với bộ đệm L2 cho đến khi bạn đạt đến vị trí byte bắt đầu mà bạn muốn, có thể sẽ tạo ra một vài% khác biệt ít nhất. Có thể thậm chí 10%, nhưng tôi chỉ chiếm số ở đây. Tôi chưa zlibtìm hiểu chi tiết nào để xem mức độ lớn của bộ nhớ cache và mức độ xả TLB (và do đó là xóa bộ nhớ cache) trên mỗi cuộc gọi hệ thống bị tổn thương khi bật KPTI.


Có một vài dự án phần mềm thêm chỉ mục tìm kiếm vào định dạng tệp gzip . Điều này không giúp ích gì cho bạn nếu bạn không thể khiến bất cứ ai tạo các tệp nén có thể tìm kiếm cho bạn, nhưng những người đọc khác trong tương lai có thể có lợi.

Có lẽ không ai trong số các dự án này có một chức năng giải mã mà biết làm thế nào để bỏ qua một dòng Deflate mà không có một chỉ số, bởi vì họ chỉ được thiết kế để làm việc khi một chỉ số có sẵn.


1

Bạn có thể mở tệp zip trong phiên python, bằng cách sử dụng zf = zipfile.ZipFile(filename, 'r', allowZip64=True)và sau khi mở, bạn có thể mở, để đọc, bất kỳ tệp nào trong kho lưu trữ zip và đọc các dòng, v.v., từ đó như một tệp bình thường.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.