Quiero saber cuántos archivos regulares tienen la extensión .c
en una estructura de directorio grande y compleja, y también cuántos directorios se distribuyen entre estos archivos. La salida que quiero es solo esos dos números.
He visto esta pregunta sobre cómo obtener la cantidad de archivos, pero también necesito saber la cantidad de directorios en los que están los archivos.
- Mis nombres de archivo (incluidos los directorios) pueden tener caracteres; pueden comenzar con
.
o-
y tener espacios o líneas nuevas. - Podría tener algunos enlaces simbólicos cuyos nombres terminan en
.c
, y enlaces simbólicos a directorios. No quiero que se sigan o cuenten los enlaces simbólicos, o al menos quiero saber si se están contando y cuándo. - La estructura de directorios tiene muchos niveles y el directorio de nivel superior (el directorio de trabajo) tiene al menos un
.c
archivo.
Escribí rápidamente algunos comandos en el shell (Bash) para contarlos yo mismo, pero no creo que el resultado sea exacto ...
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
Esto genera quejas sobre redirecciones ambiguas, pierde archivos en el directorio actual y se tropeza con caracteres especiales (por ejemplo, la salida redirigida find
imprime nuevas líneas en los nombres de archivo ) y escribe un montón de archivos vacíos (Uy).
¿Cómo puedo enumerar de manera confiable mis .c
archivos y sus directorios que contienen?
En caso de que ayude, aquí hay algunos comandos para crear una estructura de prueba con nombres incorrectos y enlaces 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
En la estructura resultante, 7 directorios contienen .c
archivos y 29 archivos regulares terminan con .c
(si dotglob
está desactivado cuando se ejecutan los comandos) (si he contado mal, hágamelo saber). Estos son los números que quiero.
Por favor, siéntase libre de no usar esta prueba en particular.
NB: Las respuestas en cualquier shell u otro idioma serán probadas y apreciadas por mí. Si tengo que instalar nuevos paquetes, no hay problema. Si conoces una solución GUI, te animo a compartir (pero podría no ir tan lejos como para instalar un DE completo para probarlo) :) Uso Ubuntu MATE 17.10.
Respuestas:
No he examinado la salida con enlaces simbólicos pero:
find
comando imprime el nombre del directorio de cada.c
archivo que encuentra.sort | uniq -c
nos dará cuántos archivos hay en cada directorio (sort
puede ser innecesario aquí, no estoy seguro)sed
, reemplazo el nombre del directorio con1
, eliminando así todos los posibles caracteres extraños, solo con el recuento y el1
restotr
d
aquí es esencialmente lo mismo queNR
. Podría haber omitido la inserción1
en elsed
comando e imprimirloNR
aquí, pero creo que esto es un poco más claro.Hasta el momento
tr
, los datos están delimitados por NUL, a salvo de todos los nombres de archivo válidos.Con zsh y bash, puede usar
printf %q
para obtener una cadena entre comillas, que no tendría nuevas líneas. Entonces, podrías hacer algo como:Sin embargo, aunque
**
no se supone que se expanda para enlaces simbólicos a directorios , no pude obtener el resultado deseado en bash 4.4.18 (1) (Ubuntu 16.04).Pero zsh funcionó bien, y el comando se puede simplificar:
D
habilita este globo para seleccionar archivos de puntos,.
selecciona archivos regulares (por lo tanto, no enlaces simbólicos) e:h
imprime solo la ruta del directorio y no el nombre del archivo (comofind
's'%h
) (Ver secciones sobre Generación y modificadores de nombre de archivo ). Entonces, con el comando awk solo necesitamos contar el número de directorios únicos que aparecen, y el número de líneas es el recuento de archivos.fuente
29 7
. Si agrego-L
afind
, eso va hasta41 10
. ¿Qué salida necesitas?Python tiene
os.walk
, lo que hace que tareas como esta sean fáciles, intuitivas y automáticamente robustas incluso frente a nombres de archivos extraños como los que contienen caracteres de nueva línea. Esta secuencia de comandos de Python 3, que había publicado originalmente en el chat , está pensado para ejecutarse en el directorio actual (pero que no tiene que estar ubicado en el directorio actual, y se puede cambiar cuál es el camino que pasa aos.walk
):Eso imprime el recuento de directorios que contienen directamente al menos un archivo cuyo nombre termina en
.c
, seguido de un espacio, seguido del recuento de archivos cuyos nombres terminan en.c
. Los archivos "ocultos", es decir, los archivos cuyos nombres comienzan con.
, se incluyen, y los directorios ocultos se recorren de manera similar.os.walk
recorre recursivamente una jerarquía de directorios. Enumera todos los directorios a los que se puede acceder de forma recursiva desde el punto de partida, y proporciona información sobre cada uno de ellos como una tupla de tres valoresroot, dirs, files
. Para cada directorio al que atraviesa (incluido el primero cuyo nombre le da):root
contiene el nombre de ruta de ese directorio. Tenga en cuenta que esto es totalmente ajeno a "directorio raíz" del sistema/
(y también sin relación con/root
) a pesar de que iba a ir a aquellos si se inicia allí. En este caso,root
comienza en la ruta.
, es decir, el directorio actual, y va a todas partes debajo de él.dirs
contiene una lista de las rutas de todos los subdirectorios del directorio cuyo nombre se encuentra actualmente enroot
.files
contiene una lista de las rutas de todos los archivos que residen en el directorio cuyo nombre se encuentra actualmenteroot
pero que no son directorios en sí mismos. Tenga en cuenta que esto incluye otros tipos de archivos que no son archivos normales, incluidos enlaces simbólicos, pero parece que no espera que tales entradas terminen.c
y esté interesado en ver alguna que sí lo haga.En este caso, solo necesito examinar el tercer elemento de la tupla
files
(que llamofs
en el script). Al igual que elfind
comando, Pythonos.walk
atraviesa subdirectorios para mí; Lo único que tengo que inspeccionar es el nombre de los archivos que contiene cada uno de ellos. Sinfind
embargo, a diferencia del comando,os.walk
automáticamente me proporciona una lista de esos nombres de archivo.Ese guión no sigue enlaces simbólicos. Es muy probable que no desee que se sigan los enlaces simbólicos para una operación de este tipo, ya que podrían formar ciclos, y porque incluso si no hay ciclos, los mismos archivos y directorios pueden atravesarse y contarse varias veces si son accesibles a través de diferentes enlaces simbólicos.
Si alguna vez quisiste
os.walk
seguir enlaces simbólicos, lo que normalmente no harías, entonces puedes pasarfollowlinks=true
a él. Es decir, en lugar de escribiros.walk('.')
, podrías escribiros.walk('.', followlinks=true)
. Reitero que rara vez querrá eso, especialmente para una tarea como esta en la que enumera recursivamente una estructura de directorio completa, sin importar cuán grande sea, y cuenta todos los archivos que cumplen algún requisito.fuente
Encuentra + Perl:
Explicación
El
find
comando encontrará los archivos normales (por lo que no hay enlaces simbólicos o directorios) y luego imprimirá el nombre del directorio en el que están (%h
) seguido de\0
.perl -0 -ne
: lea la entrada línea por línea (-n
) y aplique el script dado por-e
a cada línea. La-0
fija el separador de línea de entrada a\0
lo que podemos leer la entrada nula delimitado.$k{$_}++
:$_
es una variable especial que toma el valor de la línea actual. Esto se usa como una clave para el hash%k
, cuyos valores son el número de veces que se vio cada línea de entrada (nombre del directorio).}{
: esta es una forma abreviada de escribirEND{}
. Cualquier comando después del}{
se ejecutará una vez, después de que se haya procesado toda la entrada.print scalar keys %k, " $.\n"
:keys %k
devuelve una matriz de claves en el hash%k
.scalar keys %k
da la cantidad de elementos en esa matriz, la cantidad de directorios vistos. Esto se imprime junto con el valor actual de$.
, una variable especial que contiene el número de línea de entrada actual. Como esto se ejecuta al final, el número de línea de entrada actual será el número de la última línea, por lo tanto, el número de líneas vistas hasta ahora.Puede ampliar el comando perl a esto, para mayor claridad:
fuente
Aquí está mi sugerencia:
Este breve script crea un archivo temporal, encuentra todos los archivos dentro y debajo del directorio actual que termina en
.c
y escribe la lista en el archivo temporal.grep
luego se usa para contar los archivos (siguiendo ¿Cómo puedo obtener un recuento de archivos en un directorio usando la línea de comando? ) dos veces: La segunda vez, los directorios que se enumeran varias veces se eliminan usandosort -u
después de quitar los nombres de archivo de cada línea usandosed
.Esto también funciona correctamente con las nuevas líneas en los nombres de archivo:
grep -c /
cuenta solo las líneas con una barra inclinada y, por lo tanto, solo considera la primera línea de un nombre de archivo de varias líneas en la lista.Salida
fuente
Pequeño shellscript
Sugiero un pequeño shellscript de bash con dos líneas de comando principales (y una variable
filetype
para facilitar el cambio para buscar otros tipos de archivos).No busca ni en enlaces simbólicos, solo archivos regulares.
Shellscript detallado
Esta es una versión más detallada que también considera enlaces simbólicos,
Prueba de salida
Del breve shellscript:
Del shellscript detallado:
fuente
Perl One liner simple:
O más simple con
find
comando:Si te gusta el golf y tienes Perl reciente (como de menos de una década):
fuente
Considere usar el
locate
comando que es mucho más rápido que elfind
comando.Ejecutando en datos de prueba
Gracias a Muru por su respuesta para ayudarme a eliminar enlaces simbólicos del conteo de archivos en la respuesta de Unix y Linux .
Gracias a Terdon por su respuesta de
$PWD
(no dirigida a mí) en la respuesta de Unix y Linux .Respuesta original a continuación referenciada por comentarios
Forma corta:
sudo updatedb
Actualice la base de datos utilizada por ellocate
comando si los.c
archivos se crearon hoy o si ha eliminado.c
archivos hoy.locate -cr "$PWD.*\.c$"
ubica todos los.c
archivos en el directorio actual y son hijos ($PWD
). En lugar de imprimir nombres de archivos e imprimir, contar con-c
argumento. Losr
Especifica expresión regular en lugar de defecto*pattern*
a juego que puede producir demasiados resultados.locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
. Ubique todos los*.c
archivos en el directorio actual y a continuación. Elimine el nombre del archivosed
dejando solo el nombre del directorio. Cuente el número de archivos en cada directorio usandouniq -c
. Contar el número de directorios conwc -l
.Comience en el directorio actual con one-liner
Observe cómo han cambiado el recuento de archivos y el recuento de directorios. Creo que todos los usuarios tienen el
/usr/src
directorio y pueden ejecutar los comandos anteriores con diferentes recuentos dependiendo del número de núcleos instalados.Forma larga:
La forma larga incluye el tiempo para que pueda ver cuánto más rápido
locate
ha terminadofind
. Incluso si tiene que corrersudo updatedb
, es muchas veces más rápido que uno solofind /
.Nota: Estos son todos los archivos en TODAS las unidades y particiones. es decir, también podemos buscar comandos de Windows:
Tengo tres particiones NTFS de Windows 10 montadas automáticamente en
/etc/fstab
. ¡Ten en cuenta que localizar lo sabe todo!Cuenta interesante:
Lleva 15 segundos contar 1,637,135 archivos en 286,705 directorios. YMMV.
Para obtener un desglose detallado sobre
locate
el manejo de expresiones regulares del comando (parece que no es necesario en estas preguntas y respuestas, pero se usa por si acaso), lea esto: ¿ Use "localizar" en algún directorio específico?Lectura adicional de artículos recientes:
fuente
.c
(tenga en cuenta que se romperá si hay un archivo nombrado-.c
en el directorio actual ya que no está citando*.c
) y luego imprimirá todos los directorios en el sistema, independientemente de si contienen archivos .c.~/my_c_progs/*.c
. Está contando 638 directorios con.c
programas, el total de directorios se muestra más tarde como286,705
. Revisaré la respuesta a la comilla doble "* .c". Gracias por el consejo.locate -r "/path/to/dir/.*\.c$"
, pero eso no se menciona en ninguna parte de su respuesta. Solo da un enlace a otra respuesta que menciona esto pero sin explicación de cómo adaptarlo para responder la pregunta que se hace aquí. Toda su respuesta se centra en cómo contar la cantidad total de archivos y directorios en el sistema, lo cual no es relevante para la pregunta que se hizo, ¿cómo puedo contar la cantidad de archivos .c y la cantidad de directorios que contienen? archivos c en un directorio específico ". Además, sus números están equivocados, pruébelo en el ejemplo en el OP.$PWD
variable: unix.stackexchange.com/a/188191/200094$PWD
no contenga caracteres que tal vez sean especiales en una expresión regular