Rekursives Bash-Skript zum Sammeln von Informationen zu jeder Datei in einer Verzeichnisstruktur


14

Wie arbeite ich mich rekursiv durch einen Verzeichnisbaum und führe einen bestimmten Befehl für jede Datei aus und gebe den Pfad, den Dateinamen, die Erweiterung, die Dateigröße und einen anderen bestimmten Text in einer einzelnen Datei in Bash aus.


lol, danke für die Bearbeitung; Ich werde der Erste sein, der zugibt, dass ich Dinge zu kompliziert mache, weil ich es gewohnt bin, 800 irrelevante Fragen in der Welt der Hoomans zu stellen. also versuche ich die offensichtlichen in den fragen zu beantworten; Ich werde es aber lernen :-)
SPOOKYINESS

1
OK, ich denke, die Frage ist ziemlich klar, was zu tun ist, gehen Sie durch den Verzeichnisbaum und geben Sie Informationen zu jeder Datei aus. Die Frage ist ziemlich klar, und wenn man die Anzahl der Antworten berücksichtigt, verstehen die Leute sie ziemlich gut. Die 3 Stimmen für unklar sind wirklich nicht zu dieser Frage verdient
Sergiy Kolodyazhnyy

Antworten:


15

Obwohl die findLösungen einfach und leistungsstark sind, habe ich beschlossen, eine kompliziertere Lösung zu erstellen, die auf dieser interessanten Funktion basiert , die ich vor einigen Tagen gesehen habe.

  • Weitere Erklärungen und zwei weitere Skripte, die auf dem aktuellen Stand basieren, finden Sie hier .

1. Erstellen Sie eine ausführbare Skriptdatei mit dem Namen walk, /usr/local/binauf die Sie als Shell-Befehl zugreifen können:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • Kopieren Sie den folgenden Skriptinhalt und verwenden Sie ihn in nano: Shift+ Insertzum Einfügen; Ctrl+ Ound Enterzum Speichern; Ctrl+ Xzum Verlassen.

2. Der Inhalt des Skripts walkist:

#!/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. Erklärung:

  • Der Hauptmechanismus der walk()Funktion wird von Zanna in ihrer Antwort ziemlich gut beschrieben . Also werde ich nur den neuen Teil beschreiben.

  • Innerhalb der walk()Funktion habe ich diese Schleife hinzugefügt:

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

    Das heißt, für jede $entryDatei wird die Funktion ausgeführt file_specification().

  • Die Funktion file_specification()besteht aus zwei Teilen. Der erste Teil bezieht sich auf den Dateinamen, den Pfad, die Größe usw. Der zweite Teil gibt die Daten in gut formatierter Form aus. Zum Formatieren der Daten wird der Befehl verwendet printf. Und wenn Sie das Skript optimieren möchten, sollten Sie diesen Befehl lesen - zum Beispiel diesen Artikel .

  • Die Funktion file_specification()ist ein guter Ort, an dem Sie den spezifischen Befehl eingeben können, der für jede Datei ausgeführt werden soll . Verwenden Sie dieses Format:

    Befehl "$ {entry}"

    Oder Sie können die Ausgabe des Befehls als Variable speichern und dann printfdiese Variable usw .:

    MY_VAR = "$ ( Befehl " $ {entry} ")"
    printf "% * s \ tDateigröße: \ t $ {YEL}% s $ {NCL} \ n" $ ((Einzug + 4)) '' "$ MY_VAR"

    Oder direkt printfdie Ausgabe des Befehls:

    printf "% * s \ tDateigröße: \ t $ {YEL}% s $ {NCL} \ n" $ ((Einzug + 4)) '' "$ ( Befehl " $ {entry} ")"

  • Der aufgerufene Abschnitt zum Aufrufen Colourise the outputinitialisiert einige Variablen, die im printfBefehl zum Färben der Ausgabe verwendet werden. Mehr dazu finden Sie hier .

  • Am unteren Rand des Skripts wird eine zusätzliche Bedingung hinzugefügt, die sich mit absoluten und relativen Pfaden befasst.

4. Anwendungsbeispiele:

  • So führen Sie walkdas aktuelle Verzeichnis aus:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • So führen Sie ein walkbeliebiges untergeordnetes Verzeichnis aus:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • So führen Sie walkein anderes Verzeichnis aus:

    walk /full/path/to/<directory name>
  • So erstellen Sie eine Textdatei basierend auf der walkAusgabe:

    walk > output.file
  • So erstellen Sie eine Ausgabedatei ohne Farbcodes ( Quelle ):

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

5. Demonstration der Nutzung:

Bildbeschreibung hier eingeben


Das ist eine Menge Arbeit, sieht aber gut aus. Gut gemacht !
Sergiy Kolodyazhnyy

Welchen Prozess verwenden Sie, um diese GIFs @ pa4080 zu erstellen?
pbhj

@pbhj, unter Ubuntu benutze ich Peek, es ist einfach und nett, aber manchmal stürzt es ab und hat keine Bearbeitungsfähigkeiten. Die meisten meiner GIFs werden unter Windows erstellt, wo ich das Fenster der VNC-Verbindung aufzeichne. Ich habe einen separaten Desktop-Computer, den ich hauptsächlich für die Erstellung von MS Office- und GIF- Dateien verwende :) Das Tool, das ich dort verwende, ist ScreenToGif . Es ist Open Source, kostenlos und verfügt über leistungsstarke Editor- und Verarbeitungsmechanismen. Leider kann ich kein Tool wie ScreenToGif für Ubuntu finden.
pa4080

13

Ich bin ein wenig ratlos darüber, warum es noch niemand gepostet hat, aber ich habe tatsächlich bashrekursive Fähigkeiten, wenn Sie die globstarOption aktivieren und **glob verwenden. Als solches können Sie ein (fast) reines bash Skript schreiben , das diesen rekursiven Globstar wie folgt verwendet:

#!/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

Beachten Sie, dass wir hier die Parametererweiterung verwenden, um die gewünschten Teile des Dateinamens abzurufen, und uns nicht auf externe Befehle verlassen, außer die Dateigröße mit duabzurufen und die Ausgabe mit zu bereinigen awk.

Und während es Ihren Verzeichnisbaum durchläuft, sollte Ihre Ausgabe ungefähr so ​​aussehen:

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

Standardregeln der Skriptgebrauch gelten: Stellen Sie sicher , ist es ausführbar mit chmod +x ./myscript.shund aus aktuellen Verzeichnis laufen über ./myscript.shoder es an seinem Platz ~/binund laufen source ~/.profile.


Wenn Sie den vollständigen Dateinamen drucken, welches Extra bietet Ihnen die "Erweiterung"? Vielleicht möchten Sie wirklich die MIME-Informationen, die "$(file "$i")"(im obigen Skript als zweiter Teil eines printf) zurückkehren würden?
pbhj

1
@pbhj Für mich persönlich? Nichts. Aber der OP, der die gestellte Frage gestellt hat output the path, filename, extension, filesize , stimmt mit der gestellten Frage überein. :)
Sergiy Kolodyazhnyy

12

Sie können verwenden find, um die Arbeit zu erledigen

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

Dies hilft Ihnen, wenn Sie nur alle Dateien mit der Größe auflisten möchten.

-execDamit können Sie einen benutzerdefinierten Befehl oder ein benutzerdefiniertes Skript für jede Datei ausführen, \;die zum Analysieren von Dateien nacheinander verwendet wird. Diese Option können Sie verwenden, +;wenn Sie sie verketten möchten (dh Dateinamen).


Dies ist schön, aber nicht auf alle genannten Anforderungen OP zu beantworten.
αғsнιη

1
@ αғsнιη Ich habe ihm gerade eine Vorlage zur Bearbeitung gegeben. Ich weiß, dies ist keine vollständige Antwort auf diese Frage, da ich der Meinung bin, dass die Frage selbst einen breiten Anwendungsbereich hat.
Rajesh Rajendran

6

Mit findnur.

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

Oder Sie könnten stattdessen Folgendes verwenden:

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
Elegant und schlicht (wenn Sie sich auskennen find -printf). +1
David Foerster

1

Wenn Sie wissen, wie tief der Baum ist, können Sie am einfachsten die Platzhalter verwenden *.

Schreiben Sie alles, was Sie tun möchten, als Shell-Skript oder als Funktion auf

function thing() { ... }

dann laufen for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done... etc

Innerhalb Ihrer Funktion / Ihres Skripts können Sie einige einfache Tests verwenden , um die Dateien herauszusuchen, mit denen Sie arbeiten möchten, und um alles zu tun, was Sie dazu benötigen.


"Das funktioniert nicht, wenn einer Ihrer Dateinamen Leerzeichen enthält" ... weil Sie vergessen haben, Ihre Variablen in Anführungszeichen zu setzen! Verwenden Sie "$ i" anstelle von $i.
Muru

@muru nein, der Grund, warum es nicht funktioniert, ist, dass die "for" -Schleife in Leerzeichen aufgeteilt wird - " / 'wird zu einer durch Leerzeichen getrennten Liste aller Dateien erweitert. Sie können dies umgehen, indem Sie z. B. mit dem IFS herumspielen. Aber an diesem Punkt können Sie auch einfach find verwenden.
Benubird,

@ pa4080 nicht relevant für diese Antwort, aber das sieht trotzdem super nützlich aus, danke!
Benubird

Ich denke du verstehst nicht wie das for i in */*funktioniert. Hier, testen Sie es:for i in */*; do printf "|%s|\n" "$i"; done
muru

Hier ist ein Beweis für die Bedeutung von Anführungszeichen: i.stack.imgur.com/oYSj2.png
pa4080

1

find kann dies tun:

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

Schauen Sie sich man findandere Dateieigenschaften an.

Wenn Sie die Erweiterung wirklich benötigen, können Sie Folgendes hinzufügen:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.