Obter lista de subdiretórios que contêm um arquivo cujo nome contém uma string

30

Como posso obter uma lista dos subdiretórios que contêm um arquivo cujo nome corresponda a um padrão específico?

Mais especificamente, estou procurando diretórios que contenham um arquivo com a letra 'f' em algum lugar ocorrendo no nome do arquivo.

Idealmente, a lista não teria duplicatas e conteria apenas o caminho sem o nome do arquivo.

    
por Muhd 01.02.2014 / 03:10

6 respostas

27
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

Os itens acima encontram todos os arquivos abaixo do diretório atual ( . ) que são arquivos regulares ( -type f ) e têm f em algum lugar em seu nome ( -name '*f*' ). Em seguida, sed remove o nome do arquivo, deixando apenas o nome do diretório. Em seguida, a lista de diretórios é classificada ( sort ) e as duplicatas são removidas ( uniq ).

O comando sed consiste em um único substituto. Ele procura por correspondências com a expressão regular /[^/]+$ e substitui qualquer coisa que corresponda a isso com nada. O cifrão significa o fim da linha. [^/]+' significa um ou mais caracteres que não são barras. Portanto, /[^/]+$ significa todos os caracteres da barra final até o final da linha. Em outras palavras, isso corresponde ao nome do arquivo no final do caminho completo. Assim, o comando sed remove o nome do arquivo, deixando inalterado o nome do diretório em que o arquivo estava.

Simplificações

Muitos comandos sort modernos suportam um sinal -u , o que torna uniq desnecessária. Para o GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

E, para o MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

Além disso, se o seu comando find for compatível, é possível ter find imprimir os nomes dos diretórios diretamente. Isso evita a necessidade de sed :

find . -type f -name '*f*' -printf '%h\n' | sort -u

Versão mais robusta (requer ferramentas GNU)

As versões acima serão confundidas por nomes de arquivos que incluem novas linhas. Uma solução mais robusta é fazer a classificação em strings terminadas em NUL:

find . -type f -name '*f*' -printf '%h
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq
' | sort -zu | sed -z 's/$/\n/'
    
por 01.02.2014 / 03:17
16

Por que não tentar isso:

find / -name '*f*' -printf "%h\n" | sort -u
    
por 07.04.2015 / 23:38
8

Existem essencialmente 2 métodos que você pode usar para fazer isso. Um irá analisar a cadeia enquanto o outro irá operar em cada arquivo. Analisar a string usando uma ferramenta como grep , sed ou awk obviamente será mais rápido, mas aqui está um exemplo mostrando os dois, bem como como você pode "definir o perfil" dos dois métodos.

Dados de amostra

Para os exemplos abaixo, usaremos os seguintes dados

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

Exclua alguns dos arquivos *f* de dir1/* :

$ rm dir1/dir10{0..2}/*f*

Abordagem # 1 - Análise por meio de strings

Aqui, vamos usar as seguintes ferramentas, find , grep e sort .

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

Abordagem # 2 - Análise usando arquivos

Mesma cadeia de ferramentas de antes, exceto que, desta vez, usaremos dirname em vez de grep .

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

OBSERVAÇÃO: Os exemplos acima estão usando head -5 para limitar a quantidade de saída que estamos lidando para esses exemplos. Eles normalmente seriam removidos para obter sua listagem completa!

Comparando os resultados

Podemos usar time para ver as duas abordagens.

dirname

real        0m0.372s
user        0m0.028s
sys         0m0.106s

grep

real        0m0.012s
user        0m0.009s
sys         0m0.007s

Portanto, é sempre melhor lidar com as sequências, se possível.

Métodos de análise de sequência alternativa

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

sed

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

awk

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u
    
por 01.02.2014 / 03:41
1

Esta resposta é descaradamente baseada na resposta slm. Foi uma abordagem interessante, mas tem uma limitação se os nomes de arquivos e / ou diretórios tiverem caracteres especiais (espaço, semicoluna ...). Um bom hábito é usar find /somewhere -print0 | xargs -0 someprogam .

Dados de amostra

Para os exemplos abaixo, usaremos os seguintes dados

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

Exclua alguns dos arquivos *f* de dir1/*/ :

rm dir1/dir\ 10{0..2}/*f*

Abordagem # 1 - Análise usando arquivos

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

OBSERVAÇÃO : Os exemplos acima estão usando head -5 para limitar a quantidade de saída que estamos lidando para esses exemplos. Eles normalmente seriam removidos para obter sua listagem completa! Além disso, substitua o echo que qualquer comando que você deseja usar.

    
por 04.04.2015 / 01:33
1

Aqui está uma que eu acho útil:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq
    
por 01.04.2016 / 20:25
1

com zsh :

typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))

printf '%s\n' $dirs
    
por 01.04.2018 / 09:04