¿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.
¿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.
Respuestas:
Si bien las find
soluciones son simples y potentes, decidí crear una solución más complicada, basada en esta interesante función , que vi hace unos días.
1. Cree un archivo de script ejecutable, llamado walk
, que esté ubicado /usr/local/bin
para 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
nano
: Shift+ Insertpara pegar; Ctrl+ Oy Enterpara guardar; Ctrl+ Xpara salir.2. El contenido del guión walk
es:
#!/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 $entry
archivo 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 printf
esta 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 printf
la 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 printf
comando 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 walk
el directorio actual:
walk # You shouldn't use any argument,
walk ./ # but you can use also this format
Para ejecutar walk
cualquier directorio secundario:
walk <directory name>
walk ./<directory name>
walk <directory name>/<sub directory>
Para ejecutar walk
cualquier otro directorio:
walk /full/path/to/<directory name>
Para crear un archivo de texto, basado en la walk
salida:
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:
Estoy un poco perplejo en cuanto a por qué nadie lo ha publicado todavía, pero bash
sí tiene capacidades recursivas, si habilita la globstar
opció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 du
y 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.sh
y ejecútelo desde el directorio actual ./myscript.sh
o colóquelo ~/bin
y ejecútelo source ~/.profile
.
"$(file "$i")"
(en la secuencia de comandos anterior como segunda parte de un printf) regresaría?
output the path, filename, extension, filesize
, por lo que la respuesta coincide con lo que se pregunta. :)
Puedes usar find
para hacer el trabajo
find /path/ -type f -exec ls -alh {} \;
Esto lo ayudará si solo desea enumerar todos los archivos con tamaño.
-exec
le 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).
Con find
solo.
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
find -printf
). +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.
$i
.
for i in */*
funciona. Aquí, for i in */*; do printf "|%s|\n" "$i"; done
find
puede hacer esto:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'
Echa un vistazo a man find
otras 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"' {} \;