View previous topic :: View next topic |
Author |
Message |
slick Bodhisattva
Joined: 20 Apr 2003 Posts: 3495
|
Posted: Fri Oct 19, 2012 3:07 pm Post subject: Bash: lange Listen auf langsamen Rechner & grep |
|
|
Ich habe ein Script welches nach folgendem Muster arbeitet:
Code: | cat list.txt | while read file ; do
if ! cat files.log | grep "^${file}$" &> /dev/null ; then
$Kommando && echo "${file}" >> files.log
fi
done |
Das läuft soweit ganz gut. Es werden jedoch mehrere zehntausend Dateien auf einem sehr langsamen Rechner bearbeitet. Das Problem ist nun, das nach einem Stop des Script und erneutem Start mal eben gefühlte 30 min. vergehen bevor es wieder an der Position fortfährt wo es abgebrochen wurde. Logisch, den es muss ja die potenzierte Anzahl der Zeilen bis zu dieser Position mit grep durchsuchen. Da kommt was zusammen.
Hat jemand eine Idee wie ich dieses Konstrukt schneller machen kann, insbesondere unter der Annahme das ich dieses Script während des Durchlaufs anhalten und neustarten muss. |
|
Back to top |
|
|
Finswimmer Bodhisattva
Joined: 02 Sep 2004 Posts: 5467 Location: Langen (Hessen), Germany
|
Posted: Fri Oct 19, 2012 3:24 pm Post subject: |
|
|
Du könntest die list.txt zunächst als Sicherung kopieren, dann in der Schleife die bearbeitete Zeile löschen.
Ich denke, das sollte mit sed gehen. Wenn du das Skript abbrichst, hast du eine Datei, die um X Zeilen kleiner ist.
Alternativ könntest du dir die Zeilennummer in einer separaten Datei speichern und beim erneuten Aufruf diese Anzahl an Zeilen überspringen.
Viele Grüße
Tobi
P.S: Schön, dass du mal wieder da bist
EDIT:
Code: | if ! cat files.log | grep "^${file}$" &> /dev/null ; then |
ist für mich gefühlt schlechter als
Code: | if [ $(cat files.log | grep "^${file}$" -c) -eq 0 ] ; then |
zumindest müsste so auf der Bash nicht die Ausgabe des Grep-Befehls verarbeitet werden, sondern nur ein Integer-Vergleich durchgeführt werden.
Ob und inwieweit das was bringt, kann ich nicht beurteilen. _________________ Bitte auf Rechtschreibung, korrekte Formatierung und Höflichkeit achten!
Danke |
|
Back to top |
|
|
slick Bodhisattva
Joined: 20 Apr 2003 Posts: 3495
|
Posted: Fri Oct 19, 2012 5:44 pm Post subject: |
|
|
Also ein -m1 bringt schonmal deutlich was. Dann hört er beim ersten Treffer aus, was ja auch reicht.
Code: | if ! cat files.log | grep -m 1 "^${file}$" &> /dev/null ; then |
EDIT:
Deine Version ist messbar langsamer, auch mit -m1.
Btw. ich war nie wirklich weg |
|
Back to top |
|
|
py-ro Veteran
Joined: 24 Sep 2002 Posts: 1734 Location: Velbert
|
Posted: Fri Oct 19, 2012 10:30 pm Post subject: |
|
|
Das cat weglassen sollte auch nochmal helfen, wieder ein Prozess weniger:
Code: | if ! grep -m 1 "^${file}$" files.log &> /dev/null ; then |
Die Version oben ist übrigens langsamer, weil [ zusätzlichen code erzeugt, gibt auch nur nen returncode zurück. Außerdem wird jedesmal mit $() eine Sub-Shell erzeugt. |
|
Back to top |
|
|
mv Watchman
Joined: 20 Apr 2005 Posts: 6747
|
Posted: Sat Oct 20, 2012 3:27 pm Post subject: |
|
|
slick wrote: | Also ein -m1 bringt schonmal deutlich was. Dann hört er beim ersten Treffer aus, was ja auch reicht. |
Wird durch -q impliziert, was extra für solche Dinge gedacht ist. Und besonders hier ist der "useless use of cat" störend, da die Pipe unnötig ist und im Trefferfall abgebrochen wird. Außerdem kann man sich das "^ \$" schenken, wenn man -x benutzt. Ob die Option -F vielleicht sogar gewünscht ist, kann ich dem Kontext nicht entnehmen; schneller wäre es damit wohl allemal:
Code: | if ! grep -q -x -F -- "${file}" files.log
then ...
fi |
Edit: Für mich sieht das Problem so aus, dass man statt einer Fileliste lieber ein Hash-Array nehmen sollte - dann braucht man "praktisch" nur konstante Zeit. Hash-Arrays gibt es zumindest in zsh (oder natürlich awk oder perl), aber wenn es denn unbedingt bash sein muss, auch in neueren Bash-Versionen. Aber da hier Zeit ein Thema zu sein scheint, ist bash so ziemlich die schlechteste Wahl: Ich würde eher zu zsh (oder ggf. auch perl oder notfalls awk) raten. |
|
Back to top |
|
|
toralf Developer
Joined: 01 Feb 2004 Posts: 3922 Location: Hamburg
|
Posted: Mon Oct 22, 2012 9:22 am Post subject: |
|
|
Jungs, Detailverbesserungen bringen hier gar nix - wenn der Lösungsansatz selbst nicht für viele Dateien taugt.
Besser (wenn vielleicht auch noch nicht gut genug) ist : Code: | [tfoerste ~]$ cat list.txt
file1
file2
file3
[tfoerste ~]$ cat files.log
file3
file4
[tfoerste ~]$ diff list.txt files.log | grep '^<' | cut -f2 -d ' ' | xargs -n1 echo do something with
do something with file1
do something with file2
|
|
|
Back to top |
|
|
py-ro Veteran
Joined: 24 Sep 2002 Posts: 1734 Location: Velbert
|
Posted: Mon Oct 22, 2012 9:25 am Post subject: |
|
|
Naja, um eine bessere Lösung zu generieren bräuchte man mehr Informationen über Ausgangslage und Ziel. |
|
Back to top |
|
|
slick Bodhisattva
Joined: 20 Apr 2003 Posts: 3495
|
Posted: Mon Oct 22, 2012 3:54 pm Post subject: |
|
|
Also das ursprüngliche Ziel ist das rekursive abarbeiten in einem Verzeichnisbaum. Vereinfacht sah das so aus:
Code: | find ./ -type f | while read file ; do
[..]
done |
Das ganze auf einem NFS-Mount auf einem kleinem schwachen headless System. Dabei kommen bis zu 100.000 Datein raus. Um mir das durchsuchen jedesmal zu sparen habe ich die Ausgabe von find in die list.txt gepipt und später nur statt dem find die liste verwendet. Die (gefundenen) Dateien ansich werden nicht angefaßt, sie werden nur "lesend" bearbeitet. In der Regel bleibt sowohl das Dateisystem wie auch die Liste im read-only Zugriff. u.U. stoppe ich das Script aber auch, sortiere die Liste nach irgendwelchen Kriterien um (um bestimmte Files zu priorisieren) und starte das Script neu. (Daher fallen Sachen wie 'Zeilennummer merken' eigentlich aus). Bearbeitet werden soll jedes File in der Liste nur 1x.
Ich hatte auch schon die Idee mit Symlinks o.ä. die bearbeiteten zu "markieren" o.ä. Das ginge jedoch nur auf Ramdisk und die wäre bei einem evt. Reboot weg. Zumal ein Test hier dann vermutlich noch länger dauert.
Ínteressanter Punkt ist in diesem Szenario noch folgender: Die Abarbeitung selbst dauert je File ein paar Sekunden. Daher ists egal ob da noch ein paar Millisekunden drauf kämen für die Pflege eines Index etc. bei jedem Filedurchlauf. Entscheidend ist das schnelle Anspringen der letzten Position beim nächsten Scriptstart (nach einem Stopp vor Ende der Abarbeitung) |
|
Back to top |
|
|
py-ro Veteran
Joined: 24 Sep 2002 Posts: 1734 Location: Velbert
|
Posted: Mon Oct 22, 2012 4:15 pm Post subject: |
|
|
Also ist das eigentlich Problem, was zu lösen ist, zu behalten welche Dateien bereits bearbeitet wurden
Ich gehe mal davon aus, ein Dateiname pro Zeile.
Quick & Dirty und erlaubt keine Priorisierung...
Code: |
for file in $(cat input log | sort | uniq -u )
do
something && echo "${file}" >> log
done
|
Ungetestet und ohne Gewähr. Evtl. muss der IFS noch passend gesetzt werden.
Bye
Py |
|
Back to top |
|
|
mv Watchman
Joined: 20 Apr 2005 Posts: 6747
|
Posted: Mon Oct 22, 2012 4:19 pm Post subject: |
|
|
Wie gesagt: Das natürliche Hilfsmittel für die Aufgabe ist ein Hashtabelle (oder ein sortierter Baum, aber das erledigt sowie die Programmiersprache/Bibliothek).
Hier ein Beispiel für zsh:
Code: | #! /usr/bin/env zsh
declare -A files
while read i
do files[$i]=
done <list.txt
while read i
do unset "files[$i]"
done <files.log
for i in ${(k)files}
do process_file $i
printf '%s\n' $i >>files.log
done |
In Bash ginge es wohl ähnlich, allerdings wäre mehr Quoting nötig (und die Syntax für "unset" und ${(k)files} müsste ich erst nachschauen). |
|
Back to top |
|
|
slick Bodhisattva
Joined: 20 Apr 2003 Posts: 3495
|
Posted: Mon Oct 22, 2012 4:48 pm Post subject: |
|
|
mv wrote: | Hier ein Beispiel für zsh |
Da gibst nur bash. |
|
Back to top |
|
|
mv Watchman
Joined: 20 Apr 2005 Posts: 6747
|
Posted: Mon Oct 22, 2012 6:37 pm Post subject: |
|
|
slick wrote: | Da gibst nur bash. |
Ich schrieb doch: Geht ähnlich, ist nur langsamer und mehr Quoting nötig:
Code: | #! /usr/bin/env bash
declare -A files
while read i
do files["$i"]=
done <list.txt
while read i
do unset files["$i"]
done <files.log
for i in "${!files[@]}"
do process_file "$i"
printf '%s\n' "$i" >>files.log
done |
|
|
Back to top |
|
|
|