Script bash récursif pour collecter des informations sur chaque fichier dans une structure de répertoires

14

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 un autre texte spécifique dans un seul fichier dans bash.

SPooKYiNeSS
la source
lol, merci pour l'édition; je serai le premier à admettre que je suis trop compliqué, parce que j'ai l'habitude de se voir poser 800 questions non pertinentes dans le monde hooman; donc j'essaye de répondre aux plus évidentes dans les questions; j'apprendrai cependant :-)
SPooKYiNeSS
1
OK, je pense que la question est assez claire sur ce qui doit être fait, parcourez l'arborescence des répertoires et affichez les informations sur chaque fichier. La question est assez claire, et à en juger par la quantité de réponses déjà, les gens la comprennent assez bien. Les 3 votes pour manque de clarté ne sont vraiment pas mérités pour cette question
Sergiy Kolodyazhnyy

Réponses:

16

Alors que les findsolutions sont simples et puissantes, j'ai décidé de créer une solution plus compliquée, basée sur cette fonction intéressante , que j'ai vue il y a quelques jours.

  • Plus d'explications et deux autres scripts, basés sur le courant sont fournis ici .

1. Créez un fichier script exécutable, appelé walk, qui est situé dans /usr/local/binpour ê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
  • Copiez le contenu du script ci-dessous et utilisez-le dans nano: Shift+ Insertpour coller; Ctrl+ Oet Enterpour économiser; Ctrl+ Xpour sortir.

2. Le contenu du script walkest:

#!/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 walk()fonction est assez bien décrit par Zanna dans sa réponse . Je ne décrirai donc que la nouvelle partie.

  • Dans la walk()fonction, j'ai ajouté cette boucle:

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

    Cela signifie que pour chacun $entryqui est un fichier sera exécuté la fonction file_specification().

  • La fonction file_specification()comporte deux parties. La première partie obtient les données relatives au fichier - nom, chemin, taille, etc. La deuxième partie génère les données sous une forme bien formatée. Pour formater les données, on utilise la commande printf. Et si vous souhaitez modifier le script, vous devriez lire à propos de cette commande - par exemple cet article .

  • La fonction file_specification()est un bon endroit où vous pouvez placer la commande spécifique qui doit être exécutée pour chaque fichier . Utilisez ce format:

    commande "$ {entry}"

    Ou vous pouvez enregistrer la sortie de la commande en tant que variable, puis printfcette variable, etc.:

    MY_VAR = "$ ( command " $ {entry} ")"
    printf "% * s \ tTaille du fichier: \ t $ {YEL}% s $ {NCL} \ n" $ ((retrait + 4)) '' "$ MY_VAR"

    Ou directement printfla sortie de la commande:

    printf "% * s \ tTaille du fichier: \ t $ {YEL}% s $ {NCL} \ n" $ ((retrait + 4)) '' "$ ( commande " $ {entry} ")"

  • La section à la mendicité, appelée Colourise the output, initialise quelques variables utilisées dans la printfcommande pour coloriser la sortie. Vous trouverez plus d'informations à ce sujet 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 walkpour le répertoire actuel:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • Pour exécuter walkpour n'importe quel répertoire enfant:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • Pour exécuter walkpour tout autre répertoire:

    walk /full/path/to/<directory name>
  • Pour créer un fichier texte, basé sur la walksortie:

    walk > output.file
  • Pour créer un fichier de sortie sans codes couleur ( source ):

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

5. Démonstration d'utilisation:

entrez la description de l'image ici

pa4080
la source
C'est beaucoup de travail, mais ça a l'air bien. Bon travail !
Sergiy Kolodyazhnyy
Quel processus utilisez-vous pour créer ces gifs @ pa4080?
pbhj
@pbhj, sous Ubuntu j'utilise Peek c'est simple et sympa, mais parfois ça plante et ça n'a pas de capacité d'édition. La plupart de mes GIF sont créés sous Windows, où j'enregistre la fenêtre de la connexion VNC. J'ai une machine de bureau distincte que j'utilise principalement pour la création de MS Office et GIF :) L'outil que j'utilise ici est ScreenToGif . Il est open source, gratuit et dispose d'un puissant éditeur et d'un mécanisme de traitement. Malheureusement, je ne trouve pas d'outil comme ScreenToGif pour Ubuntu.
pa4080
13

Je suis un peu perplexe quant à la raison pour laquelle personne ne l'a encore posté, mais a effectivement bashdes capacités récursives, si vous activez l' globstaroption et utilisez **glob. En tant que tel, vous pouvez écrire un bash script (presque) pur qui utilise cette globstar récursive 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 l'expansion des paramètres pour obtenir les parties du nom de fichier que nous voulons et que nous ne comptons pas sur des commandes externes, sauf pour obtenir la taille du fichier avec duet nettoyer la sortie avecawk .

Et pendant qu'il parcourt 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 des scripts s'appliquent: assurez-vous qu'il est exécutable avec chmod +x ./myscript.shet exécutez-le à partir du répertoire actuel via ./myscript.shou placez-le dans ~/binet exécutez source ~/.profile.

Sergiy Kolodyazhnyy
la source
Si vous imprimez le nom de fichier complet, qu'est-ce que "l'extension" vous apporte? Peut-être voulez-vous vraiment les informations MIME qui "$(file "$i")"(dans le script ci-dessus en tant que deuxième partie d'un printf) retourneraient?
pbhj
1
@pbhj Pour moi personnellement? Rien. Mais OP qui a posé la question posée output the path, filename, extension, filesize , donc la réponse correspond à ce qui est demandé. :)
Sergiy Kolodyazhnyy
12

Vous pouvez utiliser findpour faire le travail

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

Cela vous aidera si vous souhaitez simplement répertorier tous les fichiers avec leur taille.

-execvous permettra d'exécuter une commande ou un script personnalisé pour chaque fichier \;utilisé pour analyser les fichiers un par un, vous pouvez les utiliser +;si vous souhaitez les concaténer (signifie les noms de fichiers).

Rajesh Rajendran
la source
C'est bien, mais ne répond pas à toutes les exigences OP mentionnées.
αғsнιη
1
@ αғsнιη Je viens de lui donner un modèle sur lequel travailler. Je sais, ce n'est pas une réponse complète à cette question, car je pense que la question elle-même a une large portée.
Rajesh Rajendran
6

Avec findseulement.

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
αғsнιη
la source
2
Élégant et simple (si vous connaissez find -printf). +1
David Foerster du
1

Si vous connaissez la profondeur de l'arbre, le moyen le plus simple sera d'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 des tests simples pour identifier les fichiers avec lesquels vous souhaitez travailler et faire tout ce dont vous avez besoin avec eux.

Benubird
la source
"cela ne fonctionnera pas si l'un de vos noms de fichiers contient des espaces" ... parce que vous avez oublié de citer vos variables! Utilisez "$ i" au lieu de $i.
muru
@muru non, la raison pour laquelle cela ne fonctionne pas est que la boucle "for" se divise en espaces - " / 'est développé dans une liste de tous les fichiers séparés par des espaces. Vous pouvez contourner cela, par exemple en jouant avec l'IFS, mais à ce stade, vous pourriez aussi bien utiliser find.
Benubird
@ pa4080 n'est pas pertinent pour cette réponse, mais cela semble quand même super utile, merci!
Benubird
Je pense que vous ne comprenez pas comment ça for i in */*marche. Ici, testez-le:for i in */*; do printf "|%s|\n" "$i"; done
muru
Voici une preuve de l'importance des guillemets: i.stack.imgur.com/oYSj2.png
pa4080
1

find peut le faire:

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

Jettes un coup d'oeil à man find 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"' {} \;
Katu
la source