web-dev-qa-db-ja.com

tar.gzからgrepを抽出せずに[速い方]

ダースのファイル.tar.gzからパターンをgrepしようとしていますが、非常に遅いです

使用しています

tar -ztf file.tar.gz | while read FILENAME
do
        if tar -zxf file.tar.gz "$FILENAME" -O | grep "string" > /dev/null
        then
                echo "$FILENAME contains string"
        fi
done
47
Pixel

zgrepがある場合は、使用できます

zgrep -a string file.tar.gz
104
lanes

--to-commandオプションを使用して、ファイルを任意のスクリプトにパイプできます。これを使用すると、単一のパスで(および一時ファイルなしで)アーカイブを処理できます。 この質問 、および 手動 も参照してください。上記の情報を準備して、次のようなものを試すことができます。

$ tar xf file.tar.gz --to-command "awk '/bar/ { print ENVIRON[\"TAR_FILENAME\"]; exit }'"
bfe2/.bferc
bfe2/CHANGELOG
bfe2/README.bferc
30
Jester

この質問は4年前のものですが、いくつかのオプションがあります。

オプション1:tar --to-command grepの使用

次の行は、PATTERNexample.tgzを探します。これは@Jesterの例に似ていますが、パターンマッチングを機能させることができませんでした。

tar xzf example.tgz --to-command 'grep --label="$TAR_FILENAME" -H PATTERN ; true'

オプション2:tar -tzfの使用

2番目のオプションは、tar -tzfを使用してファイルをリストし、grepを使用してファイルを検索することです。関数を作成して繰り返し使用することができます:

targrep () {
    for i in $(tar -tzf "$1"); do
        results=$(tar -Oxzf "$1" "$i" | grep --label="$i" -H "$2")
        echo "$results"
    done
}

使用法:

targrep example.tar.gz "pattern"
5
Katie

これが本当に遅い場合は、大きなアーカイブファイルを扱っていると思われます。 grepの場合、ファイルリストを抽出するために1回解凍し、N回(Nはアーカイブ内のファイル数)解凍します。すべての圧縮解除に加えて、各ファイルを抽出するために、毎回かなりの数のアーカイブをスキャンする必要があります。 tarの最大の欠点の1つは、最初に目次がないことです。アーカイブ内のすべてのファイルに関する情報を取得し、ファイルのその部分のみを読み取る効率的な方法はありません。基本的に、毎回抽出するものまですべてのファイルを読み取る必要があります。ファイル名の場所にすぐにジャンプすることはできません。

これを高速化する最も簡単な方法は、最初にファイルを解凍することです(gunzip file.tar.gz)そして、.tarファイル。それだけで十分に役立つかもしれません。ただし、アーカイブ全体をN回ループします。

これを本当に効率的にしたい場合、唯一のオプションは、アーカイブを処理する前にアーカイブ内のすべてを完全に抽出することです。あなたの問題は速度なので、これは最初に抽出したくない巨大なファイルであると思いますが、可能であれば、これは物事を大幅にスピードアップします:

tar zxf file.tar.gz
for f in hopefullySomeSubdir/*; do
  grep -l "string" $f
done

ご了承ください grep -lは、一致するファイルの名前を出力し、最初の一致後に終了し、一致するものがない場合はサイレント状態になります。それだけでコマンドのgrep部分が高速化されるため、アーカイブ全体を抽出するスペースがなくても、grep -l 役立ちます。ファイルが巨大な場合、それは大いに役立ちます。

4
Jim Stewart

まず、複数のプロセスを開始できます。

tar -ztf file.tar.gz | while read FILENAME
do
        (if tar -zxf file.tar.gz "$FILENAME" -O | grep -l "string"
        then
                echo "$FILENAME contains string"
        fi) &
done

( ... ) &は、新しい分離(読み取り:親シェルは子を待機しません)プロセスを作成します。

その後、アーカイブの抽出を最適化する必要があります。 OSは既にファイルアクセスをキャッシュしているはずなので、読み取りは問題ありません。ただし、tarはループが実行されるたびにアーカイブを解凍する必要があるため、時間がかかる場合があります。アーカイブを一度解凍し、結果を反復処理すると、ここで役立つ場合があります。

local tempPath=`tempfile`
mkdir $tempPath && tar -zxf file.tar.gz -C $tempPath &&
find $tempPath -type f | while read FILENAME
do
        (if grep -l "string" "$FILENAME"
        then
                echo "$FILENAME contains string"
        fi) &
done && rm -r $tempPath

ここでは、findを使用して、tarのターゲットディレクトリにあるファイルのリストを取得します。これは、文字列を検索する各ファイルについて、繰り返し処理しています。

編集: Jimが指摘したように、grep -lを使用して物事をスピードアップします。 man grepから:

   -l, --files-with-matches
          Suppress normal output; instead print the name of each input file from which output would
          normally have been printed.  The scanning will stop on the first match.  (-l is specified
          by POSIX.)
2
nemo

このオプションは本当に実行可能です:zcat log.tar.gz | grep -a -i "string"

これにより、パターンに一致する行全体が印刷されます。 zgrepは実際には有用な出力を提供しません。

$ zgrep -i 'CDF_FEED' FeedService.log.1.05-31-2019-150003.tar.gz | more
Binary file (standard input) matches

$ zcat FeedService.log.1.05-31-2019-150003.tar.gz | grep -ai 'CDF_FEED'
2019-05-30 19:20:14.568 ERROR 281 --- [http-nio-8007-exec-360] DrupalFeedService  : CDF_FEED_SERVICE::CLASSIFICATION_ERROR:408: Classification failed even after maximum retries for url : abcd.html
2
Nutan

上記のすべてのコードは本当に役に立ちましたが、私自身のニーズに完全に応えるものはありませんでした:grep all *.tar.gzファイルを現在のディレクトリに配置して、出力する再利用可能なスクリプトの引数として指定されたパターンを見つけます。

  • アーカイブファイルと抽出されたファイルの両方の名前
  • パターンが見つかった行番号
  • 一致する行の内容

zgrepが私にできることを本当に望んでいたのに、できません。

私のソリューションは次のとおりです。

pattern=$1
for f in *.tar.gz; do
     echo "$f:"
     tar -xzf "$f" --to-command 'grep --label="`basename $TAR_FILENAME`" -Hin '"$pattern ; true";
done

すべての変数が基本的なtarステートメントで適切に展開されていることをテストする場合は、echo行を次の行に置き換えることもできます。

tar -xzf "$f" --to-command 'echo "f:`basename $TAR_FILENAME` s:'"$pattern\""

何が起こっているのか説明しましょう。うまくいけば、問題のアーカイブファイル名のforループとechoが明らかです。

tar -xzfx抽出、z gzipによるフィルター、fは次のアーカイブファイルに基づいて...

"$f":forループによって提供されるアーカイブファイル(lsを実行することで得られるものなど)を二重引用符で囲んで、変数を展開し、スクリプトがどのファイルでも破損しないようにします。スペースなどの名前.

--to-command:実際にファイルをファイルシステムに抽出するのではなく、tarコマンドの出力を別のコマンドに渡します。この後のすべてが、コマンドが何であるか(grep)と、そのコマンドに渡す引数を指定します。

ここでは「秘密のソース」なので、その部分を分解してみましょう。

'grep --label="`basename $TAR_FILENAME`" -Hin '"$pattern ; true"

最初に、単一引用符を使用してこのチャンクを開始し、実行されたサブコマンド(basename $TAR_FILENAME)はnotすぐに展開/解決されます。それについては後ほど詳しく説明します。

grep:(実際にではなく)抽出されたファイルで実行されるコマンド

--label=:結果を付加するラベル。値は二重引用符で囲まれています。これは、dogrepコマンドで$TAR_FILENAME環境変数tarコマンドによって渡されます。

basename $TAR_FILENAME:コマンドとして実行され(バッククォートで囲まれています)、ディレクトリパスを削除し、ファイルの名前のみを出力します

-HinHファイル名の表示(ラベルで提供)、i大文字と小文字を区別しない検索、n一致する行番号の表示

次に、コマンド文字列の最初の部分を一重引用符で「終了」し、次の部分を二重引用符で開始して、$pattern(最初の引数として渡される)は解決できます。

私が最も長くつまずいた部分はどこで使用する必要があるかを理解しました。うまくいけば、これはすべてあなたにとって理にかなっており、他の誰かを助けます。また、1年後に再び必要になったときにこれを見つけられることを願っています(そして、そのために作成したスクリプトを忘れていました!)


そして、上記を書いてから数週間経ちましたが、それでも非常に便利です...しかし、ファイルが山積みになり、物事の検索が面倒になったので、それは十分ではありませんでした。ファイルの日付までに表示するものを制限する方法が必要でした(最新のファイルのみを表示する)。そのコードは次のとおりです。うまくいけば、それはかなり自明です。

if [ -z "$1" ]; then
    echo "Look within all tar.gz files for a string pattern, optionally only in recent files"
    echo "Usage: targrep <string to search for> [start date]"
fi
pattern=$1
startdatein=$2
startdate=$(date -d "$startdatein" +%s)
for f in *.tar.gz; do
    filedate=$(date -r "$f" +%s)
    if [[ -z "$startdatein" ]] || [[ $filedate -ge $startdate ]]; then
        echo "$f:"
        tar -xzf "$f" --to-command 'grep --label="`basename $TAR_FILENAME`" -Hin '"$pattern ; true"
    fi
done

そして、私はこのことを微調整するのを止めることができません。 tarファイルの出力ファイルの名前でフィルターする引数を追加しました。ワイルドカードも機能します。

使用法:

targrep.sh [-d <start date>] [-f <filename to include>] <string to search for>

例:

targrep.sh -d "1/1/2019" -f "*vehicle_models.csv" ford

while getopts "d:f:" opt; do
    case $opt in
            d) startdatein=$OPTARG;;
            f) targetfile=$OPTARG;;
    esac
done
shift "$((OPTIND-1))" # Discard options and bring forward remaining arguments
pattern=$1

echo "Searching for: $pattern"
if [[ -n $targetfile ]]; then
    echo "in filenames:  $targetfile"
fi

startdate=$(date -d "$startdatein" +%s)
for f in *.tar.gz; do
    filedate=$(date -r "$f" +%s)
    if [[ -z "$startdatein" ]] || [[ $filedate -ge $startdate ]]; then
            echo "$f:"
            if [[ -z "$targetfile" ]]; then
                    tar -xzf "$f" --to-command 'grep --label="`basename $TAR_FILENAME`" -Hin '"$pattern ; true"
            else
                    tar -xzf "$f" --no-anchored "$targetfile" --to-command 'grep --label="`basename $TAR_FILENAME`" -Hin '"$pattern ; true"
            fi
    fi
done
0
John T.