Como reportar o número de arquivos em todos os subdiretórios?

21

Eu preciso inspecionar todos os subdiretórios e relatar quantos arquivos (sem recursão adicional) eles contêm:

directoryName1 numberOfFiles
directoryName2 numberOfFiles
    
por ShyBoy 23.10.2011 / 04:46

7 respostas

24

Isso é feito de maneira segura e portátil. Não vai ficar confuso com nomes estranhos de arquivos.

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

Se você tem um conjunto específico de subdiretórios nos quais está interessado, é possível substituir o * por eles.

Por que isso é seguro? (e, portanto, digno do script)

Nomes de arquivo podem conter qualquer caractere, exceto / . Existem alguns caracteres que são tratados especialmente pelo shell ou pelos comandos. Esses incluem espaços, novas linhas e traços.

Usar a construção for f in * é uma maneira segura de obter cada nome de arquivo, independentemente do conteúdo.

Depois de ter o nome do arquivo em uma variável, você ainda terá que evitar itens como find $f . Se $f continha o nome do arquivo -test , find reclamaria da opção que você acabou de fornecer. A maneira de evitar isso é usando ./ na frente do nome; Desta forma, tem o mesmo significado, mas não começa mais com um traço.

Novas linhas e espaços também são um problema. Se $f contiver "Olá, amigo" como um nome de arquivo, find ./$f , será find ./hello, buddy . Você está dizendo find para ver ./hello, e buddy . Se isso não existir, ele irá reclamar, e nunca irá aparecer em ./hello, buddy . Isso é fácil de evitar - use aspas em torno de suas variáveis.

Por fim, os nomes de arquivos podem conter novas linhas, portanto, contar novas linhas em uma lista de nomes de arquivos não funcionará; você receberá uma contagem extra para cada nome de arquivo com uma nova linha. Para evitar isso, não conte as novas linhas em uma lista de arquivos; em vez disso, conte as novas linhas (ou qualquer outro caractere) que representem um único arquivo. É por isso que o comando find tem simplesmente -exec echo \; e não -exec echo {} \; . Eu só quero imprimir uma única nova linha com a finalidade de contabilizar os arquivos.

    
por 23.10.2011 / 13:41
5

Supondo que você esteja procurando uma solução Linux padrão, uma maneira relativamente direta de alcançar isso é com find :

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

Em que find percorre os dois subdiretórios especificados, até -maxdepth de 1, o que impede nova recursão e apenas relata arquivos ( -type f ) separados por novas linhas. O resultado é canalizado para wc para contar o número dessas linhas.

    
por 23.10.2011 / 06:46
4

Por "sem recursão", você quer dizer que, se directoryName1 tiver subdiretórios, você não deseja contar os arquivos nos subdiretórios? Se assim for, aqui está uma maneira de contar todos os arquivos regulares nos diretórios indicados:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

Observe que o teste -f executa duas funções: testa se a entrada correspondida por uma das globs acima é um arquivo regular e testa se a entrada foi uma correspondência (se uma das globs não corresponder a nada, padrão permanece como está). Se você quiser contar todas as entradas nos diretórios fornecidos, independentemente do tipo, substitua -f por -e .

O Ksh tem uma maneira de fazer com que os padrões correspondam aos arquivos de ponto e produzir uma lista vazia, no caso de nenhum arquivo corresponder a um padrão. Então, no ksh você pode contar arquivos regulares como este:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

ou todos os arquivos simplesmente assim:

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

O Bash tem maneiras diferentes de simplificar isso. Para contar arquivos regulares:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

Para contar todos os arquivos:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

Como de costume, é ainda mais simples em zsh. Para contar arquivos regulares:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

Altere (DN.) para (DN) para contar todos os arquivos.

¹ Observe que cada padrão corresponde a si mesmo; caso contrário, os resultados podem estar desativados (por exemplo, se você estiver contando arquivos que começam com um dígito, não é possível fazer for x in [0-9]*; do if [ -f "$x" ]; then … porque pode haver um arquivo chamado %código%).

    
por 24.10.2011 / 03:04
2

Com base em um script de contagem , Shawn's answer e um truque de Bash para garantir que até mesmo nomes de arquivos com novas linhas sejam impressos em uma forma utilizável em uma única linha:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %q é imprimir uma versão entre aspas de uma string, isto é, uma string de uma única linha que você poderia colocar em um script Bash para ser interpretado como uma string literal incluindo (potencialmente) novas linhas e outros caracteres especiais. Por exemplo, veja echo -n $'\tfoo\nbar' vs printf %q $'\tfoo\nbar' .

O comando find funciona simplesmente imprimindo um único caractere para cada arquivo e depois contando-os em vez de contar linhas.

    
por 26.10.2011 / 09:38
0

Veja uma maneira "bruta-strong" de obter seu resultado usando find , echo , ls , wc , xargs e awk .

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'
    
por 23.10.2011 / 07:26
-1
for i in *; do echo $i; ls $i | wc -l; done
    
por 11.12.2014 / 14:25
-1
    for i in 'ls -1'; do echo $i : 'ls -1 $i|wc -l'; done
    
por 13.08.2015 / 18:24

Tags