Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
Bash: lange Listen auf langsamen Rechner & grep
View unanswered posts
View posts from last 24 hours
View posts from last 7 days

 
Reply to topic    Gentoo Forums Forum Index Deutsches Forum (German) Diskussionsforum
View previous topic :: View next topic  
Author Message
slick
Bodhisattva
Bodhisattva


Joined: 20 Apr 2003
Posts: 3495

PostPosted: Fri Oct 19, 2012 3:07 pm    Post subject: Bash: lange Listen auf langsamen Rechner & grep Reply with quote

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
View user's profile Send private message
Finswimmer
Bodhisattva
Bodhisattva


Joined: 02 Sep 2004
Posts: 5467
Location: Langen (Hessen), Germany

PostPosted: Fri Oct 19, 2012 3:24 pm    Post subject: Reply with quote

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
View user's profile Send private message
slick
Bodhisattva
Bodhisattva


Joined: 20 Apr 2003
Posts: 3495

PostPosted: Fri Oct 19, 2012 5:44 pm    Post subject: Reply with quote

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
View user's profile Send private message
py-ro
Veteran
Veteran


Joined: 24 Sep 2002
Posts: 1734
Location: Velbert

PostPosted: Fri Oct 19, 2012 10:30 pm    Post subject: Reply with quote

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
View user's profile Send private message
mv
Watchman
Watchman


Joined: 20 Apr 2005
Posts: 6747

PostPosted: Sat Oct 20, 2012 3:27 pm    Post subject: Reply with quote

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
View user's profile Send private message
toralf
Developer
Developer


Joined: 01 Feb 2004
Posts: 3919
Location: Hamburg

PostPosted: Mon Oct 22, 2012 9:22 am    Post subject: Reply with quote

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
View user's profile Send private message
py-ro
Veteran
Veteran


Joined: 24 Sep 2002
Posts: 1734
Location: Velbert

PostPosted: Mon Oct 22, 2012 9:25 am    Post subject: Reply with quote

Naja, um eine bessere Lösung zu generieren bräuchte man mehr Informationen über Ausgangslage und Ziel.
Back to top
View user's profile Send private message
slick
Bodhisattva
Bodhisattva


Joined: 20 Apr 2003
Posts: 3495

PostPosted: Mon Oct 22, 2012 3:54 pm    Post subject: Reply with quote

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
View user's profile Send private message
py-ro
Veteran
Veteran


Joined: 24 Sep 2002
Posts: 1734
Location: Velbert

PostPosted: Mon Oct 22, 2012 4:15 pm    Post subject: Reply with quote

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
View user's profile Send private message
mv
Watchman
Watchman


Joined: 20 Apr 2005
Posts: 6747

PostPosted: Mon Oct 22, 2012 4:19 pm    Post subject: Reply with quote

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
View user's profile Send private message
slick
Bodhisattva
Bodhisattva


Joined: 20 Apr 2003
Posts: 3495

PostPosted: Mon Oct 22, 2012 4:48 pm    Post subject: Reply with quote

mv wrote:
Hier ein Beispiel für zsh


Da gibst nur bash.
Back to top
View user's profile Send private message
mv
Watchman
Watchman


Joined: 20 Apr 2005
Posts: 6747

PostPosted: Mon Oct 22, 2012 6:37 pm    Post subject: Reply with quote

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
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Deutsches Forum (German) Diskussionsforum All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum