我正在尝试从十几个文件 .tar.gz 中提取模式,但它非常慢
我正在使用
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
以下行将查找example.tgz
. PATTERN
这类似于@Jester 的示例,但我无法让他的模式匹配工作。
tar xzf example.tgz --to-command 'grep --label="$TAR_FILENAME" -H PATTERN ; true'
tar -tzf
第二个选项是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"
以下两个选项都运行良好。
$ zgrep -ai 'CDF_FEED' FeedService.log.1.05-31-2019-150003.tar.gz | more
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
$ 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
如果这真的很慢,我怀疑您正在处理一个大型存档文件。它将解压缩一次以提取文件列表,然后将其解压缩 N 次——其中 N 是存档中的文件数——用于 grep。除了所有解压缩之外,每次都必须在存档中扫描相当多的内容以提取每个文件。最大的缺点之一tar
是一开始没有目录。没有有效的方法来获取有关存档中所有文件的信息,并且只读取文件的该部分。它本质上必须读取所有文件,直到您每次提取的内容;它不能立即跳转到文件名的位置。
要加快速度,您可以做的最简单的事情是先解压缩文件 ( gunzip file.tar.gz
),然后处理该.tar
文件。这本身可能就足够了。不过,它仍然会循环整个存档 N 次。
如果您真的希望它高效,您唯一的选择是在处理之前完全提取存档中的所有内容。由于您的问题是速度,我怀疑这是一个您不想先提取的巨大文件,但如果可以的话,这将大大加快速度:
tar zxf file.tar.gz
for f in hopefullySomeSubdir/*; do
grep -l "string" $f
done
请注意,grep -l
打印任何匹配文件的名称,在第一个匹配后退出,如果没有匹配则静音。仅此一项就可以加快命令的 grepping 部分,因此即使您没有空间来提取整个存档,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
创建一个新的( ... ) &
分离(阅读:父外壳不等待子外壳)进程。
之后,您应该优化存档的提取。读取没有问题,因为操作系统应该已经缓存了文件访问。但是,每次循环运行时,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
,用于搜索字符串的每个文件。
编辑: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.)
我正在尝试从十几个文件 .tar.gz 中提取模式,但它非常慢
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
使用ugrep选项实际上很容易-z
:
-z, --decompress
Decompress files to search, when compressed. Archives (.cpio,
.pax, .tar, and .zip) and compressed archives (e.g. .taz, .tgz,
.tpz, .tbz, .tbz2, .tb2, .tz2, .tlz, and .txz) are searched and
matching pathnames of files in archives are output in braces. If
-g, -O, -M, or -t is specified, searches files within archives
whose name matches globs, matches file name extensions, matches
file signature magic bytes, or matches file types, respectively.
Supported compression formats: gzip (.gz), compress (.Z), zip,
bzip2 (requires suffix .bz, .bz2, .bzip2, .tbz, .tbz2, .tb2, .tz2),
lzma and xz (requires suffix .lzma, .tlz, .xz, .txz).
只需一个命令即可搜索file.tar.gz
,如下所示:
ugrep -z "string" file.tar.gz
这对每个存档文件进行 greps 以显示匹配项。存档文件名显示在大括号中,以区别于普通文件名。例如:
$ ugrep -z "Hello" archive.tgz
{Hello.bat}:echo "Hello World!"
Binary file archive.tgz{Hello.class} matches
{Hello.java}:public class Hello // prints a Hello World! greeting
{Hello.java}: { System.out.println("Hello World!");
{Hello.pdf}:(Hello)
{Hello.sh}:echo "Hello World!"
{Hello.txt}:Hello
如果您只想要文件名,请使用选项-l
( --files-with-matches
) 并使用选项自定义文件名输出以--format="%z%~"
摆脱大括号:
$ ugrep -z Hello -l --format="%z%~" archive.tgz
Hello.bat
Hello.class
Hello.java
Hello.pdf
Hello.sh
Hello.txt
上面的所有代码都非常有用,但没有一个能完全满足我自己的需求:当前目录中的grep
所有*.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
)不会立即展开/解析。稍后会详细介绍。
grep
:要在(实际上不是)提取的文件上运行的命令
--label=
: 用于添加结果的标签,其值用双引号括起来,因为我们确实希望grep
命令解析命令$TAR_FILENAME
传入的环境变量tar
。
basename $TAR_FILENAME
:作为命令运行(由反引号包围)并删除目录路径并仅输出文件名
-Hin
:H
显示文件名(由标签提供),i
不区分大小写搜索,n
显示匹配行数
然后我们用单引号“结束”命令字符串的第一部分,并用双引号开始下一部分,以便$pattern
可以解析作为第一个参数传入的 。
意识到我需要使用哪些引语是让我绊倒时间最长的部分。希望这一切对您有意义并帮助其他人。另外,我希望我能在一年内再次需要它时找到它(我已经忘记了我为它制作的脚本!)
自从我写以上内容以来已经有几个星期了,它仍然非常有用......但它还不够好,因为文件已经堆积起来并且搜索东西变得更加混乱。我需要一种方法来限制我在文件日期之前查看的内容(仅查看更新的文件)。所以这就是代码。希望这是不言自明的。
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
就我而言,压缩包有很多小文件,我想知道压缩包内的存档文件匹配。zgrep
速度很快(不到一秒),但没有提供我想要的信息tar --to-command grep
,而且慢得多(很多分钟)1。
所以我转向另一个方向,zgrep
告诉我 tarball 中匹配项的字节偏移量,并将其与所有存档文件的 tarball 中的偏移量列表放在一起,以找到匹配的存档文件。
#!/bin/bash
set -e
set -o pipefail
function tar_offsets() {
# Get the byte offsets of all the files in a given tarball
# based on https://stackoverflow.com/a/49865044/60422
[ $# -eq 1 ]
tar -tvf "$1" -R | awk '
BEGIN{
getline;
f=$8;
s=$5;
}
{
offset = int($2) * 512 - and((s+511), compl(512)+1)
print offset,s,f;
f=$8;
s=$5;
}'
}
function tar_byte_offsets_to_files() {
[ $# -eq 1 ]
# Convert the search results of a tarball with byte offsets
# to search results with archived file name and offset, using
# the provided tar_offsets output (single pass, suitable for
# process substitution)
offsets_file="$1"
prev_offset=0
prev_offset_filename=""
IFS=' ' read -r last_offset last_len last_offset_filename < "$offsets_file"
while IFS=':' read -r search_result_offset match_text
do
while [ $last_offset -lt $search_result_offset ]; do
prev_offset=$last_offset
prev_offset_filename="$last_offset_filename"
IFS=' ' read -r last_offset last_len last_offset_filename < "$offsets_file"
# offsets increasing safeguard
[ $prev_offset -le $last_offset ]
done
# now last offset is the first file strictly after search result offset so prev offset is
# the one at or before it, and must be the one it is in
result_file_offset=$(( $search_result_offset - $prev_offset ))
echo "$prev_offset_filename:$result_file_offset:$match_text"
done
}
# Putting it together e.g.
zgrep -a --byte-offset "your search here" some.tgz | tar_byte_offsets_to_files <(tar_offsets some.tgz)