Existe um comando bash que conta arquivos?

182

Existe um comando bash que conta o número de arquivos que correspondem a um padrão?

Por exemplo, quero obter a contagem de todos os arquivos em um diretório que corresponda a esse padrão: log*

hudi
fonte

Respostas:

243

Este simples liner deve funcionar em qualquer shell, não apenas no bash:

ls -1q log* | wc -l

ls -1q fornecerá uma linha por arquivo, mesmo que contenham espaços em branco ou caracteres especiais, como novas linhas.

A saída é canalizada para wc -l, que conta o número de linhas.

Daniel
fonte
10
Eu não usaria -l, uma vez que isso exige stat(2)em cada arquivo e, para fins de contagem, não acrescenta nada.
Camh
12
Eu não usaria ls, pois cria um processo filho. log*é expandido pelo shell, não ls, então um simples echoseria.
cdarke
2
Exceto que um eco não funcionará se você tiver nomes de arquivos com espaços ou caracteres especiais.
Daniel
4
@ WalterTross Isso é verdade (não que a eficiência fosse um requisito da pergunta original). Também acabei de descobrir que -q cuida de arquivos com novas linhas, mesmo quando a saída não é o terminal. E esses sinalizadores são suportados por todas as plataformas e shells em que testei. Atualizando a resposta, obrigado a você e camh pela entrada!
Daniel
3
Se houver um diretório chamado logsno diretório em questão, o conteúdo desse diretório de logs também será contado. Provavelmente isso não é intencional.
Mogie8 de
54

Você pode fazer isso com segurança (ou seja, não será incomodado por arquivos com espaços ou \nem seus nomes) com o bash:

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

É necessário ativar nullglobpara que você não obtenha o literal *.logna $logfiles matriz se nenhum arquivo corresponder. (Consulte Como "desfazer" um 'set -x'? Para exemplos de como redefini-lo com segurança.)

Esteira
fonte
2
Talvez explicitamente salientar que este é um Bash- única resposta, especialmente para os novos visitantes que ainda não estão totalmente até a velocidade sobre a Diferença entre sh e do bash
tripleee
Além disso, a final shopt -u nullglobdeve ser ignorada se nullglobnão estiver definida, então você começou.
Tripleee 01/09/19
Nota: Substituir *.logpor apenas *contará diretórios. Se os arquivos que você deseja enumerar tiverem a convenção de nomenclatura tradicional name.extension, use *.*.
AlainD
52

Muitas respostas aqui, mas algumas não levam em consideração

  • nomes de arquivos com espaços, novas linhas ou caracteres de controle neles
  • nomes de arquivos que começam com hífens (imagine um arquivo chamado -l)
  • arquivos ocultos, que começam com um ponto (se o globo estivesse em *.logvez delog*
  • diretórios que correspondem ao glob (por exemplo, um diretório chamado logsque corresponde log*)
  • diretórios vazios (ou seja, o resultado é 0)
  • diretórios extremamente grandes (listar todos eles pode esgotar a memória)

Aqui está uma solução que lida com todos eles:

ls 2>/dev/null -Ubad1 -- log* | wc -l

Explicação:

  • -Ufaz lscom que não classifique as entradas, o que significa que não precisa carregar toda a lista de diretórios na memória
  • -bimprime escapes no estilo C para caracteres não gráficos, fazendo com que novas linhas sejam impressas como \n.
  • -aimprime todos os arquivos, mesmo arquivos ocultos (não é estritamente necessário quando a glob log*não implica arquivos ocultos)
  • -dimprime diretórios sem tentar listar o conteúdo do diretório, o que lsnormalmente faria
  • -1 garante que ele esteja em uma coluna (o ls faz isso automaticamente ao gravar em um pipe, por isso não é estritamente necessário)
  • 2>/dev/nullredireciona o stderr para que, se houver 0 arquivos de log, ignore a mensagem de erro. (Observe que isso shopt -s nullglobfaria com lsque listasse todo o diretório de trabalho.)
  • wc -lconsome a listagem de diretórios enquanto ela está sendo gerada, portanto a saída de lsnunca fica na memória em nenhum momento.
  • --Os nomes dos arquivos são separados do comando usando-os --para não serem entendidos como argumentos para ls(caso log*seja removido)

O shell se expandirá log*para a lista completa de arquivos, o que pode esgotar a memória se houver muitos arquivos; portanto, executá-lo no grep é melhor:

ls -Uba1 | grep ^log | wc -l

Este último lida com diretórios extremamente grandes de arquivos sem usar muita memória (embora use um subshell). O -dnão é mais necessário, porque está listando apenas o conteúdo do diretório atual.

mogsie
fonte
48

Para uma pesquisa recursiva:

find . -type f -name '*.log' -printf x | wc -c

wc -ccontará o número de caracteres na saída de find, enquanto -printf xinforma findpara imprimir um único xpara cada resultado.

Para uma pesquisa não recursiva, faça o seguinte:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
Will Vousden
fonte
6
Mesmo se você não tiver arquivos com espaços, algum outro usuário do seu script poderá encontrar um arquivo nomeado com códigos maliciosos, causando falha nos scripts. Além disso, outras pessoas que encontrarem isso no StackOverflow podem ter arquivos com novas linhas e precisam conhecer as armadilhas.
Mogie22 /
Para sua informação, se você simplesmente deixar de fora -name '*.log', ele contará todos os arquivos, que é o que eu precisava para o meu caso de uso. Além disso, o sinalizador -maxdepth é extremamente útil, obrigado!
starmandeluxe
2
Isso ainda produz resultados incorretos se houver nomes de arquivos com novas linhas. A solução alternativa é fácil com find; basta imprimir algo diferente do nome do arquivo literal.
Tripleee 01/09/19
8

A resposta aceita para esta pergunta está errada, mas eu tenho um representante baixo, portanto não consigo adicionar um comentário.

A resposta correta para esta pergunta é dada por Mat:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

O problema com a resposta aceita é que wc -l conta o número de caracteres de nova linha e os conta mesmo se eles imprimirem no terminal como '?' na saída de 'ls -l'. Isso significa que a resposta aceita FALHA quando um nome de arquivo contém um caractere de nova linha. Eu testei o comando sugerido:

ls -l log* | wc -l

e informa erroneamente um valor 2, mesmo que exista apenas 1 arquivo correspondente ao padrão cujo nome contenha um caractere de nova linha. Por exemplo:

touch log$'\n'def
ls log* -l | wc -l
Dan Yard
fonte
6

Se você possui muitos arquivos e não deseja usar a shopt -s nullglobsolução elegante e básica, pode usar find e assim por diante, desde que não imprima o nome do arquivo (que pode conter novas linhas).

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

Ele encontrará todos os arquivos que correspondem ao log * e que não começam com .*- O "não nome. *" É redunante, mas é importante observar que o padrão para "ls" é não mostrar arquivos de ponto, mas o padrão pois encontrar é incluí-los.

Esta é uma resposta correta e lida com qualquer tipo de nome de arquivo que você possa fornecer, porque o nome do arquivo nunca é passado entre os comandos.

Mas, a shopt nullglobresposta é a melhor resposta!

mogsie
fonte
Você provavelmente deve atualizar sua resposta original em vez de responder novamente.
Qodeninja
Eu acho que usar findvs usar lssão duas maneiras diferentes de resolver o problema. findnem sempre está presente em uma máquina, mas lsnormalmente é,
mogsie
2
Mas então uma caixa de banha de porco que não tem findprovavelmente também não tem todas essas opções sofisticadas ls.
Tripleee
1
Note-se também como isso se estende a uma árvore de diretórios inteira se você tirar o-maxdepth 1
tripleee
1
Observe que esta solução contará arquivos dentro de diretórios ocultos em sua contagem. findfaz isso por padrão. Isso pode criar confusão se você não perceber que há uma pasta filho oculta e pode tornar vantajoso o uso lsem algumas circunstâncias, que não relatam arquivos ocultos por padrão.
MrPotatoHead 12/02/19
6

Aqui está o meu forro para isso.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
zee
fonte
Levei algum tempo para entender, mas isso é legal! Portanto, set -- não está fazendo nada, exceto nos preparando $#, que armazena o número de argumentos da linha de comando que foram passados ​​para o programa shell
xverges
@xverges Sim, "shopt -s nullglob" é para não contar arquivos ocultos (.files). set - é para armazenar / definir o número de parâmetros posicionais (número de arquivos, neste caso). e # $ para exibir o número de parâmetros posicionais (contagem de arquivos).
zee
3

Você pode usar a opção -R para encontrar os arquivos junto com aqueles dentro dos diretórios recursivos

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log

você pode usar padrões no grep

Moh .S
fonte
3

Um comentário importante

(reputação insuficiente para comentar)

Este é BUGGY :

ls -1q some_pattern | wc -l

Se shopt -s nullglobestiver definido, ele imprime o número de TODOS os arquivos regulares, não apenas aqueles com o padrão (testado no CentOS-8 e Cygwin). Quem sabe o que outros bugs sem sentido lstêm?

Isso é CORRETO e muito mais rápido:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

Faz o trabalho esperado.


E os tempos de execução são diferentes.
O primeiro: 0.006no CentOS e 0.083no Cygwin (caso seja usado com cuidado).
O segundo: 0.000no CentOS e 0.003no Cygwin.

Menino pequeno
fonte
2

Você pode definir esse comando facilmente, usando uma função shell. Este método não requer nenhum programa externo e não gera nenhum processo filho. Ele não tenta a lsanálise perigosa e manipula caracteres “especiais” (espaços em branco, novas linhas, barras invertidas e assim por diante). Ele depende apenas do mecanismo de expansão de nome de arquivo fornecido pelo shell. É compatível com pelo menos sh, bash e zsh.

A linha abaixo define uma função chamada countque imprime o número de argumentos com os quais foi chamada.

count() { echo $#; }

Basta chamá-lo com o padrão desejado:

count log*

Para que o resultado seja correto quando o padrão de globbing não corresponder, a opção de shell nullglob(ou failglob- que é o comportamento padrão no zsh) deve ser configurada no momento em que a expansão ocorrer. Pode ser definido assim:

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

Dependendo do que você deseja contar, você também pode estar interessado na opção de shell dotglob.

Infelizmente, pelo menos com o bash, não é fácil definir essas opções localmente. Se você não deseja defini-los globalmente, a solução mais direta é usar a função desta maneira mais complicada:

( shopt -s nullglob ; shopt -u failglob ; count log* )

Se você deseja recuperar a sintaxe leve count log*, ou se realmente deseja evitar a geração de um subshell, pode hackear algo como:

# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
    eval "$_count_saved_shopts"
    unset _count_saved_shopts
    echo $#
}
alias count='
    _count_saved_shopts="$(shopt -p nullglob failglob)"
    shopt -s nullglob
    shopt -u failglob
    count'

Como um bônus, esta função é de uso mais geral. Por exemplo:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

Ao transformar a função em um arquivo de script (ou um programa C equivalente), que pode ser chamado pelo PATH, também pode ser composta por programas como finde xargs:

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search
Maëlan
fonte
2

Pensei bastante nessa resposta, especialmente considerando as coisas que não analisamos . No começo, eu tentei

<AVISO! NÃO FUNCIONA>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ ATENÇÃO! NÃO FUNCIONA>

que funcionava se houvesse apenas um nome de arquivo como

touch $'w\nlf.aa'

mas falhei se eu fizesse um nome de arquivo como este

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

Eu finalmente inventei o que estou colocando abaixo. Observe que eu estava tentando obter uma contagem de todos os arquivos no diretório (sem incluir nenhum subdiretório). Acho que, junto com as respostas de @Mat e @Dan_Yard, além de ter pelo menos a maioria dos requisitos estabelecidos por @mogsie (não tenho certeza sobre a memória). Acho que a resposta de @mogsie está correta, mas sempre tento me afastar da análise, a lsmenos que seja uma situação extremamente específica.

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

Mais facilmente:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

Isso é uma descoberta específica para arquivos, delimitando a saída com um caractere nulo (para evitar problemas com espaços e alimentações de linha) e depois contando o número de caracteres nulos. O número de arquivos será um a menos que o número de caracteres nulos, pois haverá um caractere nulo no final.

Para responder à pergunta do OP, há dois casos a considerar

1) Pesquisa não recursiva:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2) Pesquisa recursiva. Observe que o conteúdo do -nameparâmetro pode precisar ser alterado para um comportamento ligeiramente diferente (arquivos ocultos etc.).

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

Se alguém quiser comentar sobre como essas respostas se comparam às que eu mencionei nesta resposta, faça.


Observe que cheguei a esse processo de pensamento ao obter esta resposta .

bballdave025
fonte
1

Aqui está o que eu sempre faço:

ls log * | awk 'END {print NR}'

Shuang Liang
fonte
awk 'END{print NR}'deve ser equivalente a wc -l.
musiphil 28/02
0
ls -1 log* | wc -l

O que significa listar um arquivo por linha e canalizá-lo para o comando de contagem de palavras com a alternância de parâmetros para as linhas de contagem.

nudzo
fonte
A opção "-1" não é necessária ao canalizar a saída ls. Mas você pode ocultar a mensagem de erro ls se nenhum arquivo corresponder ao padrão. Eu sugiro "ls log * 2> / dev / null | wc -l".
JohnMudd
A discussão sob a resposta de Daniel também é relevante aqui. Isso funciona bem quando você não possui diretórios correspondentes ou nomes de arquivos com novas linhas, mas uma boa resposta deve ao menos apontar essas condições de contorno e uma ótima resposta não deve tê-las. Muitos bugs ocorrem porque alguém copia / cola código que não entende; portanto, apontar as falhas pelo menos ajuda a entender o que observar. (Com certeza, muitos mais erros acontecem porque eles ignoraram as advertências e depois as coisas mudaram depois que pensou que o código foi provavelmente bom o suficiente para o seu propósito.)
tripleee
-1

Para contar tudo, apenas coloque sl na linha de contagem de palavras:

ls | wc -l

Para contar com o padrão, canalize para grep primeiro:

ls | grep log | wc -l
jturi
fonte