来自tar.gz的grep没有提取[更快的]

问题描述 投票:43回答:7

我试图从十几个文件.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
linux bash grep
7个回答
98
投票

如果你有zgrep你可以使用

zgrep -a string file.tar.gz

29
投票

您可以使用--to-command选项将文件传输到任意脚本。使用此功能,您可以一次性处理存档(并且没有临时文件)。另见this questionmanual。有了上述信息,您可以尝试以下方法:

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

4
投票

如果这真的很慢,我怀疑你正在处理一个大型档案文件。它将解压缩一次以提取文件列表,然后将其解压缩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也会有所帮助。如果文件很大,那将会有很大帮助。


4
投票

我知道这个问题是4岁,但我有几个不同的选择:

Option 1: Using tar --to-command grep

以下行将在example.tgz中查找PATTERN。这类似于@Jester的例子,但我无法让他的模式匹配工作。

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

Option 2: Using 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"

2
投票

对于初学者,您可以启动多个进程:

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

( ... ) &创建一个新的分离(读:父shell不等待子进程)进程。

之后,您应该优化存档的提取。读取没有问题,因为操作系统应该已经缓存了文件访问权限。但是,每次循环运行时,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.)

0
投票

上面的所有代码都非常有用,但它们都没有完全满足我自己的需要: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 -xzfx提取物,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:作为命令运行(由反引号包围)并删除目录路径并仅输出文件名

-HinH显示文件名(由标签提供),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

0
投票

这个选项真的可行: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
© www.soinside.com 2019 - 2024. All rights reserved.