Quero saber quantos arquivos regulares têm a extensão .c
em uma estrutura de diretórios grande e complexa e também quantos diretórios esses arquivos estão espalhados. A saída que eu quero é apenas esses dois números.
Vi essa pergunta sobre como obter o número de arquivos, mas também preciso saber o número de diretórios em que os arquivos estão.
- Meus nomes de arquivos (incluindo diretórios) podem ter caracteres; eles podem começar com
.
ou-
e ter espaços ou novas linhas. - Talvez eu tenha alguns links simbólicos cujos nomes terminem com
.c
e links simbólicos para diretórios. Não quero que links simbólicos sejam seguidos ou contados, ou pelo menos quero saber se e quando eles estão sendo contados. - A estrutura de diretórios possui muitos níveis e o diretório de nível superior (o diretório de trabalho) possui pelo menos um
.c
arquivo.
Escrevi rapidamente alguns comandos no shell (Bash) para contá-los, mas não acho que o resultado seja preciso ...
shopt -s dotglob
shopt -s globstar
mkdir out
for d in **/; do
find "$d" -maxdepth 1 -type f -name "*.c" >> out/$(basename "$d")
done
ls -1Aq out | wc -l
cat out/* | wc -l
Isso gera reclamações sobre redirecionamentos ambíguos, perde arquivos no diretório atual e ativa caracteres especiais (por exemplo, a saída redirecionada find
imprime novas linhas nos nomes de arquivos ) e grava um monte de arquivos vazios (oops).
Como enumerar meus .c
arquivos de maneira confiável e os diretórios que os contêm?
Caso isso ajude, aqui estão alguns comandos para criar uma estrutura de teste com nomes incorretos e links simbólicos:
mkdir -p cfiles/{1..3}/{a..b} && cd cfiles
mkdir space\ d
touch -- i.c -.c bad\ .c 'terrible
.c' not-c .hidden.c
for d in space\ d 1 2 2/{a..b} 3/b; do cp -t "$d" -- *.c; done
ln -s 2 dirlink
ln -s 3/b/i.c filelink.c
Na estrutura resultante, 7 diretórios contêm .c
arquivos e 29 arquivos regulares terminam com .c
(se dotglob
estiver desativado quando os comandos forem executados) (se eu tiver errado a conta, informe-me). Estes são os números que eu quero.
Por favor, sinta-se livre para não usar este teste específico.
NB: Respostas em qualquer shell ou outro idioma serão testadas e apreciadas por mim. Se eu tiver que instalar novos pacotes, não há problema. Se você conhece uma solução GUI, recomendo que você compartilhe (mas talvez não vá tão longe quanto instalar um DE inteiro para testá-lo) :) Eu uso o Ubuntu MATE 17.10.
Respostas:
Não examinei a saída com links simbólicos, mas:
find
comando imprime o nome do diretório de cada.c
arquivo encontrado.sort | uniq -c
will nos fornece quantos arquivos existem em cada diretório (o quesort
pode ser desnecessário aqui, não tenho certeza)sed
, substituo o nome do diretório por1
, eliminando todos os caracteres estranhos possíveis, apenas com a contagem e o1
restantetr
d
aqui é essencialmente o mesmo queNR
. Eu poderia ter omitido a inserção1
nosed
comando e apenas impressoNR
aqui, mas acho que isso é um pouco mais claro.Até o momento
tr
, os dados são delimitados por NUL, seguros contra todos os nomes de arquivos válidos.Com zsh e bash, você pode usar
printf %q
para obter uma string entre aspas, que não teria novas linhas. Portanto, você pode fazer algo como:No entanto, embora
**
não deva se expandir para links simbólicos para diretórios , não consegui obter a saída desejada no bash 4.4.18 (1) (Ubuntu 16.04).Mas o zsh funcionou bem, e o comando pode ser simplificado:
D
permite que este glob selecione arquivos de ponto,.
selecione arquivos regulares (portanto, não links simbólicos) e:h
imprima apenas o caminho do diretório e não o nome do arquivo (comofind
os%h
) (consulte as seções em Geração e modificadores de nome de arquivo ). Portanto, com o comando awk, precisamos apenas contar o número de diretórios exclusivos que aparecem e o número de linhas é a contagem de arquivos.fonte
29 7
. Se eu adicionar-L
afind
, que vai até41 10
. Qual saída você precisa?O Python possui
os.walk
, o que torna tarefas como essa fáceis, intuitivas e automaticamente robustas, mesmo diante de nomes de arquivos estranhos, como aqueles que contêm caracteres de nova linha. Este script Python 3, que eu tinha originalmente publicado no chat , se destina a ser executado no diretório atual (mas ele não tem que ser localizado no diretório atual, e você pode mudar o caminho que ele passa aos.walk
):Isso imprime a contagem de diretórios que contêm diretamente pelo menos um arquivo cujo nome termina em
.c
, seguido por um espaço, seguido pela contagem de arquivos cujos nomes terminam em.c
. Arquivos "ocultos" - ou seja, arquivos cujos nomes começam com -.
são incluídos e os diretórios ocultos são atravessados de maneira semelhante.os.walk
percorre recursivamente uma hierarquia de diretórios. Ele enumera todos os diretórios que são recursivamente acessíveis a partir do ponto inicial que você fornece, fornecendo informações sobre cada um deles como uma tupla de três valoresroot, dirs, files
. Para cada diretório para o qual ele acessa (incluindo o primeiro cujo nome você deu):root
mantém o nome do caminho desse diretório. Note-se que este é totalmente alheios ao "diretório raiz" do sistema/
(e também alheios a/root
) embora seria ir para aqueles se você começar por aí. Nesse caso,root
inicia no caminho.
- ou seja, o diretório atual - e vai para todo lugar abaixo dele.dirs
contém uma lista dos nomes de caminho de todos os subdiretórios do diretório cujo nome está atualmente emroot
.files
mantém uma lista dos nomes de caminho de todos os arquivos que residem no diretório cujo nome está atualmente armazenado,root
mas que não são os próprios diretórios. Observe que isso inclui outros tipos de arquivos que não os regulares, incluindo links simbólicos, mas parece que você não espera que essas entradas terminem.c
e está interessado em ver o que faz.Nesse caso, só preciso examinar o terceiro elemento da tupla
files
(que chamofs
no script). Como ofind
comando, o Pythonos.walk
atravessa subdiretórios para mim; a única coisa que tenho para me inspecionar são os nomes dos arquivos que cada um deles contém. Ao contrário dofind
comando, no entanto,os.walk
fornece-me automaticamente uma lista desses nomes de arquivos.Esse script não segue links simbólicos. Você provavelmente não deseja que os links simbólicos sejam seguidos para essa operação, porque eles podem formar ciclos e, mesmo que não haja ciclos, os mesmos arquivos e diretórios podem ser percorridos e contados várias vezes, se estiverem acessíveis através de links simbólicos diferentes.
Se você já quis
os.walk
seguir links simbólicos - o que normalmente não faria -, pode passarfollowlinks=true
para ele. Ou seja, em vez de escrever,os.walk('.')
você poderia escreveros.walk('.', followlinks=true)
. Reitero que você raramente desejaria isso, especialmente para uma tarefa como essa, na qual recursivamente enumera toda uma estrutura de diretórios, não importa o tamanho, e conte todos os arquivos nela que atendem a algum requisito.fonte
Encontre + Perl:
Explicação
O
find
comando encontrará todos os arquivos regulares (sem links simbólicos ou diretórios) e, em seguida, imprimirá o nome do diretório em que estão (%h
) seguido por\0
.perl -0 -ne
: leia a entrada linha por linha (-n
) e aplique o script fornecido por-e
cada linha. A-0
define o separador de linha de entrada para\0
que possamos ler a entrada delimitada por nulo.$k{$_}++
:$_
é uma variável especial que aceita o valor da linha atual. Isso é usado como uma chave para o hash%k
, cujos valores são o número de vezes que cada linha de entrada (nome do diretório) foi vista.}{
: esta é uma maneira abreviada de escreverEND{}
. Qualquer comando após o}{
será executado uma vez, depois que toda a entrada tiver sido processada.print scalar keys %k, " $.\n"
:keys %k
retorna uma matriz das chaves no hash%k
.scalar keys %k
fornece o número de elementos nessa matriz, o número de diretórios vistos. Isso é impresso junto com o valor atual de$.
, uma variável especial que mantém o número da linha de entrada atual. Como isso é executado no final, o número da linha de entrada atual será o número da última linha, portanto, o número de linhas vistas até agora.Você pode expandir o comando perl para isso, para maior clareza:
fonte
Aqui está a minha sugestão:
Esse script curto cria um arquivo temporário, localiza todos os arquivos no diretório atual que termina em
.c
e grava a lista no arquivo temporário.grep
é então usado para contar os arquivos (a seguir: Como posso obter uma contagem de arquivos em um diretório usando a linha de comando? ) duas vezes: Na segunda vez, os diretórios listados várias vezes são removidossort -u
após a remoção de nomes de arquivos de cada linha usandosed
.Isso também funciona corretamente com novas linhas nos nomes de arquivos:
grep -c /
conta apenas linhas com uma barra e, portanto, considera apenas a primeira linha de um nome de arquivo com várias linhas na lista.Resultado
fonte
Shellscript pequeno
Sugiro um pequeno shellscript do bash com duas linhas de comando principais (e uma variável
filetype
para facilitar a alternância para procurar outros tipos de arquivo).Ele não procura ou nos links simbólicos, apenas arquivos regulares.
Shellscript detalhado
Esta é uma versão mais detalhada que também considera links simbólicos,
Saída de teste
Do shellscript curto:
Do shellscript detalhado:
fonte
Um liner Perl simples:
Ou mais simples com o
find
comando:Se você gosta de golfe e tem Perl recente (com menos de uma década):
fonte
Considere usar o
locate
comando que é muito mais rápido que ofind
comando.Executando dados de teste
Obrigado a Muru por sua resposta para me ajudar a remover links simbólicos da contagem de arquivos na resposta Unix e Linux .
Agradeço a Terdon por sua resposta
$PWD
(não direcionada a mim) na resposta Unix e Linux .Resposta original abaixo referenciada por comentários
Forma curta:
sudo updatedb
Atualize o banco de dados usado pelolocate
comando se os.c
arquivos foram criados hoje ou se você excluiu.c
arquivos hoje.locate -cr "$PWD.*\.c$"
localize todos os.c
arquivos no diretório atual e seus filhos ($PWD
). Em vez de imprimir nomes de arquivos, imprima e conte com-c
argumento. Or
especifica regex em vez da*pattern*
correspondência padrão, que pode gerar muitos resultados.locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
. Localize todos os*.c
arquivos no diretório atual e abaixo. Remova o nome do arquivosed
deixando apenas o nome do diretório. Conte o número de arquivos em cada diretório usandouniq -c
. Conte o número de diretórios comwc -l
.Comece no diretório atual com uma linha
Observe como a contagem de arquivos e o diretório foram alterados. Acredito que todos os usuários tenham o
/usr/src
diretório e possam executar comandos acima com contagens diferentes, dependendo do número de kernels instalados.Forma longa:
O formulário longo inclui o tempo para que você possa ver quanto tempo mais rápido
locate
acaboufind
. Mesmo se você precisar executá-sudo updatedb
lo, é muitas vezes mais rápido que um únicofind /
.Nota: São todos os arquivos em TODAS as unidades e partições. ou seja, também podemos procurar comandos do Windows:
Eu tenho três partições NTFS do Windows 10 montadas automaticamente
/etc/fstab
. Esteja ciente de localizar sabe tudo!Contagem interessante:
Leva 15 segundos para contar 1.637.135 arquivos em 286.705 diretórios. YMMV.
Para uma análise detalhada
locate
do manuseio de expressões regulares do comando (parece não ser necessário nesta seção de perguntas e respostas, mas usada apenas para o caso), leia o seguinte: Use "localizar" em algum diretório específico?Leitura adicional de artigos recentes:
fonte
.c
(observe que ele será quebrado se houver um arquivo nomeado-.c
no diretório atual, pois você não está citando*.c
) e, em seguida, imprimirá todos os diretórios no sistema, independentemente de eles conterem arquivos .c.~/my_c_progs/*.c
. Está contando 638 diretórios com.c
programas, o total de diretórios é mostrado posteriormente como286,705
. Vou revisar a resposta para aspas duplas `" * .c ". Obrigado pela dica.locate -r "/path/to/dir/.*\.c$"
, mas isso não é mencionado em nenhum lugar da sua resposta. Você fornece apenas um link para outra resposta que mencione isso, mas sem explicação de como adaptá-lo para responder à pergunta que está sendo feita aqui. Toda a sua resposta está focada em como contar o número total de arquivos e diretórios no sistema, o que não é relevante para a pergunta: "como posso contar o número de arquivos .c e o número de diretórios que contêm". arquivos c em um diretório específico ". Além disso, seus números estão errados, tente no exemplo no OP.$PWD
variável: unix.stackexchange.com/a/188191/200094$PWD
não contenha caracteres que talvez especial em um regex