Comment puis-je travailler récursivement à travers une arborescence de répertoires et exécuter une commande spécifique sur chaque fichier, et sortir le chemin, le nom de fichier, l'extension, la taille du fichier et d'autres textes spécifiques dans un seul fichier en bash.
Réponses
Trop de publicités?Alors que find
sont simples et puissantes, j'ai décidé de créer une solution plus compliquée, qui se base sur cette fonction intéressante que j'ai vu il y a quelques jours.
- Plus d'explications et deux autres scripts, basés sur le courant sont fournis aquí .
1. Créer un fichier exécutable script, appelé walk
qui est situé dans /usr/local/bin
pour être 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
- Copier le contenu du script ci-dessous et l'utiliser dans
nano
: Shift + Insert pour la pâte ; Ctrl + O y 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 principal mécanisme de l
walk()
est assez bien décrite par Zanna dans son livre intitulé réponse . Je ne décrirai donc que la nouvelle partie. -
Au sein de la
walk()
j'ai ajouté cette boucle :for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done
Cela signifie que pour chaque
$entry
c'est-à-dire un fichier sera exécuté la fonctionfile_specification()
. -
La fonction
file_specification()
comporte deux parties. La première partie récupère les données relatives au fichier - nom, chemin, taille, etc. La deuxième partie produit les données sous une forme bien formatée. Pour formater les données, on utilise la commandeprintf
. Et si vous voulez modifier le script vous devriez lire cette commande - par exemple cet article . -
La fonction
file_specification()
est un bon endroit où vous pouvez mettre la commande spécifique qui doit être exécutée pour chaque fichier . Utilisez ce format :command "${entry}"
Ou vous pouvez enregistrer la sortie de la commande comme variable, puis
printf
cette variable, etc :MY\_VAR="$(_command_ "${entry}")" printf "%\*s\\tFile size:\\t${YEL}%s${NCL}\\n" $((indent+4)) '' "$MY\_VAR"
Ou directement
printf
la sortie de la commande :printf "%\*s\\tFile size:\\t${YEL}%s${NCL}\\n" $((indent+4)) '' "$(_command_ "${entry}")"
-
La section de la mendicité, appelée
Colourise the output
initialiser quelques variables qui sont utilisées dans l'applicationprintf
pour colorer la sortie. Pour en savoir plus, vous pouvez consulter aquí . -
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 courant :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 le
walk
sortie :walk > output.file
-
Pour créer un fichier de sortie sans codes 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 posté, mais en effet bash
a des capacités récursives, si vous activez la fonction globstar
et utiliser l'option **
glob. En tant que tel, vous pouvez écrire (presque) de pures bash
script qui utilise ce 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
Remarquez qu'ici nous utilisons l'expansion des paramètres pour obtenir les parties du nom de fichier que nous voulons et nous ne nous appuyons pas sur des commandes externes sauf pour obtenir la taille du fichier avec du
et la sortie de nettoyage 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 de script s'appliquent : assurez-vous qu'il est exécutable avec chmod +x ./myscript.sh
et l'exécuter depuis le répertoire courant via ./myscript.sh
ou le placer dans ~/bin
et exécuter source ~/.profile
.
Vous pouvez utiliser find
pour faire le travail
find /path/ -type f -exec ls -alh {} \;
Cela vous aidera si vous voulez juste lister tous les fichiers avec leur taille.
-exec
vous permettra d'exécuter une commande personnalisée ou script pour chaque fichier \;
utilisé pour analyser les fichiers un par un, vous pouvez utiliser +;
si vous voulez les concaténer (signifie noms de fichiers).
Avec find
seulement.
find /path/ -type f -printf "path:%h fileName:%f size:%kKB Some Text\n" > to_single_file
Ou bien, vous pouvez utiliser la méthode 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 savez quelle est la profondeur de l'arbre, le plus simple sera d'utiliser le joker *
.
Écrire tout ce que vous voulez faire comme un Shell Shell ou une fonction.
function thing() { ... }
puis exécutez for i in *; do thing "$i"; done
, for i in */*; do thing "$i"; done
, ... etc.
À l'intérieur de votre fonction/script, vous pouvez utiliser quelques des tests simples pour sélectionner les fichiers sur lesquels vous voulez travailler et faire ce que vous voulez avec eux.
- Réponses précédentes
- Plus de réponses