Я отправляю сжатый файл с помощью конвейера локально или из сетевого расположения. И на принимающей стороне я хотел бы определить тип сжатия и использовать соответствующую утилиту декомпрессии (gzip, bzip2, xz..etc) для его извлечения. Команды выглядят следующим образом:
Местный:
cat misteryCompressedFile | [compressionUtility] -d -fc > /opt/files/uncompressedfile
По сети:
ssh user@ipaddr "cat misteryCompressedFile" | [compressionUtility] -d -fc > /opt/files/uncompressedfile
Можно определить тип используемого сжатия, даже если расширение не указано (например, .gz или .bz2), посмотрев на первые несколько шестнадцатеричных значений файла. Например, если я использую xxd
для просмотра первых нескольких шестнадцатеричных значений двух сжатых файлов, то я буду 1f8b 0808
использовать gzip и 425a 6836
bzip2.
Однако, чтобы по-прежнему использовать конвейеризацию, как мне проверить первый входящий байт, чтобы выбрать правильную утилиту распаковки для первой части файла?
Таким образом, если неизвестный сжатый файл имеет тип gzip, команда будет следующей:
cat misteryCompressedFile | gzip -d -fc > /opt/files/uncompressedfile
а если неизвестный сжатый файл имеет тип bzip2, команда будет такой:
cat misteryCompressedFile | bzip2 -d -fc > /opt/files/uncompressedfile
Возможно ли принять такое решение с помощью конвейеризации на лету, не загружая весь файл и не принимая затем решения, что использовать для распаковки?
решение1
Да, это можно сделать в процессе разработки, не читая весь файл.
Этот первый фрагмент скрипта иллюстрирует механизм, с помощью которого мы перехватим и проверим заголовок и передадим его дальше. Обратите внимание, что мы выводим заголовок в stderr (>&2), но он продолжает появляться в выводе:
$ echo 0123456789ABCDEF |
(
HEADER=$(dd bs=1 count=4);
printf 'HEADER:%s\n' "$HEADER" >&2;
printf '%s\n' "$HEADER";
cat
)
4+0 records in
4+0 records out
4 bytes (4 B) copied, 8.4293e-05 s, 47.5 kB/s
HEADER:0123
0123456789ABCDEF
$
Ключевым моментом является использование dd
утилиты преобразования файлов с небольшим размером блока bs=1
.
Расширяя это, это рабочее решение. Мы будем использовать временный файл для хранения двоичного заголовка. Если он не видит один из двух 4-байтовых заголовков, то он ничего не делает:
#!/bin/sh
trap "rm -f /tmp/$$; exit 1" 1 2 3 15
# grab the 1st 4 bytes off the input stream,
# store them in a file, convert to ascii,
# and store in variable:
HEADER=$(
dd bs=1 count=4 2>/dev/null |
tee /tmp/$$ |
od -t x1 |
sed '
s/^00* //
s/ //g
q
'
)
case "$HEADER" in
1f8b0800)
UNCOMPRESS='gzip -d -fc'
;;
425a6839)
UNCOMPRESS='bzip2 -d -fc'
;;
*)
echo >&2 "$0: unknown stream type for header '$HEADER'"
exit 2
;;
esac
echo >&2 "$0: File header is '$HEADER' using '$UNCOMPRESS' on stream."
cat /tmp/$$ - | $UNCOMPRESS
rm /tmp/$$
решение2
Используйте file
эту информацию на отправляющем компьютере, чтобы решить, какую команду распаковки следует запустить на удаленном хосте.
например
#! /bin/sh
filetype=$(file misteryCompressedFile)
case "$filetype" in
*gzip*) CMD='gzip' ; ARGS='-d -fc' ;;
*bzip2*) CMD='bzip2' ; ARGS='-d -fc' ;;
*) echo "error: unknown compression type" ; exit 1 ;;
esac
cat misteryCompressedFile | ssh user@ipaddr "$CMD $ARGS > /opt/files/uncompressedfile"
В показанном примере команды ARGS gzip
и bzip2
одинаковы, но для других инструментов декомпрессии они могут отличаться.
Вот версия, которая распаковывает файл, полученный с удаленного хоста:
#! /bin/sh
# set up an anonymous fifo on fd 3 so we can pass the
# output of `file` to the second subshell without risking
# corruption of stdout/stdin
FIFO=$(mktemp -u)
mkfifo "$FIFO"
exec 3<>"$FIFO"
rm -f "$FIFO"
ssh user@ipaddr 'cat misteryCompressedFile' |
(
HEADER=$(dd bs=1 count=20 2> /dev/null |
od -A none -t o1 -w512 |
sed -e 's: :\\:g')
printf "$HEADER" | file --mimetype - | cut -d/ -f2 >&3
printf "$HEADER"
cat
) | (
read -u 3 -r filetype
case "$filetype" in
gzip) CMD='gzip' ; ARGS='-d -fc' ;;
x-bzip2) CMD='bzip2' ; ARGS='-d -fc' ;;
x-xz) CMD='unxz' ; ARGS='' ;;
x-lzma) CMD='lzcat' ; ARGS='' ;;
x-compress) CMD='uncompress' ; ARGS='' ;;
x-lrzip) CMD='lrzcat' ; ARGS='' ;;
*) echo "error: unknown compression type" >&2 ; exit 1 ;;
esac
$CMD $ARGS > /opt/files/uncompressedfile
)