Respostas:
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.
ls
, pois cria um processo filho. log*
é expandido pelo shell, não ls
, então um simples echo
seria.
logs
no diretório em questão, o conteúdo desse diretório de logs também será contado. Provavelmente isso não é intencional.
Você pode fazer isso com segurança (ou seja, não será incomodado por arquivos com espaços ou \n
em seus nomes) com o bash:
$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}
É necessário ativar nullglob
para que você não obtenha o literal *.log
na $logfiles
matriz se nenhum arquivo corresponder. (Consulte Como "desfazer" um 'set -x'? Para exemplos de como redefini-lo com segurança.)
shopt -u nullglob
deve ser ignorada se nullglob
não estiver definida, então você começou.
*.log
por apenas *
contará diretórios. Se os arquivos que você deseja enumerar tiverem a convenção de nomenclatura tradicional name.extension
, use *.*
.
Muitas respostas aqui, mas algumas não levam em consideração
-l
)*.log
vez delog*
logs
que corresponde log*
)Aqui está uma solução que lida com todos eles:
ls 2>/dev/null -Ubad1 -- log* | wc -l
Explicação:
-U
faz ls
com que não classifique as entradas, o que significa que não precisa carregar toda a lista de diretórios na memória-b
imprime escapes no estilo C para caracteres não gráficos, fazendo com que novas linhas sejam impressas como \n
.-a
imprime todos os arquivos, mesmo arquivos ocultos (não é estritamente necessário quando a glob log*
não implica arquivos ocultos)-d
imprime diretórios sem tentar listar o conteúdo do diretório, o que ls
normalmente 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/null
redireciona o stderr para que, se houver 0 arquivos de log, ignore a mensagem de erro. (Observe que isso shopt -s nullglob
faria com ls
que listasse todo o diretório de trabalho.)wc -l
consome a listagem de diretórios enquanto ela está sendo gerada, portanto a saída de ls
nunca 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 -d
não é mais necessário, porque está listando apenas o conteúdo do diretório atual.
Para uma pesquisa recursiva:
find . -type f -name '*.log' -printf x | wc -c
wc -c
contará o número de caracteres na saída de find
, enquanto -printf x
informa find
para imprimir um único x
para cada resultado.
Para uma pesquisa não recursiva, faça o seguinte:
find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
-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!
find
; basta imprimir algo diferente do nome do arquivo literal.
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
Se você possui muitos arquivos e não deseja usar a shopt -s nullglob
soluçã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 nullglob
resposta é a melhor resposta!
find
vs usar ls
são duas maneiras diferentes de resolver o problema. find
nem sempre está presente em uma máquina, mas ls
normalmente é,
find
provavelmente também não tem todas essas opções sofisticadas ls
.
-maxdepth 1
find
faz 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 ls
em algumas circunstâncias, que não relatam arquivos ocultos por padrão.
Aqui está o meu forro para isso.
file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
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
(reputação insuficiente para comentar)
Este é BUGGY :
ls -1q some_pattern | wc -l
Se shopt -s nullglob
estiver 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 ls
têm?
Isso é CORRETO e muito mais rápido:
shopt -s nullglob; files=(some_pattern); echo ${#files[@]};
Faz o trabalho esperado.
0.006
no CentOS e 0.083
no Cygwin (caso seja usado com cuidado).
0.000
no CentOS e 0.003
no Cygwin.
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 ls
aná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 count
que 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 find
e xargs
:
find "$FIND_OPTIONS" -exec count {} \+ # count results of a search
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 ls
menos 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 -name
parâ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 .
Aqui está o que eu sempre faço:
ls log * | awk 'END {print NR}'
awk 'END{print NR}'
deve ser equivalente a wc -l
.
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.
-l
, uma vez que isso exigestat(2)
em cada arquivo e, para fins de contagem, não acrescenta nada.