ダースのファイル.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
zgrep
がある場合は、使用できます
zgrep -a string file.tar.gz
この質問は4年前のものですが、いくつかのオプションがあります。
tar --to-command grep
の使用次の行は、PATTERN
のexample.tgz
を探します。これは@Jesterの例に似ていますが、パターンマッチングを機能させることができませんでした。
tar xzf example.tgz --to-command 'grep --label="$TAR_FILENAME" -H PATTERN ; true'
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"
これが本当に遅い場合は、大きなアーカイブファイルを扱っていると思われます。 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
役立ちます。ファイルが巨大な場合、それは大いに役立ちます。
まず、複数のプロセスを開始できます。
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.)
このオプションは本当に実行可能です: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
上記のすべてのコードは本当に役に立ちましたが、私自身のニーズに完全に応えるものはありませんでした: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 -xzf
:x
抽出、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
:コマンドとして実行され(バッククォートで囲まれています)、ディレクトリパスを削除し、ファイルの名前のみを出力します
-Hin
:H
ファイル名の表示(ラベルで提供)、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