wxpte
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1175
Wohnort: Schäl Sick
|
Hallo liebe Community, ich habe folgende Situation: in einem größeren Archiv, unterteilt nach Jahresordnern, sind Dokumente abgelegt, welche ein Datum sowie zwei bis drei Schlüsselworte im Namen enthalten. Des öfteren bekomme ich Listen mit etwa 10 bis 20 Einträgen zum Heraussuchen von Dokumenten. Das meiste sind vollständige Namen, manche Teile von Dateinamen, die zwei oder drei Treffer erzielen; und in Einzelfällen ist der Name auch falsch geschrieben, so dass gar kein Treffer erzielt wird. Um die Liste automatisch abzuarbeiten, habe ich mir ein kleines Skript angelegt:
| #!/bin/bash
while read d
do
if [ $(find /windows2 -name "*$d*" | wc -l) -gt 0 ]
then
find /windows2 -name "*$d*" -exec cp -au {} . \;
else
echo "Die Datei $d wurde nicht gefunden."
fi
done < liste.csv
|
Damit ich in den oben genannten Einzelfällen weiß, welche Datei nicht gefunden wurde, lasse ich sie mir auf dem Bildschirm ausgeben. Jedoch benötige ich dazu das Konstrukt in der if-Bedingung, denn die Fehlermeldung, die das Skript bei einer Kopieranweisung mit leerem Ergebnis erzeugt, ist nicht aufschlussreich. Überlegt habe ich schon, das Ergebnis von find in einer Zwischenvariablen abzulegen, und dann cp von find abzukoppeln; aber das funktioniert nicht, sobald mehr als ein Treffer erzielt wird. Leider ändert sich auch der Exit-Status nicht, wenn die Suche ergebnislos ist. Gibt es eine Möglichkeit, das Verhalten von find davon abhängig zu machen, ob die Suche einen Treffer hat oder nicht? Grüße, WinXP to Edgy.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Ein gangbarer Weg ist, das Ergebnis von find in eine temporäre Datei zu schreiben und dann deren Länge auf 0 zu prüfen: 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | #!/bin/sh
tmp="$(tempfile -d /tmp)"
trap "rm -f '$tmp'" 0
while read d; do
find /windows2 -name "*$d*" -print0 >|"$tmp"
if [ $(stat -c %s "$tmp") -eq 0 ]; then
echo "Die Datei $d wurde nicht gefunden."
else
xargs -r0 -a "$tmp" cp -aut .
fi
done < liste.csv
|
Anmerkungen
Ich habe "-print0" und "-0" verwendet, um das Skript robust gegen ungewöhnliche Zeichen in Dateinamen zu machen. "cp -t {dir}" bietet sich hier an, weil xargs dann einfach alle Argumente hinten an die Kommandozeile von cp anhängen kann.
Ein anderer Ansatz wäre, find eine Ausgabe produzieren zu lassen, wenn etwas gefunden wird, und darauf zu prüfen: | while read d; do
find /windows2 -name "*$d*" -exec cp -aut . {} + -printf x | fgrep -q x \
|| echo "Die Datei $d wurde nicht gefunden."
done < liste.csv
|
Das ist eigentlich noch eleganter, weil es ohne die temporäre Datei auskommt. ☺
|
linuxer-nds
Anmeldungsdatum: 1. Februar 2015
Beiträge: 33
|
Hallo,
eine Lösung ist mir auch noch eingefallen: | #!/bin/bash
while read d
do
find dir/ -name "*$d*" -type f | grep "[[:alnum:]]" | xargs -r -I{} cp -au {} .;
if [ ${PIPESTATUS[1]} -ne 0 ]; then echo "Die Datei $d wurde nicht gefunden."; fi
done < liste.csv
|
MfG
|
wxpte
(Themenstarter)
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1175
Wohnort: Schäl Sick
|
Hallo Robert, deine Vorschläge enthielten genau die Denkanstöße und Informationen, die mir noch fehlten. Dabei gefällt mir Lösung 2 besonders gut: wenn find keinen brauchbaren Exit-Status für meine Zwecke zurückliefert, dann schiebt man einfach fgrep dazwischen, das mit Leerergebnissen eindeutig besser umgehen kann. Saubere Lösung. 👍 Mit dem Verstehen des ersten Vorschlags tue ich mich allerdings ein bisschen schwerer: dass es im Zusammenhang mit xargs notwendig ist, eine temporäre Datei zu erstellen, habe ich inzwischen herausgefunden. Mit einer Variablen scheint das nicht zu funktionieren:
| #!/bin/bash
while read d
do
se=$(find /windows2 -name "*$d*" -print0)
if [ ! -z "$se" ]
then
echo $se | xargs -r0 -i cp -aut . {}
else
echo "Die Datei $d wurde nicht gefunden."
fi
done < liste.csv
|
Das wird mir dann leider um die Ohren gehauen:
cp: der Aufruf von stat für »/windows2/Daten/editor/Abstand.txt\n“ ist nicht möglich: Datei oder Verzeichnis nicht gefunden
cp: der Aufruf von stat für »/windows2/Daten/DOS-Test/Auftraege/Bericht.txt\n“ ist nicht möglich: Datei oder Verzeichnis nicht gefunden Keine Ahnung, woran das liegt, ohne die print0-Option läuft es noch sauber durch:
| #!/bin/bash
while read d
do
se=$(find /windows2 -name "*$d*")
if [ ! -z "$se" ]
then
cp -aut . $se
else
echo "Die Datei $d wurde nicht gefunden."
fi
done < liste.csv
|
Da ich dein Skript mit der temporären Datei besser verstehen möchte, noch zwei Fragen:
>| # Zeile 7 deines ersten Vorschlags @linuxer-nds: Deine Variante ist ebenfalls interessant, hat allerdings einen Nachteil: sobald ich die Option -print0 ins Spiel bringe, steigt es ebenso aus, wie mein Versuch mit der Variablen. Schon einmal ein Dankeschön an euch beide, drei Dinge habe ich bereits gelernt:
mit der Option -t kann man den Kopierbefehl dazu bringen, auch mehrere Argumente anzunehmen indem man die Ausgabe von -print bzw. -printf in eine Pipe schickt, kann ein anderes Programm einen geeigneten Exit-Status auslesen mit dem Array PIPESTATUS[] kann man ggf. den Exit-Status auch direkt aus der Pipe lesen.
Grüße, WinXP to Edgy.
|
linuxer-nds
Anmeldungsdatum: 1. Februar 2015
Beiträge: 33
|
Hallo WinXP to Edgy
@linuxer-nds: Deine Variante ist ebenfalls interessant, hat allerdings einen Nachteil: sobald ich die Option -print0 ins Spiel bringe, steigt es ebenso aus, wie mein Versuch mit der Variablen.
Wenn die ausgegebenen Zeilen von find mit einem Nullbyte enden, muss man zusätzlich grep mit Option -z aufrufen, bzw. xargs mit -0.
Aber auch ohne Nullbyte passiert wahrscheinlich nichts, bei meinem Test hatte ich zB. keine Probleme mit Leerzeichen in Dateinamen. Ok, null-terminated ist wohl doch sicherer, zumindest vor dem Aufruf von cp: | #!/bin/bash
while read d; do
{ find dir/ -name "$d" -type f | grep "[[:alnum:]]" || echo "Die Datei $d wurde nicht gefunden." >&2; } | tr '\n' '\0' | xargs -r0 -I{} cp -au {} .;
done < liste.csv
|
So spart man auch noch das if-then. MfG
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
WinXP to Edgy schrieb:
Mit dem Verstehen des ersten Vorschlags tue ich mich allerdings ein bisschen schwerer: dass es im Zusammenhang mit xargs notwendig ist, eine temporäre Datei zu erstellen, habe ich inzwischen herausgefunden. Mit einer Variablen scheint das nicht zu funktionieren:
Ja, weil die Null-Bytes dabei verloren gehen: $ printf 'a\000b\000'
ab$
$ printf 'a\000b\000' | od -t x1c
0000000 61 00 62 00
a \0 b \0
0000004
$ v="$(printf 'a\000b\000')"
$ echo -n "$v" | od -t x1c
0000000 61 62
a b
0000002
Keine Ahnung, woran das liegt, ohne die print0-Option läuft es noch sauber durch:
s.o. - ohne die Null-Bytes fehlen die Trenner zwischen den einzelnen Dateinamen.
Da ich dein Skript mit der temporären Datei besser verstehen möchte, noch zwei Fragen:
Beim Terminieren. Siehe Manpage der Shell.
Steht auch in der Manpage der Shell.
indem man die Ausgabe von -print bzw. -printf in eine Pipe schickt, kann ein anderes Programm einen geeigneten Exit-Status auslesen
Ich weiß nicht genau, was Du damit meinst; es klingt für mich komisch. Es ist in meinem Beispiel ja so, dass der find den Exit-Status selbst produziert, der dann durch den Oder-Operator (|| ) ausgewertet wird.
mit dem Array PIPESTATUS[] kann man ggf. den Exit-Status auch direkt aus der Pipe lesen.
Das braucht man hier allerdings nicht (s.u.). linuxer-nds schrieb:
| #!/bin/bash
while read d
do
find dir/ -name "*$d*" -type f | grep "[[:alnum:]]" | xargs -r -I{} cp -au {} .;
if [ ${PIPESTATUS[1]} -ne 0 ]; then echo "Die Datei $d wurde nicht gefunden."; fi
done < liste.csv
|
Die ist leider viel fragiler bezüglich Sonderzeichen (z.B. Newline) in Dateinamen als meine Lösungen. Wenn, müsstest Du find ... -print0 , grep -z ... und xargs -0 ... nutzen. Die Variable $PIPESTATUS brauchst Du auch nicht, das kannst Du mit einem "||" am Ende der Pipeline mit find erledigen, so wie ich das gemacht habe, wenn Du den grep ans Ende packst. Viel schwerwiegender ist aber, dass der grep hier auch an der falschen Stelle steht, da man die Liste der Dateinamen, die find liefert, überhaupt nicht filtern will. Deshalb wird ja in meinem Beispiel erst der cp ausgeführt und dann per -printf ein Zeichen pro Datei ausgegeben und schließlich schaut der fgrep nach, ob mindestens ein Zeichen gekommen ist. Gute Nacht! robert
|
wxpte
(Themenstarter)
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1175
Wohnort: Schäl Sick
|
rklm schrieb: Es ist in meinem Beispiel ja so, dass der find den Exit-Status selbst produziert, der dann durch den Oder-Operator (|| ) ausgewertet wird.
Wirklich? find produziert doch in jedem Fall den Exit-Status 0, egal ob etwas gefunden wird, oder nicht. Das war ja auch mein Problem in der Ausgangsfrage. Das Programm, das den zur Verzweigung passenden Exit-Status an den Oder-Operator schickt, ist vielmehr fgrep , oder sehe ich das falsch? rklm schrieb:
Steht auch in der Manpage der Shell.
Könntest du bitte eine genauere Fundstelle angeben? Im Abschnitt REDIRECTION gibt es alle möglichen Kombinationen, diese aber nicht.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
WinXP to Edgy schrieb: rklm schrieb: Es ist in meinem Beispiel ja so, dass der find den Exit-Status selbst produziert, der dann durch den Oder-Operator (|| ) ausgewertet wird.
Wirklich? find produziert doch in jedem Fall den Exit-Status 0, egal ob etwas gefunden wird, oder nicht. Das war ja auch mein Problem in der Ausgangsfrage. Das Programm, das den zur Verzweigung passenden Exit-Status an den Oder-Operator schickt, ist vielmehr fgrep , oder sehe ich das falsch?
Meine Formulierung war nicht ganz korrekt: gemeint war die Pipeline, die mit find beginnt. Der Exit-Status, der dann ausgewertet wird, kommt natürlich vom fgrep . Ciao robert
|
wxpte
(Themenstarter)
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1175
Wohnort: Schäl Sick
|
Stimmt natürlich: fgrep liest den Exit-Status nicht aus, sondern erzeugt ihn erst. Die Auswertung übernimmt dann der Oder-Operator. Insofern hast du recht. Mit deinem Hinweis auf die man-pages komme ich leider nicht weiter: wie erwähnt, finde ich im Abschnitt REDIRECTION nichts passendes. Also habe ich in meiner Verzweiflung einmal
man bash | grep -C10 ">|" versucht, und drei Treffer erhalten, die aber alle voraussetzen, dass man bereits weiß, was die Zeichenfolge bewirkt.
apropos redirection Ungültiges MakroDieses Makro ist nicht verfügbar keine Antwort
man -k redirection Ungültiges MakroDieses Makro ist nicht verfügbar keine Antwort
man -f redirection
redirection: nichts passendes.
|
ExcitedSpoon
Anmeldungsdatum: 17. Juli 2010
Beiträge: 226
Wohnort: /home/berlin
|
Hi, hier die Stelle zu >| aus der sh -manpage:
Redirecting Output
Redirection of output causes the file whose name results from the
expansion of word to be opened for writing on file descriptor n, or the
standard output (file descriptor 1) if n is not specified. If the file
does not exist it is created; if it does exist it is truncated to zero
size.
The general format for redirecting output is:
[n]>word
If the redirection operator is >, and the noclobber option to the set
builtin has been enabled, the redirection will fail if the file whose
name results from the expansion of word exists and is a regular file.
If the redirection operator is >|, or the redirection operator is > and
the noclobber option to the set builtin command is not enabled, the
redirection is attempted even if the file named by word exists.
|
wxpte
(Themenstarter)
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1175
Wohnort: Schäl Sick
|
Das heißt, man benötigt diese Kombination eigentlich nur, wenn man zuvor die Shell mit der Option -C aufgerufen hat? Das hieße dann doch auch, die Option müsste auch im Shebang stehen, damit >| gegenüber einem simplen > überhaupt einen Effekt hat.
|
ExcitedSpoon
Anmeldungsdatum: 17. Juli 2010
Beiträge: 226
Wohnort: /home/berlin
|
WinXP to Edgy schrieb: Das heißt, man benötigt diese Kombination eigentlich nur, wenn man zuvor die Shell mit der Option -C aufgerufen hat? Das hieße dann doch auch, die Option müsste auch im Shebang stehen, damit >| gegenüber einem simplen > überhaupt einen Effekt hat.
Naja, du könntest im Laufe deines Scripts ja mit set -C den noclobber aktivieren. Da dein Problem eventuell Teil eines größeren Scripts ist könnte es ja sein, dass du vorher mit set -C arbeitest, von daher ist >| schon nicht verkehrt.
|
wxpte
(Themenstarter)
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1175
Wohnort: Schäl Sick
|
Danke für die Antwort. Jetzt weiß ich schon einmal, wofür man so etwas braucht. ExcitedSpoon schrieb: Da dein Problem eventuell Teil eines größeren Scripts ist könnte es ja sein, dass du vorher mit set -C arbeitest ...
Naja, anhand der doofen Fragen, die ich hier so stelle, ist das eher nicht zu vermuten. 😀
|
ExcitedSpoon
Anmeldungsdatum: 17. Juli 2010
Beiträge: 226
Wohnort: /home/berlin
|
WinXP to Edgy schrieb: Naja, anhand der doofen Fragen, die ich hier so stelle, ist das eher nicht zu vermuten. 😀
Es gibt keine doofen Fragen, nur dumme Antworten 😉. Grüße
|
linuxer-nds
Anmeldungsdatum: 1. Februar 2015
Beiträge: 33
|
Hallo, das Thema ist ja gelöst, aber jetzt möchte ich doch noch mal antworten. rklm schrieb: Viel schwerwiegender ist aber, dass der grep hier auch an der falschen Stelle steht, da man die Liste der Dateinamen, die find liefert, überhaupt nicht filtern will. Deshalb wird ja in meinem Beispiel erst der cp ausgeführt und dann per -printf ein Zeichen pro Datei ausgegeben und schließlich schaut der fgrep nach, ob mindestens ein Zeichen gekommen ist.
An sich ist das ja auch kein Filter, es werden ja alle find-Ergebnisse durchgelassen, es sei denn, man hätte Dateinamen wie "–./&&&#+++",
d.h. ohne Buchstaben und Zahlen. Aber das ist ja ausgeschlossen: WinXP to Edgy schrieb: in einem größeren Archiv, unterteilt nach Jahresordnern, sind Dokumente abgelegt, welche ein Datum sowie zwei bis drei Schlüsselworte im Namen enthalten.
Mit grep wird nur getestet, ob die Ausgabe von find ein Leerstring ist, was ja sinnvoll ist, da | printf "" | xargs -0 -I{} cp -v {} dir/
|
bzw. find -exec cp... bei einem Leerstring keine Fehlermeldung produzieren. Der grep lässt sich noch optimieren:
| find dir/ -name "*$d*" -type f -print0 | grep -z "^." | xargs -r0 -I{} cp -au {} .;
|
Deswegen ist die Lösung mit PIPESTATUS-Test vielleicht eine Überlegung wert, falls man keine find -exec Konstruktion verwenden möchte. MfG
|