web-dev-qa-db-fra.com

grep de tar.gz sans extraire [le plus rapide]

J'essaie de créer un motif pour une dizaine de fichiers .tar.gz, mais c'est très lent

j'utilise

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
47
Pixel

Si vous avez zgrep vous pouvez utiliser

zgrep -a string file.tar.gz
104
lanes

Vous pouvez utiliser le --to-command option pour diriger les fichiers vers un script arbitraire. Grâce à cela, vous pouvez traiter l’archive en une seule passe (et sans fichier temporaire). Voir aussi cette question , et le manuel . Armé des informations ci-dessus, vous pouvez essayer quelque chose comme:

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

Je sais que cette question a 4 ans, mais j'ai deux options différentes:

Option 1: Utiliser tar --to-command grep

La ligne suivante va regarder dans example.tgz pour PATTERN. Ceci est similaire à l'exemple de @ Jester, mais je ne parviens pas à faire correspondre sa correspondance de motif.

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

Option 2: Utiliser tar -tzf

La deuxième option utilise tar -tzf pour lister les fichiers, puis parcourez-les avec grep. Vous pouvez créer une fonction pour l'utiliser encore et encore:

targrep () {
    for i in $(tar -tzf "$1"); do
        results=$(tar -Oxzf "$1" "$i" | grep --label="$i" -H "$2")
        echo "$results"
    done
}

Usage:

targrep example.tar.gz "pattern"
5
Katie

Si cela est vraiment lent, je suppose que vous avez affaire à un fichier d'archive volumineux. Il va le décompresser une fois pour extraire la liste de fichiers, puis le décompresser N fois - où N est le nombre de fichiers de l'archive - pour le grep. En plus de la décompression, il va falloir numériser un peu chaque fois l'archive pour extraire chaque fichier. Un des plus gros inconvénients de tar est qu’il n’ya pas de table des matières au début. Il n'existe aucun moyen efficace d'obtenir des informations sur tous les fichiers de l'archive et de ne lire que cette partie du fichier. Il doit essentiellement lire tout le fichier jusqu'à la chose que vous extrayez à chaque fois; il ne peut pas simplement aller tout de suite à l'emplacement d'un nom de fichier.

La chose la plus simple que vous puissiez faire pour accélérer le processus consiste à décompresser d’abord le fichier (gunzip file.tar.gz) et ensuite travailler sur le .tar fichier. Cela pourrait aider assez par lui-même. Il restera cependant à parcourir l'archive entière N fois.

Si vous voulez vraiment que cela soit efficace, votre seule option est d'extraire complètement tout l'archive avant de la traiter. Puisque votre problème est la rapidité, je suppose que c'est un fichier géant que vous ne voulez pas extraire en premier, mais si vous le pouvez, cela accélérera beaucoup les choses:

tar zxf file.tar.gz
for f in hopefullySomeSubdir/*; do
  grep -l "string" $f
done

Notez que grep -l affiche le nom de tout fichier correspondant, se ferme après la première correspondance et reste muet s'il n'y a pas de correspondance. Cela seul accélérera la partie grepping de votre commande, donc même si vous n'avez pas assez d'espace pour extraire toute l'archive, grep -l aidera. Si les fichiers sont énormes, cela aidera beaucoup.

4
Jim Stewart

Pour commencer, vous pouvez démarrer plusieurs processus:

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

Le ( ... ) & crée un nouveau processus détaché (read: le shell parent n'attend pas l'enfant).

Après cela, vous devez optimiser l'extraction de votre archive. La lecture n’est pas un problème, car le système d’exploitation aurait déjà déjà mis en cache l’accès au fichier. Cependant, tar doit décompresser l'archive à chaque exécution de la boucle, ce qui peut être lent. Décompresser l'archive une fois et parcourir le résultat peuvent aider ici:

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 est utilisé ici pour obtenir une liste des fichiers dans le répertoire cible de tar sur lequel nous effectuons une nouvelle itération, pour chaque fichier cherchant une chaîne.

Edit: Utilisez grep -l pour accélérer les choses, comme l'a souligné Jim. De 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.)
2
nemo

Cette option est vraiment viable: zcat log.tar.gz | grep -a -i "chaîne"

Ceci imprimera toute la ligne qui correspond à votre motif. zgrep ne donne pas vraiment une sortie utile.

$ 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
2
Nutan

Tout le code ci-dessus était vraiment utile, mais rien de tout cela ne répondait parfaitement à mon propre besoin: grep all *.tar.gz fichiers dans le répertoire en cours pour trouver un modèle spécifié en tant qu’argument dans un script réutilisable afin de le générer:

  • Le nom du fichier archive et du fichier extrait
  • Le numéro de ligne où le motif a été trouvé
  • Le contenu de la ligne correspondante

C'est ce que j'espérais vraiment que zgrep puisse faire pour moi et il ne le peut tout simplement pas.

Voici ma solution:

pattern=$1
for f in *.tar.gz; do
     echo "$f:"
     tar -xzf "$f" --to-command 'grep --label="`basename $TAR_FILENAME`" -Hin '"$pattern ; true";
done

Vous pouvez également remplacer la ligne tar par ce qui suit si vous souhaitez vérifier que toutes les variables se développent correctement avec une instruction de base echo:

tar -xzf "$f" --to-command 'echo "f:`basename $TAR_FILENAME` s:'"$pattern\""

Laissez-moi vous expliquer ce qui se passe. Espérons que la boucle for et le echo du nom de fichier de l’archive en question sont évidents.

tar -xzf: x extraire, z filtrer par gzip, f en fonction du fichier d’archive suivant ...

"$f": Le fichier d’archive fourni par la boucle for (comme ce que vous obtiendriez en faisant un ls) entre guillemets pour permettre à la variable de se développer et de s’assurer que le script n’est cassé par aucun fichier. noms avec espaces, etc.

--to-command: Passez le résultat de la commande tar à une autre commande plutôt que d'extraire des fichiers dans le système de fichiers. Tout ce qui suit après spécifie ce qu'est la commande (grep) et quels arguments nous passons à cette commande.

Décomposons cette partie par elle-même, car c'est la "sauce secrète" ici.

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

Tout d’abord, nous utilisons un guillemet simple pour démarrer ce bloc de sorte que la sous-commande exécutée (basename $TAR_FILENAME) est pas immédiatement développé/résolu. Plus sur cela dans un instant.

grep: La commande à exécuter sur les fichiers extraits (pas réellement)

--label=: Le libellé à ajouter au début des résultats, dont la valeur est placée entre guillemets car nous do voulons que la commande grep résolve le $TAR_FILENAME variable d’environnement transmise par la commande tar.

basename $TAR_FILENAME: S'exécute en tant que commande (entourée de backticks), supprime le chemin du répertoire et affiche uniquement le nom du fichier.

-Hin: H Affiche le nom du fichier (fourni par l’étiquette), i Recherche insensible à la casse, n Affiche le numéro de ligne de la correspondance

Ensuite, nous "finissons" la première partie de la chaîne de commande par un guillemet simple et commençons la partie suivante par un guillemet double de sorte que le $pattern, passé en tant que premier argument, peut être résolu.

Réaliser les citations que je devais utiliser était la partie qui m'avait le plus retardée. J'espère que tout cela a du sens pour vous et aide quelqu'un d'autre. De plus, j'espère pouvoir le trouver dans une année où j'en aurai de nouveau besoin (et j'ai déjà oublié le script que j'ai déjà conçu pour cela!)


Et cela fait un peu de semaines que j'ai écrit ce qui précède et c'est toujours très utile ... mais ce n'était pas assez bon car les fichiers se sont accumulés et la recherche d'objets a été compliquée. J'avais besoin d'un moyen de limiter ce que j'ai regardé par la date du fichier (en regardant uniquement les fichiers plus récents). Alors voici ce code. J'espère que c'est assez explicite.

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

Et je ne peux pas m'empêcher de peaufiner cette chose. J'ai ajouté un argument pour filtrer par le nom des fichiers de sortie dans le fichier tar. Les jokers fonctionnent aussi.

Usage:

targrep.sh [-d <start date>] [-f <filename to include>] <string to search for>

Exemple:

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
John T.