63

我正在尝试从十几个文件 .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
4

9 回答 9

125

如果你有zgrep你可以使用

zgrep -a string file.tar.gz
于 2013-06-05T13:06:21.653 回答
33

您可以使用该--to-command选项将文件通过管道传输到任意脚本。使用它,您可以一次处理存档(并且没有临时文件)。另请参阅此问题手册。有了上述信息,您可以尝试以下操作:

$ tar xf file.tar.gz --to-command "awk '/bar/ { print ENVIRON[\"TAR_FILENAME\"]; exit }'"
bfe2/.bferc
bfe2/CHANGELOG
bfe2/README.bferc
于 2012-12-21T15:32:48.057 回答
10

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

选项 1:使用tar --to-command grep

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

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

选项 2:使用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"
于 2017-02-15T19:05:15.600 回答
6

以下两个选项都运行良好。

$ 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
于 2019-06-13T07:48:36.720 回答
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也会有所帮助。如果文件很大,它将有很大帮助。

于 2012-12-21T02:24:11.197 回答
3

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

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.)
于 2012-12-21T02:20:05.067 回答
2

我正在尝试从十几个文件 .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
于 2020-02-05T01:41:51.110 回答
1

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

-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
于 2019-03-18T20:28:38.580 回答
0

就我而言,压缩包有很多小文件,我想知道压缩包内的存档文件匹配。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)


1我没有在 Linux 上运行它,我在 Windows 的最小 MSYS2 fork unixy 环境中使用 Git,因此 grep 的启动开销可能在 Linux 中要低得多,并且使 `tar --to-command grep`够好了; 在选择之前,根据您自己的需求和情况对解决方案进行基准测试。
于 2021-12-02T09:32:39.150 回答