Подтвердить что ты не робот

Grep из tar.gz без извлечения [более быстрый]

Я пытаюсь использовать grep шаблон из дюжины файлов .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
4b9b3361

Ответ 1

Если у вас zgrep, вы можете использовать

zgrep -a string file.tar.gz

Ответ 2

Вы можете использовать опцию --to-command для передачи файлов в произвольный скрипт. Используя это, вы можете обработать архив за один проход (и без временного файла). Смотрите также этот вопрос и руководство. Вооружившись приведенной выше информацией, вы можете попробовать что-то вроде:

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

Ответ 3

Если это происходит очень медленно, я подозреваю, что вы имеете дело с большим архивом. Он собирается распаковать его один раз, чтобы извлечь список файлов, а затем разархивировать его 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 года, но у меня есть несколько разных вариантов:

Вариант 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"

Ответ 5

Для начала вы можете запустить несколько процессов:

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

( ... ) & создает новый отсоединенный (read: родительская оболочка не ждет дочернего элемента) процесс.

После этого вы должны оптимизировать извлечение своего архива. Чтение не представляет проблемы, поскольку ОС должна была кэшировать доступ к файлу уже. Однако, 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.)

Ответ 6

Весь приведенный выше код был действительно полезен, но ни один из них полностью не отвечал моей собственной потребности: 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 extract, z filter через gzip, f на основе следующего архивного файла...

"$f": файл архива, предоставленный циклом for (например, то, что вы получите, выполнив ls) в двойных кавычках, чтобы позволить переменной расширяться и гарантировать, что скрипт не будет разбит любыми именами файлов с пробелами, так далее.

--to-command: передать вывод команды tar другой команде, вместо того, чтобы фактически извлекать файлы в файловую систему. Все, что после этого указывает, что это за команда (grep) и какие аргументы мы передаем этой команде.

Давайте разбить эту часть на себя, так как это "секретный соус" здесь.

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

Во-первых, мы используем одинарную кавычку для запуска этого чанка, чтобы выполняемая basename $TAR_FILENAME (basename $TAR_FILENAME) не была сразу расширена/разрешена. Подробнее об этом через минуту.

grep: команда, запускаемая с извлеченных файлов (но не с них)

--label=: Метка для добавления результатов, значение которых заключено в двойные кавычки, поскольку мы хотим, чтобы команда grep разрешила переменную среды $TAR_FILENAME переданную командой tar.

basename $TAR_FILENAME: запускается как команда (в окружении обратных галочек), удаляет путь к каталогу и выводит только имя файла

-Hin: H Показать имя файла (-Hin меткой), i Поиск без -Hin регистра, 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

Ответ 7

Эта опция действительно жизнеспособна: zcat log.tar.gz | grep -a -i "строка"

Это напечатает всю строку, которая соответствует вашему шаблону. 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