|
faehrmann
Anmeldungsdatum: 18. Januar 2016
Beiträge: 38
|
Hallo zusammen, in einem Bash-Script möchte ich gerne in einem Verzeichnis (inkl. aller Unterverzeichnisse) alle Dateien ermitteln und diese bearbeiten.
Da die Dateinamen u.a. auch Leerzeichen enthalten, habe ich mich für folgende Variante entschieden: find ${VERZEICHNIS} -type f -print0 | xargs -0 befehl1Hierbei kann ich aber nur einen Befehl ("befehl1") ausführen... In meinem Script müssen aber ca. 30 Befehle ausgeführt werden.
Kennt Ihr eine Möglichkeit, dies zu realisieren? Danke und Gruß, Daniel
|
|
misterunknown
Ehemalige
Anmeldungsdatum: 28. Oktober 2009
Beiträge: 4405
Wohnort: Sachsen
|
Die sauberste Variante die ich kenne:
| while IFS= read -r -d '' file; do
echo "Datei: '$file'"
# ...
done < <(find /dein/pfad -type f -print0)
|
|
|
LinuxIstSuper
Anmeldungsdatum: 20. Juli 2016
Beiträge: 58
|
Du könntest als diesen 1 Befehl doch einfach ein eigenes Script aufrufen lassen oder eine Funktion auf:
| find ${VERZEICHNIS} -type f -print0 | xargs -0 ./verzeichnisScript.sh
|
|
|
track
Anmeldungsdatum: 26. Juni 2008
Beiträge: 7174
Wohnort: Wolfen (S-A)
|
Der einfachste (Standard-) Weg ist, an find ... -exec einfach direkt ein Shellskript anzuhängen: find ${verzeichnis} -type f -exec bash -c 'echo "befehl 1 mit $0"; echo "befehl 2 mit $0"; echo "befehl 3 mit $0"' {} \;
Der Vorteil bei diesem Verfahren ist, dass man hier alle Möglichkeiten der Bash direkt nutzen kann. (einschließlich Parameter Expansion und was weiß ich alles) (und komplett handwerklich sauber - Stichwort "Quoting" - ist es natürlich auch.) LG, track
|
|
misterunknown
Ehemalige
Anmeldungsdatum: 28. Oktober 2009
Beiträge: 4405
Wohnort: Sachsen
|
track schrieb: Der einfachste (Standard-) Weg ist, an find ... -exec einfach direkt ein Shellskript anzuhängen
Gut, das ist aber bei >10 Operationen/Befehlen auch recht schnell unübersichtlich. Was sagst du zu meiner Lösung? Irgendwelche Einwände?
|
|
track
Anmeldungsdatum: 26. Juni 2008
Beiträge: 7174
Wohnort: Wolfen (S-A)
|
misterunknown schrieb: Gut, das ist aber bei >10 Operationen/Befehlen auch recht schnell unübersichtlich.
Ja, wenn man unbedingt alles in einer Zeile schreiben will schon. Aber man kann den Skript-Teil ja auch bequem umbrechen, da spricht ja nichts dagegen. Dann hast Du es auch wieder übersichtlich. Was sagst du zu meiner Lösung? Irgendwelche Einwände?
Nein, Einwände nicht. Deine Lösung hat sogar den Vorteil, dass das Skript für alle Dateien nur 1x aufgerufen wird, während meines für jede Datei neu startet. Dafür hast Du halt den Overhead mit der while read - Schleife. (und die möchte auf jeden Fall auch bitte korrekt geschrieben sein !) Und dass bei Dir der find - Befehl ganz hinten steht, das ist optisch nicht so hübsch. ("aber der Profi kennt das schon" 😉 ) Sauberst (!) "quoten" muss man in beiden Fällen so wie so, da gibt es keinen Unterschied. Ja, so mal ... LG, track
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
misterunknown schrieb: track schrieb: Der einfachste (Standard-) Weg ist, an find ... -exec einfach direkt ein Shellskript anzuhängen
Gut, das ist aber bei >10 Operationen/Befehlen auch recht schnell unübersichtlich.
Das hängt davon ab, wie man es gestaltet. Das Skript kann sich ja auch über mehrere Zeilen erstrecken: | find "$verzeichnis" -type f -exec bash -c '
echo "befehl 1 mit $0"
echo "befehl 2 mit $0"
echo "befehl 3 mit $0"
' {} \;
|
Was sagst du zu meiner Lösung? Irgendwelche Einwände?
Ich bin zwar nicht track, finde das aber unnötig umständlich, weil man dabei so viel beachten muss (IFS leeren, und zwei Optionen für read setzen). Außerdem funktioniert es so, wie Du es präsentiert hast, nur in der bash und anderen Shells, die über POSIX hinausgehen ODER die while-Schleife muss in einer Subshell laufen, was manchmal zu Problemen führt, weil das Elternskript dort gesetzte Variablen nicht sieht. Ich persönlich schreibe Skripte soweit möglich für die sh, da sie schlanker ist und durch die Implementierung des POSIX-Standards portabler sein sollte. Jedenfalls können bash, ksh, zsh und Konsorten alles ausführen, was auch die sh ausführt. (Kennt jemand Ausnahmen?) Ich ziehe es vor, find -exec ... + zu nutzen und mit einem Shell-Aufruf gleich mehrere Dateien zu erfassen: | find "$verzeichnis" -type f -exec bash -c '
for f; do
echo "befehl 1 mit $f"
echo "befehl 2 mit $f"
echo "befehl 3 mit $f"
done
' -- {} +
|
Ich habe auch schon Skripte geschrieben, die sich rekursiv selbst aufgerufen haben, so dass ich nur eine Datei brauche: | #!/bin/sh
if [ $# -eq 1 -a -d "$1" ]; then
exec find "$1" -type f -exec "$0" -- {} +
else
for f; do
echo "befehl 1 mit $f"
echo "befehl 2 mit $f"
echo "befehl 3 mit $f"
done
fi
|
Man muss da ein bisschen aufpassen mit dem Kriterium für den Einstieg in die Rekursion. In diesem Fall ist es kein Problem, aber wenn der find auch Verzeichnisse liefern soll, dann muss man das anders lösen. Hier mal ein Vorschlag, der bereits recht robust sein sollte und auch in der sh funktioniert: | #!/bin/sh
if [ $PARENT_PROC -eq $PPID ]; then
for f; do
echo "befehl 1 mit $f"
echo "befehl 2 mit $f"
echo "befehl 3 mit $f"
done
else
PARENT_PROC=$$ exec find "$1" -type f -exec "$0" -- {} +
fi
|
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17673
Wohnort: Berlin
|
faehrmann schrieb: find ${VERZEICHNIS} -type f -print0 | xargs -0 befehl1
In meinem Script müssen aber ca. 30 Befehle ausgeführt werden.
Kennt Ihr eine Möglichkeit, dies zu realisieren?
Tja, kommt etwas drauf an, ob die Befehle parallel laufen können, vielleicht sogar sollen. Vielleicht sollen sie das auch gerade nicht tun. Fast immer ist ein -print0 | xargs -0 unnötiges Gedöns und die Optionen -exec, -execdir, -ok oder -okdir von find äquivalent, nur kürzer. | find ${VERZEICHNIS} -type f -exec befehl1 {} ";" -exec befehl2 {} ";" -exec befehl3 {} ";"
|
führt auf jede Datei 3 Befehle aus, aber bei 30 Befehlen ist das unübersichtlich, wie schon allgemein festgestellt.
Die einfachste Lösung ist es oft, die 30 Befehle in ein Script zu schreiben und das aufzurufen:
| find ${VERZEICHNIS} -type f -exec script_mit_30_befehlen {} ";"
|
Um die Befehle parallel auszuführen verwendest Du statt ";" ein + nach dem Befehl, dann wird Dein Script nicht für jede Datei aufgerufen, sondern mit allen Dateien auf einmal: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | > ls
a
b
c
> find ${VERZEICHNIS} -type f -exec script {} ";"
# enstspricht
script a
script b
script c
# dagegen
> find ${VERZEICHNIS} -type f -exec script {} +
# enstspricht
script a b c
|
wobei sich find um das Quoting kümmert, also um Leerstellen im Namen musst Du Dir keine Sorgen machen und kannst auf print0/xargs also verzichten. Wenn die Befehle im Script parallelisiert werden, dann kann der letzte Befehl für die letzte Datei fertig sein, bevor der erste für die erste Datei fertig ist. Ob das relevant ist musst Du wissen. Bearbeitet von rklm: Geschweifte Klammern auf Wunsch nachgerüstet.
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
user_unknown schrieb:
Um die Befehle parallel auszuführen verwendest Du statt ";" ein + nach dem Befehl, dann wird Dein Script nicht für jede Datei aufgerufen, sondern mit allen Dateien auf einmal:
Das ergibt aber nicht automatisch Parallelität. Das muss man dann innerhalb des Skriptes regeln. Oder man nutzt xargs -P. Da find in einem Verzeichnisbaum typischerweise Dateien findet, die auf dem selben Medium liegen und die Verarbeitung irgendetwas damit zu tun hat, dass diese Dateien gelesen werden, kann es bei paralleler Verarbeitung sogar langsamer sein, weil ständig in verschiedenen Dateien gelesen werden muss. Wie immer hängt das natürlich vom Einzelfall ab.
wobei sich find um das Quoting kümmert, also um Leerstellen im Namen musst Du Dir keine Sorgen machen und kannst auf print0/xargs also verzichten.
Genau! Das ist der Charme davon, die Dateinamen an das Skript als Argumente zu übergeben (im Vergleich zu der Lösung mit read).
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17673
Wohnort: Berlin
|
rklm schrieb: user_unknown schrieb:
Um die Befehle parallel auszuführen verwendest Du statt ";" ein + nach dem Befehl, dann wird Dein Script nicht für jede Datei aufgerufen, sondern mit allen Dateien auf einmal:
Das ergibt aber nicht automatisch Parallelität. Das muss man dann innerhalb des Skriptes regeln. Oder man nutzt xargs -P.
Kommt drauf an, was man unter Parallelität versteht. Die Argumente werden alle auf einmal übertragen, wie oben gezeigt. Oder vielleicht etwas klarer(?)* so (mit meinem Prompt):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 |
t201:~/proj/mini/forum/fooba > cat script.sh
#!/bin/bash
echo Argumente": $*"
for f
do
{
echo $f
sleep 1
echo $f
} &
done
t201:~/proj/mini/forum/fooba > ls
a b c script.sh
t201:~/proj/mini/forum/fooba > find -exec ./script.sh {} +
Argumente: . ./c ./b ./script.sh ./a
.
./c
./b
t201:~/proj/mini/forum/fooba > ./script.sh
./a
./b
.
./c
./script.sh
./a
t201:~/proj/mini/forum/fooba > find -exec ./script.sh {} ";"
Argumente: .
.
Argumente: ./c
./c
Argumente: ./b
./b
Argumente: ./script.sh
./script.sh
Argumente: ./a
./a
t201:~/proj/mini/forum/fooba > .
./c
./b
./script.sh
./a
|
*In den Zeilen 21 und 40 findet keine Befehlseingabe statt. Wegen des Laufens im Hintergrund ist das Programm fertig gestartet und man bekommt den Prompt schon zurück - dann wird die folgende Aussage so auf den Bildschirm geschrieben, dass es aussieht, als wäre es eine Benutzereingabe. Am Ende muss man dann auch Enter drücken, um den Prompt zurückzubekommen.
Da find in einem Verzeichnisbaum typischerweise Dateien findet, die auf dem selben Medium liegen und die Verarbeitung irgendetwas damit zu tun hat, dass diese Dateien gelesen werden, kann es bei paralleler Verarbeitung sogar langsamer sein, weil ständig in verschiedenen Dateien gelesen werden muss. Wie immer hängt das natürlich vom Einzelfall ab.
Ack. PS: Oben habe ich die geschweiften Klammern vergessen - mal sehen, ob ich die noch nachtragen kann.
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
user_unknown schrieb: rklm schrieb:
Das ergibt aber nicht automatisch Parallelität. Das muss man dann innerhalb des Skriptes regeln. Oder man nutzt xargs -P.
Kommt drauf an, was man unter Parallelität versteht. Die Argumente werden alle auf einmal übertragen, wie oben gezeigt.
Ja, wenn Du das meinst. So habe ich es noch nie betrachtet. Ich würde da eher davon sprechen, dass die Dateinamen gleichzeitig übergeben werden. Jetzt können wir anfangen, über den Unterschied von "gleichzeitig" und "parallel" zu philosophieren... 😉
Oder vielleicht etwas klarer(?)* so (mit meinem Prompt):
Das ist ja genau das, was ich meine: man muss das Skript entsprechend schreiben, damit parallel ausgeführt wird.
1
2
3
4
5
6
7
8
9
10
11
12
13 |
t201:~/proj/mini/forum/fooba > cat script.sh
#!/bin/bash
echo Argumente": $*"
for f
do
{
echo $f
sleep 1
echo $f
} &
done
[...]
|
Man kann auch in der Zeile hinter dem done noch ein wait platzieren, so dass dieses Skript erst terminiert, wenn alle Kindprozesse fertig sind. Das ist nützlich, wenn es weitere Bearbeitungsschritte geben soll, die erst nach Beendigung dieses Schrittes anfangen dürfen. Ansonsten hat der Aufrufer keine Möglichkeit mitzubekommen, wann die Arbeit erledigt ist. Ein Szenario wäre, wenn alle Dateien erst einzeln verarbeitet werden müssen, was parallel passieren kann, und dann durch einen letzten Bearbeitungsschritt zusammen gefasst werden müssen.
PS: Oben habe ich die geschweiften Klammern vergessen - mal sehen, ob ich die noch nachtragen kann.
Scheint geklappt zu haben. 👍
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17673
Wohnort: Berlin
|
rklm schrieb: PS: Oben habe ich die geschweiften Klammern vergessen - mal sehen, ob ich die noch nachtragen kann.
Scheint geklappt zu haben. 👍
Nein, hat nicht geklappt, meinte mit "oben" das vorige Posting https://forum.ubuntuusers.de/post/8697408/
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
user_unknown schrieb:
Nein, hat nicht geklappt, meinte mit "oben" das vorige Posting https://forum.ubuntuusers.de/post/8697408/
Besser so?
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17673
Wohnort: Berlin
|
Oh, welch ein Service! Ham wir denn schon Weihnachten? 😉 Vielen Dank.
|
|
faehrmann
(Themenstarter)
Anmeldungsdatum: 18. Januar 2016
Beiträge: 38
|
Hallo zusammen, erstmal vielen Dank für Eure zahlreichen Antworten. Ich habe jetzt genug Denkanstöße von Euch erhalten und werde mir überlegen, welche Variante ich umsetzen werde. Euch alles schon jetzt frohe Weihnachten! Liebe Grüße, der Fährmann
|