¿Existe una alternativa más sucinta a la conexión a wc para contar archivos en un directorio?

12

Si lo hago ls -1 target_dir | wc -l, obtengo un recuento de archivos en un directorio. Esto me parece un poco engorroso. ¿Hay alguna forma más elegante o sucinta?

codecowboy
fuente
2
No necesita el "-1" cuando se conecta a wc.
Steve
lsYa da el recuento total, ¿qué tal ls -l | head -1? Conviértelo en un alias si quieres algo más corto.
Daniel Wagner
2
@DanielWagner La salida "total: nnn" ls -lindica el tamaño total de los archivos, no el número de archivos.
David Richerby
2
Tenga en cuenta que ls | wc -lle dará un recuento incorrecto si algún nombre de archivo contiene nuevas líneas.
chepner
Esto depende del sistema de archivos y cuenta los directorios + 2 en un directorio. La respuesta tiene 2 extra (ya que se cuenta a sí misma y a su padre). stat -c %h .da la misma información quels -ld . | cut -d" " -f 2
ctrl-alt-delor

Respuestas:

12

Suponiendo bash 4+ (que tiene cualquier versión compatible de Ubuntu):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"
)

Llámalo como num_files [dir]. dires opcional, de lo contrario, utiliza el directorio actual. Su versión original no cuenta los archivos ocultos, por lo que tampoco lo hace. Si quieres eso, shopt -s dotglobantes set -- *.

Su ejemplo original cuenta no solo los archivos normales, sino también los directorios y otros dispositivos; si realmente solo desea archivos regulares (incluidos los enlaces simbólicos a archivos normales), deberá verificarlos:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    done
    echo "$count"
)

Si tiene GNU find, algo como esto también es una opción (tenga en cuenta que esto incluye archivos ocultos, que su comando original no hizo):

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c
}

(cambie -typea -xtypesi también desea contar enlaces simbólicos a archivos normales).

Chris Down
fuente
¿No setfallará si hay muchos archivos? Creo que es posible que tenga que usar xargsun código de suma para que funcione en el caso general.
l0b0
1
Además, shopt -s dotglobsi desea .contar los archivos que comienzan con
Trauma digital
1
@ l0b0 No creo setque falle en estas circunstancias, ya que en realidad no estamos haciendo un exec. A saber, en mi sistema, getconf ARG_MAXproduce 262.144, pero si lo hago test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max, felizmente responde 262145.
kojiro
@DavidRicherby -maxdepthno es POSIX.
Chris Down
44
@MichaelMartinez Escribir código obvio no es un sustituto de escribir el código correcto.
Chris Down
3

f=(target_dir/*);echo ${#f[*]}

funciona correctamente para archivos con espacios, líneas nuevas, etc. en el nombre.

Aaron Davies
fuente
¿Puedes proporcionar algún contexto? ¿Debería ir esto en un script bash?
codecowboy
podria. También puedes ponerlo directamente en el caparazón. esa versión asumió que quería el directorio actual; Lo he editado para que esté más cerca de tu pregunta. básicamente crea una variable de matriz de shell que contiene todos los archivos en el directorio, luego imprime el recuento de esa matriz. debería funcionar en cualquier shell con matrices - bash, ksh, zsh, etc. - pero probablemente no sea simple sh / ash / dash.
Aaron Davies
2

lses de varias columnas solo si sale directamente a un terminal, puede eliminar la opción "-1", puede eliminar la wcopción "-l", solo leer el primer valor (solución diferida, no se debe usar para evidencias leguales, investigaciones criminales, misiones críticas, operaciones tácticas ...).

ls target | wc 
Emmanuel
fuente
55
Esto falla para los nombres de archivo que contienen nuevas líneas.
l0b0
@Emmanuel Deberá analizar el resultado de su wcpara obtener el número de archivos, incluso en el caso trivial, entonces, ¿cómo es esto incluso una solución?
l0b0
@Emmanuel Esto puede fallar si targetes un globo que, cuando se expande, incluye algunas cosas que comienzan con guiones. Por ejemplo, crea un nuevo directorio, entra en él y hazlo touch -- {1,2,3,-a}.txt && ls *|wc(NB: rm -- *.txt
úsalo
Quiso decir wc -l? De lo contrario, obtendrá recuentos de línea nueva, palabra y byte de la lssalida. Eso es lo que dijo David Richerby: hay que analizarlo nuevamente.
erik
@erik No tengo wcargumentos que no necesites analizar si tu cerebro sabe que el primer argumento es nueva línea.
Emmanuel
2

Si se trata de la concisión que está buscando (en lugar de la corrección exacta cuando se trata de archivos con nuevas líneas en sus nombres, etc.), te recomiendo aliasing wc -la lc( "número de líneas"):

$ alias lc='wc -l'
$ ls target_dir|lc

Como otros han señalado, no necesita la -1opción ls, ya que es automática cuando lsse escribe en una tubería. (A menos que tenga un lsalias para usar siempre el modo de columna. Lo he visto antes, pero no con mucha frecuencia).

Un lcalias es bastante útil en general, y para esta pregunta, si nos fijamos en el caso de "contar el directorio actual", ls|lces lo más sucinto posible.

Aaron Davies
fuente
2

Hasta ahora, Aaron's es el único enfoque más sucinto que el suyo. Una versión más correcta de su enfoque podría verse así:

ls -aR1q | grep -Ecv '^\./|/$|^$'

Eso enumera recursivamente todos los archivos, no directorios, uno por línea, incluidos los archivos .dot debajo del directorio actual, utilizando globos de shell según sea necesario para reemplazar los caracteres no imprimibles. grep filtra cualquier listado de directorio padre o ... o * / o líneas en blanco, por lo que solo debe haber una línea por archivo, el recuento total de qué grep regresa a usted. Si desea incluir directorios secundarios también:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

Elimine el -Ren cualquier caso si no desea resultados recursivos.

mikeserv
fuente
1
Tiendo a preferir hacer ese tipo de cosas con find. Si solo desea un conteo, esto debería funcionar: find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l(elimine los controles de profundidad para obtener resultados recursivos).
Aaron Davies
@AaronDavies: eso en realidad no funciona. Pon una nueva línea en cualquiera de esos nombres de archivo y compruébalo por ti mismo. Además, para hacer lo mismo de manera portátil que usted: find . \! -name . -prune | wc -l- que todavía no funciona, por supuesto.
mikeserv
1
No sigo: la printfinstrucción imprime una cadena constante (una nueva línea) que no incluye el nombre del archivo, por lo que los resultados son independientes de cualquier nombre de archivo extraño. Este truco no se puede hacer en absoluto con uno findque no es compatible printf, por supuesto.
Aaron Davies
@AaronDavies: oh, cierto. Supuse que el nombre de archivo estaba incluido. Sin embargo, se puede hacer de forma portátil:find .//. \!. -name . -prune | grep -c '^\.//\.'
mikeserv
¡brillante! /Siendo el único otro personaje que no puede aparecer en los nombres de archivo, .//.se garantiza que la secuencia aparezca exactamente una vez para cada archivo, ¿verdad? sin embargo, un par de preguntas: ¿ .//.por qué y por qué -prune? ¿Cuándo diferiría esto find . \! -name . | grep -c '^\.'? (Supongo que .en su \!.es un error tipográfico.)
Aaron Davies