¿Cómo informar el número de archivos en todos los subdirectorios?

24

Necesito inspeccionar todos los subdirectorios e informar cuántos archivos (sin recurrencia adicional) contienen:

directoryName1 numberOfFiles
directoryName2 numberOfFiles
Niño tímido
fuente
¿Por qué quieres usar findcuándo lo hará Bash? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): Para todos los directorios, cuente el número de entradas en ese directorio (incluidos los archivos ocultos de punto, con exclusión .e ..)
janmoesen
@janmoesen ¿Por qué no respondiste eso? Soy nuevo en las secuencias de comandos de shell, pero no puedo ver ningún problema con su método. Para mí, parece la mejor manera. Nadie ha votado tu comentario, pero tampoco ha comentado por qué podría ser malo. Las respuestas votadas tienen mucha más reputación que tú, por lo que me pregunto si me estoy perdiendo algo.
toxalot
@toxalot: no me molesté en agregarlo como respuesta porque era muy corto (y posiblemente un tono ligeramente condescendiente). Siéntase libre de votar el comentario. :-) Además, la pregunta es algo vaga con respecto a qué significa "cuántos archivos". Mi solución cuenta los archivos y directorios "normales" ; tal vez el cartel realmente significa "archivos, no directorios". Otra cosa a tener en cuenta es que este bloqueo no tiene en cuenta los archivos de puntos "ocultos". Sin embargo, hay maneras de evitar esas dos trampas. Pero de nuevo: no estoy seguro de los requisitos exactos del póster original.
Janmoesen

Respuestas:

30

Esto lo hace de una manera segura y portátil. No se confundirá con nombres de archivo extraños.

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

Tenga en cuenta que imprimirá primero el número de archivos, luego el nombre del directorio en una línea separada. Si desea mantener el formato de OP, necesitará más formatos, p. Ej.

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \;|wc -l|tr '\n' ' ' && echo $f; done|awk '{print $2"\t"$1}'

Si tiene un conjunto específico de subdirectorios que le interesan, puede reemplazarlos *por ellos.

¿Por qué es esto seguro? (y por lo tanto digno de script)

Los nombres de archivo pueden contener cualquier carácter excepto /. Hay algunos caracteres que son tratados especialmente por el shell o por los comandos. Estos incluyen espacios, líneas nuevas y guiones.

Usar la for f in *construcción es una forma segura de obtener cada nombre de archivo, sin importar lo que contenga.

Una vez que tenga el nombre de archivo en una variable, aún debe evitar cosas como find $f. Si $fcontuviera el nombre del archivo -test, findse quejaría de la opción que acaba de darle. La forma de evitar eso es mediante el uso ./delante del nombre; de esta manera tiene el mismo significado, pero ya no comienza con un guión.

Las nuevas líneas y los espacios también son un problema. Si $fcontiene "hola, amigo" como nombre de archivo,, find ./$fes find ./hello, buddy. Estás diciendo findque mires ./hello,y buddy. Si no existen, se quejará y nunca se verá ./hello, buddy. Esto es fácil de evitar: use comillas alrededor de sus variables.

Finalmente, los nombres de archivo pueden contener nuevas líneas, por lo que contar líneas nuevas en una lista de nombres de archivos no funcionará; obtendrá un recuento adicional para cada nombre de archivo con una nueva línea. Para evitar esto, no cuente las nuevas líneas en una lista de archivos; en su lugar, cuente las nuevas líneas (o cualquier otro carácter) que represente un solo archivo. Es por eso que el findcomando tiene simplemente -exec echo \;y no -exec echo {} \;. Solo quiero imprimir una sola línea nueva con el fin de contar los archivos.

Shawn J. Goff
fuente
1
¿Por qué hay una persona en el mundo que usa líneas nuevas en el nombre del archivo? Gracias por la respuesta.
ShyBoy
1
Los nombres de archivo pueden contener cualquier carácter excepto / y el carácter nulo, creo. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm
2
El recuento incluirá el directorio en sí. Si desea excluir eso del recuento, use-mindepth 1
toxalot
También puedes usar en -printf '\n'lugar de -exec echo.
toxalot
1
@toxalot puede hacerlo si tiene un hallazgo que admite -printf, pero no si desea que funcione en FreeBSD, por ejemplo.
Shawn J. Goff
6

Suponiendo que está buscando una solución estándar de Linux, una forma relativamente sencilla de lograrlo es con find:

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

Donde findatraviesa los dos subdirectorios especificados, a uno -maxdepthde 1, lo que evita una mayor recursión y solo informa archivos ( -type f) separados por nuevas líneas. El resultado se canaliza wcpara contar el número de esas líneas.

jasonwryan
fuente
Tengo más de 2 directorios ... ¿Cómo puedo combinar su comando con la find . -maxdepth 1 -type dsalida?
ShyBoy
Puede (a) incluir los directorios requeridos en una variable y find $dirs ...(b) si están exclusivamente en el directorio de un nivel superior, glob desde ese directorio,find */ ...
jasonwryan
1
Esto informará resultados incorrectos si algún nombre de archivo tiene un carácter de nueva línea.
Shawn J. Goff el
@ Shawn: gracias. Pensé que tenía nombres de archivo con espacios cubiertos, pero no había considerado nuevas líneas: ¿alguna sugerencia para una solución?
jasonwryan
Agregue -exec echoa su comando de búsqueda, de esa manera no se hace eco del nombre del archivo, solo una nueva línea.
Shawn J. Goff el
5

Por "sin recursividad", ¿quiere decir que si directoryName1tiene subdirectorios, entonces no desea contar los archivos en los subdirectorios? Si es así, aquí hay una manera de contar todos los archivos regulares en los directorios indicados:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

Tenga en cuenta que la -fprueba realiza dos funciones: comprueba si la entrada que coincide con uno de los globos de más arriba es un archivo normal y prueba si la entrada era una coincidencia (si uno de los globos no coincide, el patrón permanece como está). Si desea contar todas las entradas en los directorios dados independientemente de su tipo, reemplace -fcon -e.

Ksh tiene una manera de hacer que los patrones coincidan con los archivos de puntos y producir una lista vacía en caso de que ningún archivo coincida con un patrón. Entonces, en ksh puedes contar archivos regulares como este:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

o todos los archivos simplemente así:

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

Bash tiene diferentes formas de hacer esto más simple. Para contar archivos regulares:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

Para contar todos los archivos:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

Como de costumbre, es aún más simple en zsh. Para contar archivos regulares:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

Cambie (DN.)a (DN)para contar todos los archivos.

¹ Tenga en cuenta que cada patrón coincide, de lo contrario, los resultados podrían estar apagados (por ejemplo, si está contando archivos que comienzan con un dígito, no puede hacerlo simplemente for x in [0-9]*; do if [ -f "$x" ]; then …porque puede haber un archivo llamado [0-9]foo).

Gilles 'SO- deja de ser malvado'
fuente
2

Basado en un guión de conteo , la respuesta de Shawn y un truco de Bash para asegurarse de que incluso los nombres de archivo con líneas nuevas se impriman en forma utilizable en una sola línea:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %qes imprimir una versión citada de una cadena, es decir, una cadena de una sola línea que podría poner en una secuencia de comandos Bash para interpretarla como una cadena literal que incluye (potencialmente) líneas nuevas y otros caracteres especiales. Por ejemplo, véase echo -n $'\tfoo\nbar'vs printf %q $'\tfoo\nbar'.

El findcomando funciona simplemente imprimiendo un solo carácter para cada archivo y luego contando esos en lugar de contar líneas.

l0b0
fuente
1

Aquí está una manera "fuerza bruta" -ish para obtener su resultado, utilizando find, echo, ls, wc, xargsy awk.

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'
El general
fuente
Este trabajo. Pero la salida se estropeó si tiene directorios que tienen `` espacio en el nombre.
ShyBoy
Esto informará resultados incorrectos si algún nombre de archivo tiene un carácter de nueva línea.
Shawn J. Goff el
-1
for i in *; do echo $i; ls $i | wc -l; done
Dinesh
fuente
44
Bienvenido a U&L. Las respuestas deben ser largas con explicaciones y no simplemente caídas de código. Expanda esto y explique lo que está sucediendo. Además, esta es una forma muy ineficiente de hacer esto y no maneja archivos con espacios, por ejemplo.
slm