Script bash recursivo para recopilar información sobre cada archivo en una estructura de directorio


14

¿Cómo trabajo recursivamente a través de un árbol de directorios y ejecuto un comando específico en cada archivo, y envío la ruta, el nombre de archivo, la extensión, el tamaño de archivo y algún otro texto específico a un solo archivo en bash.


jajaja, gracias por la edición; Seré el primero en admitir que complica demasiado las cosas, porque estoy acostumbrado a que me hagan 800 preguntas irrelevantes en el mundo hooman; entonces trato de responder las obvias en las preguntas; aunque aprenderé :-)
SPooKYiNeSS

1
OK, creo que la pregunta es bastante clara sobre lo que se debe hacer, ir a través del árbol de directorios y obtener información sobre cada archivo. La pregunta es bastante clara, y a juzgar por la cantidad de respuestas ya, la gente la entiende bastante bien. Los 3 votos por no ser claros realmente no se merecen para esta pregunta
Sergiy Kolodyazhnyy

Respuestas:


15

Si bien las findsoluciones son simples y potentes, decidí crear una solución más complicada, basada en esta interesante función , que vi hace unos días.

  • Más explicaciones y otros dos guiones, sobre la base de la corriente se proporcionan aquí .

1. Cree un archivo de script ejecutable, llamado walk, que esté ubicado /usr/local/binpara ser accesible como comando de shell:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • Copie el contenido del script a continuación y úselo en nano: Shift+ Insertpara pegar; Ctrl+ Oy Enterpara guardar; Ctrl+ Xpara salir.

2. El contenido del guión walkes:

#!/bin/bash

# Colourise the output
RED='\033[0;31m'        # Red
GRE='\033[0;32m'        # Green
YEL='\033[1;33m'        # Yellow
NCL='\033[0m'           # No Color

file_specification() {
        FILE_NAME="$(basename "${entry}")"
        DIR="$(dirname "${entry}")"
        NAME="${FILE_NAME%.*}"
        EXT="${FILE_NAME##*.}"
        SIZE="$(du -sh "${entry}" | cut -f1)"

        printf "%*s${GRE}%s${NCL}\n"                    $((indent+4)) '' "${entry}"
        printf "%*s\tFile name:\t${YEL}%s${NCL}\n"      $((indent+4)) '' "$FILE_NAME"
        printf "%*s\tDirectory:\t${YEL}%s${NCL}\n"      $((indent+4)) '' "$DIR"
        printf "%*s\tName only:\t${YEL}%s${NCL}\n"      $((indent+4)) '' "$NAME"
        printf "%*s\tExtension:\t${YEL}%s${NCL}\n"      $((indent+4)) '' "$EXT"
        printf "%*s\tFile size:\t${YEL}%s${NCL}\n"      $((indent+4)) '' "$SIZE"
}

walk() {
        local indent="${2:-0}"
        printf "\n%*s${RED}%s${NCL}\n\n" "$indent" '' "$1"
        # If the entry is a file do some operations
        for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done
        # If the entry is a directory call walk() == create recursion
        for entry in "$1"/*; do [[ -d "$entry" ]] && walk "$entry" $((indent+4)); done
}

# If the path is empty use the current, otherwise convert relative to absolute; Exec walk()
[[ -z "${1}" ]] && ABS_PATH="${PWD}" || cd "${1}" && ABS_PATH="${PWD}"
walk "${ABS_PATH}"      
echo                    

3. Explicación:

  • El mecanismo principal de la walk()función está bastante bien descrito por Zanna en su respuesta . Así que describiré solo la parte nueva.

  • Dentro de la walk()función he agregado este bucle:

    for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done

    Eso significa que para cada $entryarchivo que se ejecute se ejecutará la función file_specification().

  • La función file_specification()tiene dos partes. La primera parte obtiene datos relacionados con el archivo: nombre, ruta, tamaño, etc. La segunda parte genera los datos en forma bien formateada. Para formatear los datos se utiliza el comando printf. Y si desea modificar el script, debe leer sobre este comando, por ejemplo, este artículo .

  • La función file_specification()es un buen lugar donde puede colocar el comando específico que debe ejecutarse para cada archivo . Use este formato:

    comando "$ {entry}"

    O puede guardar la salida del comando como variable, y luego printfesta variable, etc.

    MY_VAR = "$ ( comando " $ {entry} ")"
    printf "% * s \ t Tamaño del archivo: \ t $ {YEL}% s $ {NCL} \ n" $ ((sangría + 4)) '' "$ MY_VAR"

    O directamente printfla salida del comando:

    printf "% * s \ t Tamaño del archivo: \ t $ {YEL}% s $ {NCL} \ n" $ ((sangría + 4)) '' "$ ( comando " $ {entry} ")"

  • La sección de inicio, llamada Colourise the output, inicializa algunas variables que se usan dentro del printfcomando para colorear la salida. Puede encontrar más sobre esto aquí .

  • En la parte inferior del script se agrega una condición adicional que trata con rutas absolutas y relativas.

4. Ejemplos de uso:

  • Para ejecutar walkel directorio actual:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • Para ejecutar walkcualquier directorio secundario:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • Para ejecutar walkcualquier otro directorio:

    walk /full/path/to/<directory name>
  • Para crear un archivo de texto, basado en la walksalida:

    walk > output.file
  • Para crear un archivo de salida sin códigos de color ( fuente ):

    walk | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" > output.file

5. Demostración de uso:

ingrese la descripción de la imagen aquí


Eso es mucho trabajo, pero se ve bien. Buen trabajo !
Sergiy Kolodyazhnyy

¿Qué proceso estás usando para hacer esos gifs @ pa4080?
pbhj

@pbhj, en Ubuntu estoy usando Peek , es simple y agradable, pero a veces falla y no tiene capacidades de edición. La mayoría de mis GIF se crean en Windows, donde estoy grabando la ventana de la conexión VNC. Tengo una máquina de escritorio separada que estoy usando principalmente para la creación de MS Office y GIF :) La herramienta que estoy usando allí es ScreenToGif . Es de código abierto, gratuito y tiene un potente editor y mecanismo de procesamiento. Desafortunadamente no puedo encontrar herramientas como ScreenToGif para Ubuntu.
pa4080

13

Estoy un poco perplejo en cuanto a por qué nadie lo ha publicado todavía, pero bashsí tiene capacidades recursivas, si habilita la globstaropción y usa **glob. Como tal, puedes escribir (casi) un bash script puro que use ese globstar recursivo como este:

#!/usr/bin/env bash

shopt -s globstar

for i in ./**/*
do
    if [ -f "$i" ];
    then
        printf "Path: %s\n" "${i%/*}" # shortest suffix removal
        printf "Filename: %s\n" "${i##*/}" # longest prefix removal
        printf "Extension: %s\n"  "${i##*.}"
        printf "Filesize: %s\n" "$(du -b "$i" | awk '{print $1}')"
        # some other command can go here
        printf "\n\n"
    fi
done

Tenga en cuenta que aquí usamos la expansión de parámetros para obtener las partes del nombre de archivo que queremos y no confiamos en comandos externos, excepto para obtener el tamaño del archivo duy limpiar la salida con awk.

Y a medida que atraviesa su árbol de directorios, su salida debería ser algo como esto:

Path: ./glibc/glibc-2.23/benchtests
Filename: sprintf-source.c
Extension: c
Filesize: 326

Se aplican las reglas estándar de uso de script: asegúrese de que sea ejecutable chmod +x ./myscript.shy ejecútelo desde el directorio actual ./myscript.sho colóquelo ~/biny ejecútelo source ~/.profile.


Si está imprimiendo el nombre de archivo completo, ¿qué extra le brinda la "extensión"? ¿Quizás realmente desea la información MIME que "$(file "$i")"(en la secuencia de comandos anterior como segunda parte de un printf) regresaría?
pbhj

1
@pbhj ¿A mí personalmente? Nada. Pero OP que hizo la pregunta solicitó output the path, filename, extension, filesize , por lo que la respuesta coincide con lo que se pregunta. :)
Sergiy Kolodyazhnyy

12

Puedes usar findpara hacer el trabajo

find /path/ -type f -exec ls -alh {} \;

Esto lo ayudará si solo desea enumerar todos los archivos con tamaño.

-execle permitirá ejecutar comandos personalizados o secuencias de comandos para cada archivo \;utilizado para analizar archivos uno por uno, puede usar +;si desea concatenarlos (significa nombres de archivo).


Esto es bueno, pero no responde a todos los requisitos que OP mencionó.
αғsнιη

1
@ αғsнιη Acabo de darle una plantilla para trabajar. Lo sé, esta no es una respuesta completa a esta pregunta, ya que creo que la pregunta en sí es de amplio alcance.
Rajesh Rajendran

6

Con findsolo.

find /path/ -type f -printf "path:%h  fileName:%f  size:%kKB Some Text\n" > to_single_file

O bien, puede usar a continuación en su lugar:

find -type f -not -name "to_single_file"  -execdir sh -c '
    printf "%s %s %s %s Some Text\n" "$PWD" "${1#./}" "${1##*.}" $(stat -c %s "$1")
' _ {} \; > to_single_file

2
Elegante y simple (si lo sabes find -printf). +1
David Foerster

1

Si sabe cuán profundo es el árbol, la forma más fácil será usar el comodín *.

Escribe todo lo que quieras hacer como un script de shell o una función

function thing() { ... }

a continuación, ejecutar for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done, ... etc.

Dentro de su función / script, puede usar algunas pruebas simples para seleccionar los archivos con los que desea trabajar y hacer lo que necesite con ellos.


"esto no funcionará si alguno de sus nombres de archivo tiene espacios" ... ¡porque olvidó citar sus variables! Use "$ i" en lugar de $i.
muru

@muru no, la razón por la que no funciona es porque el bucle "for" se divide en espacios - " / 'se expande en una lista separada por espacios de todos los archivos. Puede solucionar esto, por ejemplo, jugando con el IFS, pero en ese punto también podrías usar find.
Benubird

@ pa4080 no es relevante para esta respuesta, pero de todos modos parece súper útil, ¡gracias!
Benubird

Creo que no entiendes cómo for i in */*funciona. Aquí, for i in */*; do printf "|%s|\n" "$i"; done
pruébalo

Aquí hay una evidencia de la importancia de las comillas: i.stack.imgur.com/oYSj2.png
pa4080

1

find puede hacer esto:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'

Echa un vistazo a man findotras propiedades de archivo.

Si realmente necesita la extensión, puede agregar esto:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.