Como grep recursivamente através de arquivos compactados?

16

Estou tentando descobrir quais módulos use Test::Versionno cpan. Então, eu costumava minicpanespelhar isso. Meu problema é que eu preciso percorrer os arquivos baixados e grep os arquivos que estão nos arquivos. Alguém pode me dizer como eu poderia fazer isso? de preferência de uma maneira que me diga qual arquivo no arquivo e em que linha está.

(nota: eles não são todos tarballs, alguns são arquivos zip)

xenoterracida
fonte

Respostas:

18

Ok, vamos aplicar a filosofia unix. Quais são os componentes desta tarefa?

  • Pesquisa de texto: você precisa de uma ferramenta para pesquisar texto em um arquivo, como grep.
  • Recursiva: você precisa de uma ferramenta para procurar arquivos em uma árvore de diretórios, como find.
  • Arquivos: você precisa de uma ferramenta para lê-los.

A maioria dos programas unix opera em arquivos. Portanto, para operar facilmente em componentes de arquivamento, você precisa acessá-los como arquivos, ou seja, é necessário acessá-los como diretórios.

O sistema de arquivos AVFS apresenta uma visão do sistema de arquivos em que cada arquivo /path/to/foo.zipestá acessível como um diretório ~/.avfs/path/to/foo/zip#. O AVFS fornece acesso somente leitura aos formatos de arquivo mais comuns.

mountavfs
find ~/.avfs"$PWD" \( -name '*.zip' -o -name '*.tar.gz' -o -name '*.tgz' \) \
     -exec sh -c '
                  find "$0#" -name "*.pm" -exec grep "$1" {\} +
                 ' {} 'Test::Version' \;
fusermount -u ~/.avfs   # optional

Explicações:

  • Monte o sistema de arquivos AVFS.
  • Procure por arquivos ~/.avfs$PWDcompactados, que é a visualização AVFS do diretório atual.
  • Para cada arquivo morto, execute o snippet de shell especificado (com $0= nome do arquivo e $1= padrão para pesquisar).
  • $0#é a visualização do diretório do arquivo morto $0.
  • {\}em vez de {}é necessária no caso dos exteriores findsubstitutos {}dentro -exec ;argumentos (alguns o fazem, outros não).
  • Opcional: finalmente desmonte o sistema de arquivos AVFS.

Ou em zsh ≥4.3:

mountavfs
grep 'Test::Version' ~/.avfs$PWD/**/*.(tgz|tar.gz|zip)(e\''
     reply=($REPLY\#/**/*.pm(.N))
'\')

Explicações:

  • ~/.avfs$PWD/**/*.(tgz|tar.gz|zip) corresponde aos arquivos na visualização AVFS do diretório atual e de seus subdiretórios.
  • PATTERN(e\''CODE'\')aplica CODE a cada correspondência de PATTERN. O nome do arquivo correspondente está em $REPLY. Definir a replymatriz transforma a correspondência em uma lista de nomes.
  • $REPLY\# é a visualização do diretório do arquivo morto.
  • $REPLY\#/**/*.pmcorresponde aos .pmarquivos no arquivo morto.
  • O Nqualificador glob faz com que o padrão se expanda para uma lista vazia se não houver correspondência.
Gilles 'SO- parar de ser mau'
fonte
isso cria outro problema intesting de ter que montar e desmontar tudo, em seguida, dos arquivos, como parte do problema é que existem 22k arquivos que precisam ser pesquisados por meio
xenoterracide
@xenoterracide: Como isso é um problema? Com o AVFS, você tem um único ponto de montagem ( ~/.avfs) e o acesso a cada arquivo ~/.avfs/path/to/archive.zip\#morto é automático ( é um diretório comum no sistema de arquivos AVFS, não um ponto de montagem). Claro, cada arquivo acessado significa um pequeno impacto no desempenho, mas isso é intrínseco ao problema.
Gilles 'SO- stop be evil'
@gilles apenas o fato de que agora eu tenho que passar e descobrir como montá-los primeiro, o que parece um pouco de uma má ideia, melhor montá-los quando for desmontar depois de ser pesquisado.
xenoterracide
@xenoterracide: Novamente: não, você não precisa montá-los individualmente. O fluxo de trabalho completo (além da instalação do AVFS, se necessário) está nos meus trechos de código.
Gilles 'SO- stop be evil'
@gilles bem eu vou ter que cavar para isso um pouco ... porque eu get find: missing argument to -exec'` e lotes deste de zshzsh: Input/output error: Data-Maker-0.27
xenoterracide
0

Parece que eu posso fazer assim

find authors/ -type f -exec zgrep "Test::Version" '{}' +  

No entanto, isso fornece resultados como:

authors/id/J/JO/JONASBN/Module-Info-File-0.11.tar.gz:Binary file (standard input) matches

o que não é muito específico para onde no tarball. Espero que alguém possa ter uma resposta melhor.

xenoterracida
fonte
0

Obrigado pelo desafio, eu vim com:

#!/bin/bash
#

# tarballs to check in
find authors/ -type f | while read tarball; do

    # get list of files in tarball (not dirs ending in /):
    tar tzf $tarball | grep -v '/$' | while read file; do       

        # get contents of file and look for string
        tar -Ozxf conform.tar.gz $file | grep -q 'Text::Version' && echo "Tar ($tarball) has matching File ($file)"

    done

done
Kyle Smith
fonte
Acabei de ver seu requisito de número de linha. Provavelmente isso pode funcionar com alguma combinação de grep -n e awk para capturar o número da linha. Não pode ser tão simples quanto grep -H para listar o nome do arquivo, pois ele é sempre stdin; portanto, pode exigir mais linhas.
Kyle Smith #
erros quando executado no meu sistema, infinito repetido:tar (child): conform.tar.gz: Cannot open: No such file or directory tar (child): Error is not recoverable: exiting now tar: Child returned status 2 tar: Error is not recoverable: exiting now
xenoterracide 25/05
Também não percebi, quando publiquei isso pela primeira vez, que alguns arquivos no cpan são arquivos zip.
Xenoterracide 25/05
Hum, eu testei com uma estrutura de apenas arquivos .tar.gz - poderia ser mais robusto executar ações apropriadas com base no tipo de arquivo, mas isso deve dar um ponto de partida decente.
Kyle Smith
0

Talvez minha resposta seja útil para alguém:

#!/bin/bash

findpath=$(echo $1 | sed -r 's|(.*[^/]$)|\1/|')

# tarballs to check in
find $findpath -type f | while read tarball; do

    # get list of files in tarball (not dirs ending in /):
    if [ -n "$(file --mime-type $tarball | grep -e "application/jar")" ]; then

        jar tf $tarball | grep -v '/$' | while read file; do
            # get contents of file and look for string
            grepout=$(unzip -q -c $tarball $file | grep $3 -e "$2")

            if [ -n "$grepout" ]; then
                echo "*** $tarball has matching file ($file):"
                echo $grepout
            fi

        done

    elif tar -tf $tarball 2>/dev/null; then

        tar -tf $tarball | grep -v '/$' | while read file; do
            # get contents of file and look for string
            grepout=$(unzip -q -c $tarball $file | grep $3 -e "$2")

            if [ -n "$grepout" ]; then
                echo "*** $tarball has matching file ($file):"
                echo $grepout
            fi

        done

    else
        file=""
        grepout=$(grep $3 -e "$2" $tarball)

        if [ -n "$grepout" ]; then
            echo "*** $tarball has matching:"
            echo $grepout
        fi

    fi

done
Serge Roussak
fonte
0

Após a instalação, p7zip-*você é capaz de fazer isso:

ls | xargs -I {} 7z l {} | grep whatever | less

Você não precisa usar lsantes do primeiro canal, qualquer que seja a lista dos arquivos compactados. A final lessmostrará apenas o CAMINHO da vida útil do listet dentro do arquivo compactado, mas não o nome disso.

Roberto Robert
fonte
0

Use find para localizar todos os arquivos necessários e o zgrep para procurar arquivos compactados:

find <folder> -type f -name "<search criteria[*gz,*bz...]>" -execdir zgrep -in "<grep expression>" '{}' ';'

Não testou isso em tarballs

Iggy Pop
fonte