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.
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.
Antworten:
Obwohl die find
Lö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.
1. Erstellen Sie eine ausführbare Skriptdatei mit dem Namen walk
, /usr/local/bin
auf 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
nano
: Shift+ Insertzum Einfügen; Ctrl+ Ound Enterzum Speichern; Ctrl+ Xzum Verlassen.2. Der Inhalt des Skripts walk
ist:
#!/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 $entry
Datei 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 printf
diese Variable usw .:
MY_VAR = "$ ( Befehl " $ {entry} ")" printf "% * s \ tDateigröße: \ t $ {YEL}% s $ {NCL} \ n" $ ((Einzug + 4)) '' "$ MY_VAR"
Oder direkt printf
die Ausgabe des Befehls:
printf "% * s \ tDateigröße: \ t $ {YEL}% s $ {NCL} \ n" $ ((Einzug + 4)) '' "$ ( Befehl " $ {entry} ")"
Der aufgerufene Abschnitt zum Aufrufen Colourise the output
initialisiert einige Variablen, die im printf
Befehl 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 walk
das aktuelle Verzeichnis aus:
walk # You shouldn't use any argument,
walk ./ # but you can use also this format
So führen Sie ein walk
beliebiges untergeordnetes Verzeichnis aus:
walk <directory name>
walk ./<directory name>
walk <directory name>/<sub directory>
So führen Sie walk
ein anderes Verzeichnis aus:
walk /full/path/to/<directory name>
So erstellen Sie eine Textdatei basierend auf der walk
Ausgabe:
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:
Ich bin ein wenig ratlos darüber, warum es noch niemand gepostet hat, aber ich habe tatsächlich bash
rekursive Fähigkeiten, wenn Sie die globstar
Option 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 du
abzurufen 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.sh
und aus aktuellen Verzeichnis laufen über ./myscript.sh
oder es an seinem Platz ~/bin
und laufen source ~/.profile
.
"$(file "$i")"
(im obigen Skript als zweiter Teil eines printf) zurückkehren würden?
output the path, filename, extension, filesize
, stimmt mit der gestellten Frage überein. :)
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.
-exec
Damit 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).
Mit find
nur.
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
find -printf
). +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.
$i
.
for i in */*
funktioniert. Hier, testen Sie es:for i in */*; do printf "|%s|\n" "$i"; done
find
kann dies tun:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'
Schauen Sie sich man find
andere 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"' {} \;