어느 날 원격 서버에서 일부 로그를 수집하고 있었는데, tarball에 디렉터리를 추가하는 대신 아무 생각 없이 파일을 단일 파일로 압축했습니다. 일부 로그 파일을 수동으로 분리할 수 있지만 일부는 이미 압축되어 있습니다. 따라서 원본 파일은 다음과 같습니다.
ex_access.log
ex_access.log.1.gz
ex_access.log.2.gz
ex_debug.log
ex_debug.log.1.gz
ex_debug.log.2.gz
ex_update.log
ex_update.log.1.gz
ex_update.log.2.gz
그리고 예상대로 exlogs.gz로 압축됩니다. 이는 압축 해제 후 모든 원본 파일을 포함하는 하나의 파일입니다. 바이너리를 인쇄하는 대신 정상적으로 압축을 풀 수 있도록 원본 gz 파일을 분리하는 방법이 있습니까?
^_<8B>^H^H<9B>C<E8>a^@
^Cex_access.log.1^@<C4><FD><U+076E>-Kr<9D> <DE><F7>S<9C>^W<E8><CE><F0><FF><88>y[<D5><EA>+<A1>^EHuU<A8>^K<B6><94><AA>L4E^R̤^Z^B<EA><E1><DB>}<AE>̳<B6><D6>I<C6><F8><9C><DB><C6>
<F1>@G`<E6><D6><FE><E0>3<C2><C3>ٰ̆|<E4><FC><BB>#<FD><EE><B8>~9<EA>+<A7>W+<FF><FB><FF><F6><9F><FE><97><FF><E3><97><FF><FD>^Z<E3><FF><F8><E5><FF><FE><CB><C7><FF>Iy<FC>?<8E><F9>?<F3>?<EF><B5><F7><F9><BF><FF>ß<FF>
[etc]
예, 로그를 다시 수집할 수 있지만(원래 로그를 그대로 유지한다는 느낌이 있기 때문에) 서버에 액세스하도록 승인을 받는 것은 고통스럽고 가능하면 피하고 싶습니다.
편집: 내가 사용한 명령은
gzip -c ex_* > exlogs.gz
답변1
파일을 단일 파일로 gzip하면 gzip
마치 파일을 먼저 개별적으로 압축한 다음 연결한 것처럼 여러 gzip 스트림을 포함하는 파일이 생성됩니다.
이 행동은매뉴얼 페이지.
-c --stdout --to-stdout
출력을 표준 출력에 기록합니다. 원본 파일은 변경되지 않습니다. 입력 파일이 여러 개인 경우 출력은 독립적으로 압축된 멤버의 시퀀스로 구성됩니다.
이는 각 소스 파일에 별도의 gzip 헤더(원본 파일 이름 포함)가 있음을 의미합니다. 따라서 원칙적으로 압축을 풀면 분리될 수 있습니다.
불행히도 gzip
개발자는 이를 지원하지 않기로 결정했습니다 gunzip
.
나중에 독립적으로 추출할 수 있도록 여러 멤버가 포함된 단일 아카이브 파일을 생성하려면 tar 또는 zip과 같은 아카이브 프로그램을 사용하십시오. [...] gzip은 tar를 대체하는 것이 아니라 보완하도록 설계되었습니다.
gzip 헤더나 바닥글에는 압축된 데이터 스트림의 길이가 포함되어 있지 않기 때문에 파일을 연결 해제하는 것은 쉽지 않습니다. 이는 두 번째 스트림의 시작 부분을 안정적으로 찾으려면 전체 스트림을 압축 해제하는 데 필요한 것의 절반인 전체 수축 스트림을 디코딩해야 함을 의미합니다.
내가 아는 한, 데이터 스트림을 탐색하여 그것이 끝나는 위치를 알아낼 수 있는 도구는 없습니다.이 분야의 일부 연구에서는 gzip으로 압축된 파일의 내용에 대한 준 무작위 액세스를 지원합니다..
IO::Uncompress::Gunzip
다행스럽게도 Stéphane Chazelas가 언급한 Perl과 같은 일부 프로그래밍 라이브러리를 사용하여 gzip 스트림의 압축을 독립적으로 풀 수 있습니다.그의 대답, 또는 녹슨flate2
.
마지막으로 해결책으로 이 도구를 작성했습니다.총 지퍼 분할. 각 파일의 압축을 개별적으로 풀고 파일 연결을 해제할 수도 있습니다. 후자의 경우 각 파일의 압축을 풀고 gzip 스트림이 시작되는 오프셋을 기록한 다음 결과를 삭제합니다. 이는 추가로 최적화될 수 있지만 기가바이트 크기의 파일에서도 매우 빠르게 작동합니다.
$ ./gunzip-split --help
gunzip-split 0.1.1
Uncompress concatenated gzip files back into separate files.
USAGE:
gunzip-split [OPTIONS] <FILE>
ARGS:
<FILE> concatenated gzip input file
OPTIONS:
-d, --decompress Decompressing all files (default)
-f, --force Overwrite existing files
-h, --help Print help information
-l, --list-only List all contained files instead of decompressing
-o, --output-directory <DIRECTORY> Output directory for deconcatenated files
-s, --split-only Split into multiple .gz files instead of decompressing
-V, --version Print version information
$ ./gunzip-split -s -o ./out/ combined.gz
file_1: OK.
file_2: OK.
$ ls ./out
file_1.gz file_2.gz
답변2
공교롭게도 in은 각 파일마다 하나씩 두 개의 독립적인 압축 스트림을 생성 gzip -c file1 file2 > result
하고 gzip
파일의 파일 이름과 수정 시간까지 저장합니다.
압축을 풀 때 해당 정보를 사용할 수는 없지만 perl
의 IO::Uncompress::Gunzip
모듈을 사용하여 이를 수행할 수 있습니다. 예를 들어:
#! /usr/bin/perl
use IO::Uncompress::Gunzip;
$z = IO::Uncompress::Gunzip->new("-");
do {
$h = $z->getHeaderInfo() or die "can't get headerinfo";
open $out, ">", $h->{Name} or die "can't open $h->{Name} for writing";
print $out $buf while $z->read($buf) > 0;
close $out;
utime(undef, $h->{Time}, $h->{Name}) or warn "can't update $h->{Name}'s mtime";
} while $z->nextStream;
that-script < exlogs.gz
스크립트를 다음과 같이 호출하면 원래 이름과 수정 시간(저장되지 않은 하위 초 부분 제외)으로 현재 작업 디렉터리의 파일을 복원합니다 .gzip
답변3
이는 다소 복잡하지만 다음 요구 사항이 충족될 때 작동합니다.
- 이는
merged.gz
일반 ASCII 데이터와 gzip 압축 파일이 혼합된 것입니다. - 이런 작전에서 나오네요
cat log0 log1.gz log2.gz log3 log4.gz > merged.gz
- 일반 텍스트 ASCII 파일의 줄은 인쇄 가능한 문자에서만 나옵니다.
- gzip 압축 파일의 매직 바이트가 그대로 유지됨(16진수
1F 8B
)
대부분의 프로그램은 작동해야 하며 임시 파일을 수동으로 작성하면 이를 방지 sponge
할 수 있습니다.moreutils
당신은 무엇을 했나요:
- 각 연속 블록에 대해 파일에 인쇄 가능한 전용 문자가 있는 줄을 넣습니다. 두 개의 일반 ASCII 파일을 연속적으로 병합하면 파일이 분리되지 않으며(이 경우 로그의 타임스탬프가 파일을 분리하는 데 사용됨) 원본 파일 이름이 손실됩니다.
gz_only.gz
중간 파일 에 추가 줄 넣기- 매직 바이트를 사용하여 파일 구분
마지막으로 사용하면 csplit
개행 문자도 있는 경우에만 분할할 수 있습니다. 따라서 이는 분할 전에 도입되고 분할 후에 제거됩니다. 현재 병합된 시스템에는 gzip 압축 파일이 1000개 이하인 것으로 가정됩니다.
#!/bin/bash
#lines with printable characters go to separate files for each consecutive block
awk '{ if ($0 ~ /^[[:print:]]+$/) { print > "file_"i+0}
else {if (oldi==i) {i++}}}' merged.gz
#get lines with non-printables to other merged file
grep -av '^[[:print:]]$' merged.gz > gz_only.gz
#split into files and remember their count
#sed introduces newline before magic bytes
#csplit splits on occurrence of magic bytes and returns info on file lengths
nfiles=$( sed "s/$(printf '\x1f\x8b')/\n&/g" gz_only.gz |
csplit - -z "/$(printf '\x1f\x8b')/" '{*}' -b'%03d.gz' |
wc -l )
#first file is empty, due to introduced newline
rm -fv xx000.gz
#for all other remove newline
#note: the above grep introduced a newline to the last file
#if splitting is done for a file only concatenated from
#gz-files (no previous grep), the last file would have to
#be excluded from this operation.
for (( i=1 ; i<nfiles ; i++ )) ; do
name=xx$(printf '%03d.gz' $i)
head -c -1 $name | sponge $name
done
#retrieve original file name
for f in xx*gz ; do
#this is ready for simple filenames like the suggested logs,
#e.g. no " as file name character
mv $f "$(file $f | awk -F'"' '{print $2}').gz"
done
#unzip files
find -name '*gz' ! -name gz_only.gz ! -name merged.gz -exec gunzip {} +
나는 ASCII와 비ASCII 분리 및 분할을 사용하는 것이 더 우아할 것이라고 생각 perl
하지만 익숙하지 않습니다.