Diretórios com dois ou mais arquivos

11

Quero encontrar um subdiretório do diretório atual, que (que é o subdiretório) contém 2 ou mais arquivos regulares.

Não estou interessado em diretórios contendo menos de 2 arquivos, nem em diretórios que contêm apenas subdiretórios.

Porton
fonte

Respostas:

12

Aqui está uma abordagem completamente diferente baseada no GNU finde uniq. Isso é muito mais rápido e mais amigável à CPU do que as respostas baseadas na execução de um comando shell que conta os arquivos para cada diretório encontrado.

find . -type f -printf '%h\n' | sort | uniq -d

O findcomando imprime o diretório de todos os arquivos na hierarquia e uniqexibe apenas os diretórios que aparecem pelo menos duas vezes.

xhienne
fonte
2
Você não deve analisar a saída de find. Nesse caso, porque o GNU findirá alterar os nomes dos diretórios que possuem caracteres que não podem ser impressos no local atual (como "ä" no local C). Veja também unix.stackexchange.com/questions/321697/…
Kusalananda
4
@ Kusalananda, não quando a saída não vai para um tty. Aqui, o único problema é com os caracteres de nova linha, que você pode corrigir usando-printf '%h\0' | sort -z | uniq -zd | xargs -r0 ...
Stéphane Chazelas
6
find . -type d \
    -exec sh -c 'c=0; for n in "$1"/*; do [ -f "$n" ] && [ ! -h "$n" ] && c=$(( c + 1 )); done; [ "$c" -ge 2 ]' sh {} ';' \
    -print

Isso localizará todos os nomes no diretório atual ou abaixo dele e, em seguida, filtrará todos os nomes que não sejam nomes de diretórios.

Os nomes de diretório restantes serão atribuídos a este script curto:

c=0
for n in "$1"/*; do
    [ -f "$n" ] && [ ! -h "$n" ] && c=$(( c + 1 ))
done

[ "$c" -ge 2 ]

Esse script contará o número de arquivos regulares (pulando links simbólicos) no diretório fornecido como o primeiro argumento da linha de comando (de find). O último comando no script é um teste para verificar se a contagem foi 2 ou superior. O resultado desse teste é o valor de retorno (status de saída) do script.

Se o teste bem sucedido, -printirá causar findpara imprimir o caminho para o diretório.

Para também considerar arquivos ocultos (arquivos cujos nomes começam com um ponto), altere o sh -cscript dizendo

for n in "$1"/*; do

para

for n in "$1"/* "$1"/.*; do

Teste:

$ tree
.
`-- test
    |-- a
    |-- dir1
    |   |-- a
    |   |-- b
    |   `-- c
    `-- dir2
        |-- dira
        |-- dirb
        |   |-- file-1
        |   `-- file-2
        `-- dirc

6 directories, 6 files

$ find . -type d -exec sh -c 'c=0; for n in "$1"/*; do [ -f "$n" ] && [ ! -h "$n" ] && c=$(( c + 1 )); done; [ "$c" -ge 2 ]' sh {} ';' -print
./test/dir1
./test/dir2/dirb
Kusalananda
fonte
Sua solução não conta arquivos com um nome começando com um ponto. Você também deve inicializar c = 0 para evitar mensagens de erro com diretórios que não contêm nenhum arquivo.
xhienne
@xhienne Eu considerei arquivos ocultos e vou adicionar uma observação sobre isso. Não há erro se não houver arquivos regulares em um diretório, pois [ "" -ge 2 ]é um teste válido.
Kusalananda
Não sabe como definir "válido". O POSIX requer que arg1 seja um valor inteiro. dash, bash --posixE testtoda a exibição uma mensagem de erro e de saída com duas (ou seja, "Erro")
xhienne
@xhienne Ah, eu estava testando em um sistema que kshestá sendo executado como sh. Irá alterar imediatamente. Obrigado por me cutucar! :-)
Kusalananda
Além disso, [ -f ... ]desreferencia links simbólicos. Você deve adicionar um teste para eliminá-los, pois a pergunta especifica que apenas os arquivos regulares devem ser contados.
Xhienne # 8/17
6

Com a ajuda da resposta de Gilles no SU e seu reverso e algumas modificações, aqui está o que você precisa.

find . -type d -exec sh -c 'set -- "$1"/*;X=0; 
    for args; do [ -f "$args" ] && X=$((X+1)) ;done; [ "$X" -gt 1 ] ' _ {} \; -print

Árvore de diretórios.

.
├── test
│   ├── dir1
│   │   ├── a
│   │   ├── b
│   │   └── c
│   ├── dir2
│   │   ├── dira
│   │   │   └── a file\012with\012multiple\012line
│   │   ├── dirb
│   │   │   ├── file-1
│   │   │   └── file-2
│   │   └── dirc
│   ├── diraa
│   ├── dirbb
│   ├── dircc
│   └── x
│   └── x1
│   └── x2
└── test2
    ├── dir3
    └── dir4

Resultado:

./test
./test/dir1
./test/dir2/dirb
αғsнιη
fonte
Eu tive isso no começo também, mas você terá problemas com diretórios contendo vários subdiretórios e arquivos. Também não elimina diretórios que contêm apenas subdiretórios.
Kusalananda
Realmente não resolve. Ele encontra testos dir2diretórios e os arquivos na minha configuração de teste (veja minha resposta).
Kusalananda
Funciona para o seu exemplo, mas adicione test/x1e também test/x2como arquivos ... $1e $2serão diretórios para test, e o diretório será perdido.
Kusalananda
@Kusalananda De jeito nenhum eu encontrei, exceto o que você respondeu, tentei alterar uma parte do meu comando para não ser sua duplicata exata (não excluí arquivos ocultos como você), peço desculpas.
αғsнιη
1
Não se preocupe :-)
Kusalananda
3

Outra find+ wcabordagem:

find path/currdir -maxdepth 1 -type d ! -empty ! -path "path/currdir" \
-exec sh -c 'count=$(find "$1" -maxdepth 1 -type f | wc -l); [ $count -ge 2 ]' _ {} \; -print

  • path/currdir - caminho para o seu diretório atual

  • -maxdepth 1- considere apenas subpastas filho diretas

  • ! -empty - ignorar subpastas vazias

  • ! -path "path/currdir" - ignora o caminho do diretório atual

  • count=$(find "$1" -maxdepth 1 -type f | wc -l)- counté atribuído com o número de arquivos para cada subpasta encontrada

  • [ $count -ge 2 ] ... -print - imprime o nome / caminho da subpasta contendo 2 ou mais arquivos regulares

RomanPerekhrest
fonte