標準出力(ローカルまたはリモートの場所)からパイプされたときにヘッダーに圧縮ファイルを抽出しますか?

標準出力(ローカルまたはリモートの場所)からパイプされたときにヘッダーに圧縮ファイルを抽出しますか?

ローカルまたはネットワークの場所からパイプを介して圧縮ファイルを送信しています。受信側では圧縮タイプを検出し、適切な解凍ユーティリティ(gzip、bzip2、xzなど)を使用して解凍したいと思います。コマンドは次のとおりです。

ローカル:

cat misteryCompressedFile | [compressionUtility] -d -fc > /opt/files/uncompressedfile

インターネット経由:

ssh user@ipaddr "cat misteryCompressedFile" | [compressionUtility] -d -fc > /opt/files/uncompressedfile

拡張子が指定されていなくても(.gzや.bz2など)、ファイルの最初の16進数の値を見ると、使用されている圧縮の種類がわかります。たとえば、xxd2 つの圧縮ファイルの最初の 16 進値を調べるには、1f8b 0808gzip と425a 6836bzip2 を見てみましょう。

しかし、パイプを引き続き使用するには、最初のファイルの正しい解凍ユーティリティを選択するために最初の受信バイトを確認する必要がありますか?

したがって、不明な圧縮ファイルが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

拡張すると、これが可能な解決策です。バイナリヘッダーを保存するために一時ファイルを使用します。 2つの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"

示された例では、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
)

関連情報