Comment travailler de manière récursive dans une arborescence de répertoires et exécuter une commande spécifique sur chaque fichier et générer le chemin, le nom du fichier, l'extension, la taille du fichier et un autre texte spécifique dans un seul fichier dans bash.
Alors que les solutions find
sont simples et puissantes, j'ai décidé de créer une solution plus complexe, basée sur cette fonction intéressante , que j'ai vue il y a quelques jours.
1. Créez un fichier de script exécutable, appelé walk
, situé dans /usr/local/bin
afin qu'il soit accessible en tant que commande Shell:
Sudo touch /usr/local/bin/walk
Sudo chmod +x /usr/local/bin/walk
Sudo nano /usr/local/bin/walk
nano
: Shift+Insert pour la pâte; Ctrl+O et Enter pour sauver; Ctrl+X pour la sortie. 2. Le contenu du script walk
est:
#!/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. Explication:
Le mécanisme principal de la fonction walk()
est assez bien décrit par Zanna dans son réponse . Je ne décrirai donc que la nouvelle partie.
Dans la fonction walk()
j'ai ajouté cette boucle:
for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done
Cela signifie que pour chaque $entry
qui est un fichier, la fonction file_specification()
sera exécutée.
La fonction file_specification()
a deux parties. La première partie récupère les données liées au fichier - nom, chemin d'accès, taille, etc. La seconde partie affiche les données sous une forme bien formatée. Pour formater les données, utilisez la commande printf
. Et si vous voulez modifier le script, vous devriez en savoir plus sur cette commande - par exemple cet article .
La fonction file_specification()
est un bon endroit où vous pouvez mettre la commande spécifique à exécuter pour chaque fichier . Utilisez ce format:
commander "$ {entry}"
Ou vous pouvez enregistrer le résultat de la commande sous forme de variable, puis printf
cette variable, etc.:
MY_VAR = "$ (commander "$ {entry}") " printf"% * s\t Taille du fichier:\t $ {YEL}% s $ {NCL}\n "$ ((indent + 4)) ''" $ MY_VAR "
Ou directement printf
le résultat de la commande:
printf "% * s\t Taille du fichier:\t $ {YEL}% s $ {NCL}\n" $ ((indent + 4)) '' "$ (commander "$ {entry}") "
La section de début, appelée Colourise the output
, initialise quelques variables utilisées dans la commande printf
pour coloriser la sortie. Plus à ce sujet, vous pouvez trouver ici .
Au bas du script est ajoutée une condition supplémentaire qui traite des chemins absolus et relatifs.
4. Exemples d'utilisation:
Pour exécuter walk
pour le répertoire actuel:
walk # You shouldn't use any argument,
walk ./ # but you can use also this format
Pour exécuter walk
pour tout répertoire enfant:
walk <directory name>
walk ./<directory name>
walk <directory name>/<sub directory>
Pour exécuter walk
pour tout autre répertoire:
walk /full/path/to/<directory name>
Pour créer un fichier texte, basé sur la sortie walk
:
walk > output.file
Pour créer un fichier de sortie sans code de couleur ( source ):
walk | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" > output.file
5.. Démonstration de l'utilisation:
Je suis un peu perplexe quant à la raison pour laquelle personne ne l'a encore publiée, mais bash
a effectivement des fonctionnalités récursives, si vous activez l'option globstar
et utilisez **
glob. En tant que tel, vous pouvez écrire un script (presque) pur bash
qui utilise cet globstar récursif comme ceci:
#!/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
Notez que nous utilisons ici le développement de paramètres pour obtenir les parties de nom de fichier souhaitées et que nous ne comptons pas sur des commandes externes, à l'exception de la taille du fichier avec du
et du nettoyage de la sortie avec awk
.
Et comme il traverse votre arborescence de répertoires, votre sortie devrait ressembler à ceci:
Path: ./glibc/glibc-2.23/benchtests
Filename: sprintf-source.c
Extension: c
Filesize: 326
Les règles standard d'utilisation du script s'appliquent: assurez-vous qu'il est exécutable avec chmod +x ./myscript.sh
et exécutez-le à partir du répertoire en cours via ./myscript.sh
ou placez-le dans ~/bin
et exécutez source ~/.profile
.
Vous pouvez utiliser find
pour faire le travail
find /path/ -type f -exec ls -alh {} \;
Cela vous aidera si vous souhaitez simplement répertorier tous les fichiers de taille.
-exec
vous permettra d'exécuter une commande ou un script personnalisé pour chaque fichier \;
utilisé pour analyser les fichiers un par un. Vous pouvez utiliser +;
si vous souhaitez les concaténer (signifie noms de fichiers).
Avec find
uniquement.
find /path/ -type f -printf "path:%h fileName:%f size:%kKB Some Text\n" > to_single_file
Ou, vous pouvez utiliser ci-dessous à la place:
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
Si vous connaissez la profondeur de l’arbre, le moyen le plus simple consiste à utiliser le caractère générique *
.
Écrivez tout ce que vous voulez faire en tant que script shell ou fonction
function thing() { ... }
puis exécutez for i in *; do thing "$i"; done
, for i in */*; do thing "$i"; done
, ... etc
Dans votre fonction/script, vous pouvez utiliser quelques tests simples pour distinguer les fichiers avec lesquels vous souhaitez travailler et en faire le nécessaire.
find
peut faire ceci:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'
Regardez man find
pour d'autres propriétés de fichier.
Si vous avez vraiment besoin de l'extension, vous pouvez ajouter ceci:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;