Bash script: xml en bestanden naar directorystructuur

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Mr_Big
  • Registratie: Februari 2002
  • Niet online
Ik zit met ca 3GB aan bestanden in een arbitraire en een xml-bestand van 12MB dat die bestanden beschrijft. Met behulp van Bash heb ik een script geschreven dat de xml doorloopt, een structuur aanmaakt en de bestanden van de huidige, arbitraire structuur en bestandsnaam naar de locatie en bestandsnaam uit de xml kopieert.
Bij het kleine xml-bestand waarmee ik heb getest gaat alles prima, als ik echter het volledige bestand oppak, zie ik het proces lopen, dan afsluiten, dan weer opstarten en afsluiten en heb ik geen resultaat. Hopelijk kunnen jullie mij helpen bij het 'waarom?'.

De xml ziet er als volgt uit (maar dan vele malen groter):
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<bla>
    <document>
        <document_naam>Versie II - RenD-uitgaven private sector.doc</document_naam>
        <pk_document_id>3489</pk_document_id>
        <gepubliceerd>1</gepubliceerd>
        <document_actief>1</document_actief>
        <URI>/root/nli/00/00/51431C7B-AC23-4C9C-AB00-D69738B8077A.doc</URI>
        <file_created/>
        <file_modified/>
        <created>2008-01-07T15:53:56+01:00</created>
        <modified>2008-01-07T15:53:56+01:00</modified>
        <eigenaar_persoon_id>1559218</eigenaar_persoon_id>
        <eigenaar_persoon_formal_naam>De heer BLA</eigenaar_persoon_formal_naam>
        <doc_ident_id>3489_5304_58946</doc_ident_id>
        <tabelnaam>tbl_werkgroep</tabelnaam>
        <PK_tabel_ID>4617</PK_tabel_ID>
        <context_naam>Commissie Innovatie &amp; Kennis/14 maart 2008</context_naam>
        <tree_path>/14 maart 2008</tree_path>
        <identifier>VergaderingenBijWerkgroep</identifier>
        <PK_tree_node_locator_id>5304</PK_tree_node_locator_id>
        <PK_tree_node_id>58946</PK_tree_node_id>
    </document>
</bla>


Het bash-script dat ik gebruik ziet er als volgt uit:
code:
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
#!/bin/bash
count=1
input=$1
path=/root/nli/target

while [ -n "$document_naam" -o $count = 1 ]
do
    document_naam=`cat $input | xpath //document[$count]/document_naam 2>/dev/null | sed s/\<document_naam\>//g| sed s/\<\\\\/document_naam\>//g`
    context_naam=`cat $input | xpath //document[$count]/context_naam 2>/dev/null | sed s/\<context_naam\>//g| sed s/\<\\\\/context_naam\>//g`
    URI=`cat $input | xpath //document[$count]/URI 2>/dev/null | sed s/\<URI\>//g| sed s/\<\\\\/URI\>//g`

    if [ "$document_naam" -a "$context_naam" ]; then
        
        # &amp; vervangen door &
        document_naam=$(echo "$document_naam" | sed 's/&amp\;/\&/g')
        context_naam=$(echo "$context_naam" | sed 's/&amp\;/\&/g')

        # Acties weergeven en uitvoeren
        echo $count $path/$context_naam/$document_naam
        mkdir -p "$path/$context_naam"
        cp -pf "$URI" "$path/$context_naam/$document_naam"
    fi
    count=$((count+1))

done


Zoals je ziet gebruik ik uiteindelijk maar drie attributen van elk document: context_naam voor het pad, document_naam voor de naam van het document en URI voor de huidige locatie.
Ook kan je zien dat ik de "&" eruit filter voor het aanmaken van de mappen. Als ik die comment, dan werkt het script trouwens ook niet.

Iemand een idee waar dit op stukloopt?


PS: Als ik het aantal regels beperk tot ca 10.000, dan loopt het wel door. Helaas niet echt snel, dus misschien hebben jullie daar ook wel een tip om dit script wat efficienter te maken.

[ Voor 3% gewijzigd door Mr_Big op 11-03-2013 16:47 ]


Acties:
  • 0 Henk 'm!

  • Rainmaker
  • Registratie: Augustus 2000
  • Laatst online: 14-07-2024

Rainmaker

RHCDS

Ik gok (met nadruk op gok) dat je ergens in de bestanden een file hebt zitten met een karakter wat je niet escaped.
Je filtert & al. Mogelijk heb je nog meer bestanden met een ";"? (command seperator).
Verder, vaak voorkomend in bestandsnamen, maar killing voor een bash script:
code:
1
2
3
4
5
6
7
8
9
"
'
$
>
<
|
;
!
`

etcetera

Wellicht wil je je script liever zo hebben dat het alleen cijfers / getallen pakt, en misschien een paar "vreemde" tekens.
Bijvoorbeeld zoals hier: http://stackoverflow.com/...ric-without-an-underscore

We are pentium of borg. Division is futile. You will be approximated.


Acties:
  • 0 Henk 'm!

  • Mr_Big
  • Registratie: Februari 2002
  • Niet online
Thanks voor de tip. Heb de xml eens grondig doorgenomen (grep op ascii-waardes) en inderdaad wat ongeldige tekens tegengekomen, Na het verwijderen loopt het echter nog steeds niet door.
Daarna nog door een validator heengehaald, die haalde er nog wat 'lonely attributes' uit.
Nu duurt het wel veel langer voordat het proces ermee stopt, maar het stopt nog wel.

Alleen cijfers en getallen is wat erg beperkt, omdat dit om bestandsnamen gaat en die best nog wat 'rare', maar geldige tekens kunnen bevatten.

Iemand nog een suggestie?
Ook iemand nog een suggestie om het proces te versnellen? Nu laat ik het bestand drie keer scannen op één van de drie attributen, dus dat gaat niet echt snel.

Mijn back-up oplossing is om het bestand in meerdere delen op te knippen en zo maar te laten doorlopen. Dan gaat het iets sneller, kan het parallel en komt mogelijk het probleemgeval nog bovendrijven. Maar dit is natuurlijk geen ideale en efficiente oplossing.

[ Voor 10% gewijzigd door Mr_Big op 11-03-2013 18:23 ]


Acties:
  • 0 Henk 'm!

  • Borizz
  • Registratie: Maart 2005
  • Laatst online: 24-09 20:59
Wat gebeurt er als je de volgende regel gewoon uitvoert in bash (wel even $input vervangen):
cat $input | xpath //document[0]/document_naam 2>/dev/null | sed s/\<document_naam\>//g| sed s/\<\\\\/document_naam\>//g


Wellicht dat 1 van de processen (zoals xpath) eruitklapt i.v.m. geheugengebruik o.i.d.. Ik ben geen bash master, maar het lijkt mij efficiënter om de xml file maar 1 keer uit te lezen en per document een actie uit te voeren. Of daar commandline programma's voor zijn zou ik niet durven zeggen.

If I can't fix it, it ain't broken.


Acties:
  • 0 Henk 'm!

  • Mr_Big
  • Registratie: Februari 2002
  • Niet online
Ten eerste krijg ik een foutmelding:
sed: -e expression #1, char 21: unknown option to `s'

Het proces loopt daarna wel door, maar breekt uiteindelijk af zonder resultaten. Heb het geprobeerd voor alle drie de variabelen, maar lopen alledrie stuk. Zelfs URI waarvan ik zo goed als zeker weet dat die geen 'rare' tekens bevat.
Geheugen lijkt ook geen probleem: voordat het vastloopt is er nog ca 800MB over.

Update: het bleek toch stuk te lopen op enkele verkeerde tekens in de xml. Nu loopt het proces wel door, maar -zeer- traag: meer dan twee minuten per document. Dat is dus onwerkbaar.

Hebben jullie ideeen om dit te versnellen? Zelf heb ik alleen het idee om het bestand in stukken op te delen: dan gaat 'cat' sneller (want kleinere bestanden) en kan ik meerdere processen parallel laten lopen (efficienter gebruik cores).
Maar het feit dat het een inefficient script is bijft, dus als jullie een goede suggestie hebben...

[ Voor 38% gewijzigd door Mr_Big op 11-03-2013 22:36 ]


Acties:
  • 0 Henk 'm!

  • Rainmaker
  • Registratie: Augustus 2000
  • Laatst online: 14-07-2024

Rainmaker

RHCDS

Even kijken.

Enige wat ik zo zie; je zou de content van de file in een variabele laden. Dit versnelt het iets, ook al doet je filesystem cache hetzelfde. 3 keer een cat van dezelfde file zou al behoorlijk snel moeten zijn.

Verder heb ik ooit begrepen (maar weet niet zeker of dit klopt) dat backticks "langzaam" zijn. Gebruik $(command). Dan krijg je zoiets:

code:
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
count=1
input=$1
path=/root/nli/target

while [ -n "$document_naam" -o $count = 1 ]
do
    CONTENT=$(cat $input)
    document_naam=$(echo $CONTENT | xpath //document[$count]/document_naam 2>/dev/null | sed s/\<document_naam\>//g| sed s/\<\\\\/document_naam\>//g)
    context_naam=$(echo $CONTENT | xpath //document[$count]/context_naam 2>/dev/null | sed s/\<context_naam\>//g| sed s/\<\\\\/context_naam\>//g)
    URI=$(echo $CONTENT | xpath //document[$count]/URI 2>/dev/null | sed s/\<URI\>//g| sed s/\<\\\\/URI\>//g)
...


Verder kun je met bash -vx script.sh het script runnen om ieder subcommando voorbij te zien komen. Geeft misschien een hint waar het script blijft "hangen".

Daarnaast kun je ook nog met strace kijken welke syscall het script het langst over doet:

code:
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
:~/bin$ strace -f -c ./test.sh 
Process 5593 attached
Process 5593 detached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 31.06    0.000730           0      5003         1 fcntl
 20.81    0.000489           0      2002           dup2
 18.55    0.000436           0      1012           open
  9.15    0.000215           0      2019           close
  7.74    0.000182           0      1001           write
  4.38    0.000103           9        12         8 access
  2.94    0.000069          35         2         1 wait4
  2.21    0.000052          13         4           munmap
  1.74    0.000041           4        11         2 stat
  1.40    0.000033           1        25           mmap
  0.00    0.000000           0        40           read
  0.00    0.000000           0        12           fstat
  0.00    0.000000           0         3           lseek
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0        40           brk
  0.00    0.000000           0        22           rt_sigaction
  0.00    0.000000           0        20           rt_sigprocmask
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1           clone
  0.00    0.000000           0         2           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         2           getrlimit
  0.00    0.000000           0         5           getuid
  0.00    0.000000           0         5           getgid
  0.00    0.000000           0         5           geteuid
  0.00    0.000000           0         5           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         2           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.002350                 11274        13 total

We are pentium of borg. Division is futile. You will be approximated.


Acties:
  • 0 Henk 'm!

  • Mr_Big
  • Registratie: Februari 2002
  • Niet online
Met jouw code moest ik sed ook aanpassen, met dit als resultaat:

code:
1
2
3
4
    CONTENT=$(cat $input)
    document_naam=$(echo "$CONTENT" | xpath //document[$count]/document_naam 2>/dev/null | sed 's/<document_naam>//g' | sed 's/<\/document_naam>//g')
    context_naam=$(echo "$CONTENT" | xpath //document[$count]/context_naam 2>/dev/null | sed 's/<context_naam>//g' | sed 's/<\/context_naam>//g')
    URI=$(echo "$CONTENT" | xpath //document[$count]/URI 2>/dev/null | sed 's/<URI>//g' | sed 's/<\/URI>//g')


Helaas moet het script nog steeds per per attribuut door de xml heenlopen ('echo "$CONTENT"'), dus in dit geval drie keer per document. Conclusie: het duurt nog steeds even lang (ca 6,5 sec per document).
Leuke oefening, maar helaas dus nog geen verbetering.

Acties:
  • 0 Henk 'm!

  • matthijsln
  • Registratie: Augustus 2002
  • Laatst online: 16:57
Alternatief met XMLStarlet:
xmlstarlet sel --text -t -m "bla/document" \
  -o 'echo ' -v "position()" -o ': ' -v document_naam -n \
  -o 'mkdir -p "<pad>/' -v context_naam -o '"' -n \
  -o 'cp -pf "' -v URI -o '" "<pad>/' -v context_naam -o '/' -v document_naam -o '"' -n \
  bla.xml

Geeft deze output:
echo 1: Versie II - RenD-uitgaven private sector.doc
mkdir -p "<pad>/Commissie Innovatie & Kennis/14 maart 2008"
cp -pf "/root/nli/00/00/51431C7B-AC23-4C9C-AB00-D69738B8077A.doc" "<pad>/Commissie Innovatie & Kennis/14 maart 2008/Versie II - RenD-uitgaven private sector.doc"

Deze output dan weer door bash laten uitvoeren. Dus met 1 pass door het xml document een bash script genereren.

Acties:
  • 0 Henk 'm!

  • Mr_Big
  • Registratie: Februari 2002
  • Niet online
Holy cow ;-)
Dat gaat bijna instantaan (bij 10.000 regels, 500 documenten), waar dat eerst 60 minuten duurde.
Zo zie je maar wel dat je voor elke klus de juiste tools moet gebruiken.

Helaas wel wat problemen met 'vreemde' tekens als haakjes en quotes in de namen, maar dat is meer een probleem in de xml dat ik nog moet oplossen (& pakt 'ie gelukkig wel).

Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 20:04

deadinspace

The what goes where now?

Rainmaker schreef op dinsdag 12 maart 2013 @ 10:01:
Verder heb ik ooit begrepen (maar weet niet zeker of dit klopt) dat backticks "langzaam" zijn. Gebruik $(command).
Voorzover ik weet zijn `` en $() exact equivalent, behalve dat:
  • Je kunt $() nesten en `` niet (BLA=$(echo $(date)))
  • $() is naar mijn mening veel leesbaarder en heeft alleen al daarom de voorkeur
Snelheidsverschil is er voorzover ik weet niet :)

Wel zijn `` en $() nooit heel snel, want ze starten altijd (minstens) een nieuw proces op, iets wat al snel 1-10 ms kost en je dus beperkt tot 1000 $()'s per seconde in het allerbeste geval. Nieuwe processen starten is trouwens één van de voornaamste redenen dat shell scripts relatief traag kunnen zijn voor dit soort dingen.
Mr_Big schreef op woensdag 13 maart 2013 @ 10:08:
Holy cow ;-)
Dat gaat bijna instantaan (bij 10.000 regels, 500 documenten), waar dat eerst 60 minuten duurde.
Zo zie je maar wel dat je voor elke klus de juiste tools moet gebruiken.
En daar is onze topicstarter dus ook achter gekomen :P

Niet alleen voor snelheid zou ik afraden om dit in shell te doen, ook voor correctheid. In shell is alles een string en veel karakters hebben een speciale betekenis, waardoor je heel goed moet oppassen wat je doet. In een "echte" programmeertaal zoals bv Python heb je die problemen niet, en debuggen is daarmee ook een stuk makkelijker.

Dit trouwens niet ten nadele van de xmlstarlet oplossing, die is nog simpeler :)

Acties:
  • 0 Henk 'm!

  • Mr_Big
  • Registratie: Februari 2002
  • Niet online
Ach, ja. Ik weet hoe het eigenlijk zou 'moeten', maar ik moet het doen met de kennis die ik heb (en die is -zeer- beperkt op programmeergebied) en wat ik kan vinden.
Tot op heden ben ik tevreden met de Bash-oplossing(en) en erg tevreden met de xmlstarlet-oplossng. Ik ben van mening dat een oplossing die 'goed genoeg' is, wat mij betreft goed genoeg is.

Acties:
  • 0 Henk 'm!

  • matthijsln
  • Registratie: Augustus 2002
  • Laatst online: 16:57
Mr_Big schreef op woensdag 13 maart 2013 @ 10:08:
Holy cow ;-)
Dat gaat bijna instantaan (bij 10.000 regels, 500 documenten), waar dat eerst 60 minuten duurde.
Zo zie je maar wel dat je voor elke klus de juiste tools moet gebruiken.
Mooi dat het werkt :)
Helaas wel wat problemen met 'vreemde' tekens als haakjes en quotes in de namen, maar dat is meer een probleem in de xml dat ik nog moet oplossen (& pakt 'ie gelukkig wel).
Ik had quoting inderdaad genegeerd want dat levert een veel lastiger commando op :)

Maar omdat het leuk is: eigenlijk moeten we bash strong quotes met een ' gebruiken in plaats van ". Dan hoef je alleen de ' te escapen met '\''. Echter wordt het wat veel met alles escapen, en biedt XPath 1 ook geen standaard replace functie dus is een exslt extensie nodig:

xmlstarlet sel --text -t -m "bla/document" \
  -o "echo " -v "position()" -o ": '" -v "str:replace(document_naam,\"'\",\"'\\''\")" -o \' -n \
  -o "mkdir -p '<pad>/" -v "str:replace(context_naam,\"'\",\"'\\''\")" -o \' -n \
  -o "cp -pf '" -v "str:replace(URI,\"'\",\"'\\''\")" -o \' -o " " -o \' -o "<pad>/" -v "str:replace(context_naam,\"'\",\"'\\''\")" -o "/" -v "str:replace(document_naam,\"'\",\"'\\''\")" -o \' -n \
  bla.xml

Het is met zo'n absurd commando beter om de XSL stylesheet die je krijgt met "xmlstart sel -C ..." te pakken en die met "xmlstarlet transform myxsl.xsl bla.xml" aan te roepen. Ik heb de XSL die xmlstarlet van bovenstaand commando maakt wat aangepast voor de duidelijkheid:

XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings" version="1.0" extension-element-prefixes="str">
  <xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
  <xsl:template match="/">
    <xsl:variable name="dest-path">&lt;pad&gt;</xsl:variable>
    <xsl:for-each select="bla/document">
      <xsl:variable name="file" select="str:replace(document_naam,&quot;'&quot;,&quot;'\''&quot;)"/>
      <xsl:variable name="path" select="str:replace(context_naam,&quot;'&quot;,&quot;'\''&quot;)"/>
      <xsl:variable name="src" select="str:replace(URI,&quot;'&quot;,&quot;'\''&quot;)"/>
      <xsl:text>echo </xsl:text><xsl:value-of select="position()"/>: '<xsl:value-of select="$file"/><xsl:text>'
mkdir -p '</xsl:text><xsl:value-of select="$dest-path"/>/<xsl:value-of select="$path"/><xsl:text>'
cp -pf '</xsl:text><xsl:value-of select="$src"/>' '<xsl:value-of select="$dest-path"/>/<xsl:value-of select="$path"/>/<xsl:value-of select="$file"/><xsl:text>'
</xsl:text>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Acties:
  • 0 Henk 'm!

  • Mr_Big
  • Registratie: Februari 2002
  • Niet online
matthijsln, bedankt voor de mooie oplossingen die je produceert.
Wat ik alleen bedoelde met 'Helaas wel wat problemen met 'vreemde' tekens als haakjes en quotes in de namen, maar dat is meer een probleem in de xml dat ik nog moet oplossen', is dat er daadwerkelijk ongeldige tekens in de waardes staan. Tekens die je niet mag gebruiken in map- of bestandsnamen. Oftewel: escapen helpt dan wel genereren van de juiste code, maar dan loopt 'ie toch weer vast bij het aanmaken van de mappen en bestanden.
Dat betekent dat ik alle quotes, slashes en backslashes (ja, ik heb een zooitje aangeleverd gekregen) uit de bestands- en mapnamen moet filteren voordat ik met xmlstarlet aan de slag ga.

Maar zoals gezegd: bedankt voor je oplossing, mogelijk komt die op een later moment wel van pas.

Acties:
  • 0 Henk 'm!

  • matthijsln
  • Registratie: Augustus 2002
  • Laatst online: 16:57
Ah duidelijk! Inderdaad heb je niet opeens geldige bestandsnamen met al het ge-escape, haha.
Pagina: 1