web-dev-qa-db-ja.com

最初のファイルを除くすべてのヘッダー行をスキップして、複数のzipファイルを連結します

1つのファイルに結合したいgzip圧縮されたファイルのコレクションがあります。それらはそれぞれ同じ形式です。最初のファイルのヘッダー情報のみを保持し、後続のファイルではスキップしたい。

簡単な例として、次の内容の4つの同一のファイルがあります。

$ gzcat file1.gz
# header
1
2

で終わりたい

# header
1
2
1
2
1
2
1
2

実際には、さまざまな数のファイルを持つことができるので、これをプログラムで実行できるようにしたいと思います。これが私がこれまでに持っている非プログラム的な解決策です...

cat <(gzcat file1.gz) <(tail -q -n +2 <(gzcat file2.gz) <(gzcat file3.gz) <(gzcat file4.gz))

このコマンドは機能しますが、4つのファイルを処理するように「ハードコード」されているため、任意の数のファイルに対して一般化する必要があります。それが助けになるなら、私はシェルとしてbashを使用しています。私の好みはパフォーマンスです(実際にはファイルは数百万行の長さになる可能性があります)ので、高速であればエレガントではないソリューションで問題ありません。

3
SethMMorton

質問に表示するコマンドが基本的に機能する場合(ハードコードされた数のファイルに対して)、

first=1
for f in file*.gz
do
    if [ "$first" ]
    then
        gzcat "$f"
        first=
    else
        gzcat "$f"| tail -n +2
    fi
done > collection_single_file

あなたのために働くはずです。論理がかなり明確であることを願っています。すべてのファイルを確認します(ファイル名に応じてワイルドカードを変更します)。リストの最初のファイルの場合はgzcatなので、ファイル全体(ヘッダーを含む)を取得できます。それ以外の場合は、tailを使用してヘッダーを削除します。ファイルを処理した後は、他のファイルが最初になることはありません。

これにより、(回答のように)1回だけではなく、tail[〜#〜] n [〜#〜]-1回呼び出されます。それを除けば、私の答えはあなたの答えと同じように機能するはずです。

G-Manのソリューション のバリエーションで、最初のファイルを追跡するために個別の変数を使用しません。

set -- file*.gz

{
    gzcat "$1"; shift

    for file do
        gzcat "$file" | sed '1d'
    done
} >combined.txt

これにより、最初のファイルが解凍され、残りのファイルがループされ、それぞれが最初の行を削除する短いsedスクリプトを通過します。出力はcombined.txtにリダイレクトされます。

set -- file*.gzコマンドは、位置パラメーター($1$2など、集合的に配列$@)を、指定されたパターンに一致するファイル名に設定します。 shiftは、配列を解凍した後、配列から$1を削除します。ループは配列内の残りのファイル名をループし、記述されている可能性もあります

for file in "$@"; do
    gzcat "$file" | sed '1d'
done

{ ... }を使用すると、コマンドの出力を一度にファイルにリダイレクトできます。


さらに短く、「ヘッダー行」は常に#文字で始まり(質問の例のように)、データにはそのような行が他にないという追加の仮定があります。

gzcat file*.gz | awk 'NR > 1 && /^#/ { next } 1' >combined.txt

または、

gzcat file*.gz | sed '2,${ /^#/d; }' >combined.txt

これらは両方とも、圧縮されていないデータの結合されたコンテンツの2行目以降にある場合、#で始まる行をスキップします。

1
Kusalananda