Respuestas:
Este simple one-liner debería funcionar en cualquier shell, no solo bash:
ls -1q log* | wc -l
ls -1q le dará una línea por archivo, incluso si contienen espacios en blanco o caracteres especiales como líneas nuevas.
La salida se canaliza a wc -l, que cuenta el número de líneas.
ls
, ya que crea un proceso hijo. log*
es expandido por el shell, no ls
, por echo
lo que sería simple .
logs
en el directorio en cuestión, también se contará el contenido de ese directorio de registros. Esto probablemente no sea intencional.
Puede hacer esto de manera segura (es decir, no será molestado por archivos con espacios o \n
en su nombre) con bash:
$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}
Debe habilitar nullglob
para que no obtenga el literal *.log
en la $logfiles
matriz si no coinciden los archivos. (Consulte Cómo "deshacer" un 'set -x'? Para ver ejemplos de cómo restablecerlo de manera segura).
shopt -u nullglob
debe omitir si nullglob
no se ha desarmado, entonces comenzó.
*.log
con solo *
contará directorios. Si los archivos que desea enumerar tienen la convención de nomenclatura tradicional name.extension
, use *.*
.
Muchas respuestas aquí, pero algunas no tienen en cuenta
-l
)*.log
lugar delog*
logs
que coincide log*
)Aquí hay una solución que los maneja a todos:
ls 2>/dev/null -Ubad1 -- log* | wc -l
Explicación:
-U
hace ls
que no se ordenen las entradas, lo que significa que no necesita cargar todo el listado de directorios en la memoria-b
imprime escapes de estilo C para caracteres no gráficos, lo que hace que las nuevas líneas se impriman como \n
.-a
imprime todos los archivos, incluso los archivos ocultos (no es estrictamente necesario cuando el glob log*
implica que no hay archivos ocultos)-d
imprime directorios sin intentar enumerar el contenido del directorio, que es lo que ls
normalmente haría-1
se asegura de que esté en una columna (ls hace esto automáticamente cuando escribe en una tubería, por lo que no es estrictamente necesario)2>/dev/null
redirige stderr para que si hay 0 archivos de registro, ignore el mensaje de error. (Tenga en cuenta que shopt -s nullglob
, en su lugar, se ls
enumeraría todo el directorio de trabajo).wc -l
consume la lista de directorios a medida que se genera, por lo que la salida de ls
nunca está en la memoria en ningún momento.--
Los nombres de archivo se separan del comando utilizando --
para no ser entendidos como argumentos para ls
(en caso de que log*
se elimine)El shell se expandirá log*
a la lista completa de archivos, lo que puede agotar la memoria si se trata de muchos archivos, por lo que es mejor ejecutarlo a través de grep:
ls -Uba1 | grep ^log | wc -l
Este último maneja directorios de archivos extremadamente grandes sin usar mucha memoria (aunque sí usa una subshell). El -d
ya no es necesario, porque solo enumera el contenido del directorio actual.
Para una búsqueda recursiva:
find . -type f -name '*.log' -printf x | wc -c
wc -c
contará el número de caracteres en la salida de find
, mientras que -printf x
le indica find
que imprima un solo x
para cada resultado.
Para una búsqueda no recursiva, haga esto:
find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
-name '*.log'
, contará todos los archivos, que es lo que necesitaba para mi caso de uso. También la bandera -maxdepth es extremadamente útil, ¡gracias!
find
; simplemente imprima algo más que el nombre de archivo literal.
La respuesta aceptada para esta pregunta es incorrecta, pero tengo poca reputación, así que no puedo agregarle ningún comentario.
La respuesta correcta a esta pregunta la da Mat:
shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}
El problema con la respuesta aceptada es que wc -l cuenta el número de caracteres de nueva línea y los cuenta incluso si se imprimen en el terminal como '?' en la salida de 'ls -l'. Esto significa que la respuesta aceptada FALLA cuando un nombre de archivo contiene un carácter de nueva línea. He probado el comando sugerido:
ls -l log* | wc -l
e informa erróneamente un valor de 2 incluso si solo hay 1 archivo que coincide con el patrón cuyo nombre contiene un carácter de nueva línea. Por ejemplo:
touch log$'\n'def
ls log* -l | wc -l
Si tiene muchos archivos y no desea usar la shopt -s nullglob
solución de matriz elegante y bash, puede usar find y así sucesivamente siempre que no imprima el nombre del archivo (que puede contener nuevas líneas).
find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l
Esto encontrará todos los archivos que coincidan con log * y que no comiencen con .*
: "not name. *" Es redundante, pero es importante tener en cuenta que el valor predeterminado para "ls" es no mostrar archivos de puntos, pero el valor predeterminado para encontrar es incluirlos.
Esta es una respuesta correcta y maneja cualquier tipo de nombre de archivo que pueda lanzarle, porque el nombre de archivo nunca se pasa entre los comandos.
Pero, la shopt nullglob
respuesta es la mejor respuesta!
find
vs usar ls
son dos formas diferentes de resolver el problema. find
no siempre está presente en una máquina, pero ls
generalmente lo está,
find
probablemente no tiene tampoco tiene todas esas opciones elegantes ls
.
-maxdepth 1
find
hace esto por defecto. Esto puede crear confusión si uno no se da cuenta de que hay una carpeta secundaria oculta, y puede ser ventajoso usarla ls
en algunas circunstancias, que no informa los archivos ocultos de manera predeterminada.
Aquí está mi única línea para esto.
file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
set --
lo tanto, no está haciendo nada excepto prepararnos $#
, que almacena la cantidad de argumentos de la línea de comandos que se pasaron al programa de shell
(no hay suficiente reputación para comentar)
Esto es BUGGY :
ls -1q some_pattern | wc -l
Si shopt -s nullglob
se configura, imprime el número de TODOS los archivos regulares, no solo los que tienen el patrón (probado en CentOS-8 y Cygwin). ¿Quién sabe qué otros errores sin sentido ls
tiene?
Esto es CORRECTO y mucho más rápido:
shopt -s nullglob; files=(some_pattern); echo ${#files[@]};
Hace el trabajo esperado.
0.006
en CentOS y 0.083
en Cygwin (en caso de que se use con cuidado).
0.000
en CentOS y 0.003
en Cygwin.
Puede definir dicho comando fácilmente, utilizando una función de shell. Este método no requiere ningún programa externo y no genera ningún proceso secundario. No intenta el ls
análisis peligroso y maneja caracteres "especiales" (espacios en blanco, líneas nuevas, barras invertidas, etc.) muy bien. Solo se basa en el mecanismo de expansión de nombre de archivo proporcionado por el shell. Es compatible con al menos sh, bash y zsh.
La siguiente línea define una función llamada count
que imprime el número de argumentos con los que se ha llamado.
count() { echo $#; }
Simplemente llámelo con el patrón deseado:
count log*
Para que el resultado sea correcto cuando el patrón global no tiene coincidencia, la opción de shell nullglob
(o failglob
, que es el comportamiento predeterminado en zsh) debe establecerse en el momento en que ocurre la expansión. Se puede configurar así:
shopt -s nullglob # for sh / bash
setopt nullglob # for zsh
Dependiendo de lo que quiera contar, también podría estar interesado en la opción de shell dotglob
.
Desafortunadamente, con bash al menos, no es fácil establecer estas opciones localmente. Si no desea configurarlos globalmente, la solución más sencilla es utilizar la función de esta manera más complicada:
( shopt -s nullglob ; shopt -u failglob ; count log* )
Si desea recuperar la sintaxis ligera count log*
, o si realmente desea evitar generar una subshell, puede hackear algo en la línea de:
# 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 beneficio adicional, esta función es de uso más general. Por ejemplo:
count a* b* # count files which match either a* or b*
count $(jobs -ps) # count stopped jobs (sh / bash)
Al convertir la función en un archivo de script (o un programa C equivalente), invocable desde la RUTA, también se puede componer con programas como find
y xargs
:
find "$FIND_OPTIONS" -exec count {} \+ # count results of a search
He pensado mucho en esta respuesta, especialmente teniendo en cuenta las cosas de no analizar . Al principio intenté
<¡ADVERTENCIA! NO FUNCIONÓ>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ ¡ADVERTENCIA! NO FUNCIONÓ>
que funcionó si solo hubiera un nombre de archivo como
touch $'w\nlf.aa'
pero falló si hice un nombre de archivo como este
touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'
Finalmente se me ocurrió lo que estoy poniendo a continuación. Tenga en cuenta que estaba tratando de obtener un recuento de todos los archivos en el directorio (sin incluir ningún subdirectorio). Creo que, junto con las respuestas de @Mat y @Dan_Yard, además de tener al menos la mayoría de los requisitos establecidos por @mogsie (no estoy seguro de la memoria). Creo que la respuesta de @mogsie es correcta, pero siempre trato de evitar el análisis a ls
menos que sea una situación extremadamente específica.
awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'
Más legible:
awk -F"\0" '{print NF-1}' < \
<(find . -maxdepth 1 -type f -print0) | \
awk '{sum+=$1}END{print sum}'
Esto está haciendo una búsqueda específica para archivos, delimitando la salida con un carácter nulo (para evitar problemas con espacios y saltos de línea), luego contando el número de caracteres nulos. El número de archivos será uno menos que el número de caracteres nulos, ya que habrá un carácter nulo al final.
Para responder la pregunta del OP, hay dos casos a considerar
1) Búsqueda no recursiva:
awk -F"\0" '{print NF-1}' < \
<(find . -maxdepth 1 -type f -name "log*" -print0) | \
awk '{sum+=$1}END{print sum}'
2) Búsqueda recursiva. Tenga en cuenta que lo que hay dentro del -name
parámetro puede necesitar ser cambiado para un comportamiento ligeramente diferente (archivos ocultos, etc.).
awk -F"\0" '{print NF-1}' < \
<(find . -type f -name "log*" -print0) | \
awk '{sum+=$1}END{print sum}'
Si a alguien le gustaría comentar cómo se comparan estas respuestas con las que he mencionado en esta respuesta, por favor hágalo.
Tenga en cuenta que llegué a este proceso de pensamiento al obtener esta respuesta .
Esto es lo que siempre hago:
ls log * | awk 'END {print NR}'
awk 'END{print NR}'
debe ser equivalente a wc -l
.
ls -1 log* | wc -l
Lo que significa listar un archivo por línea y luego canalizarlo al comando de recuento de palabras con cambio de parámetro a líneas de recuento.
-l
, ya que eso requierestat(2)
en cada archivo y con el propósito de contar no agrega nada.