Fragen zu SED (UNIX/LINUX)

BxB

Lieutenant
Registriert
Apr. 2005
Beiträge
770
Hey Leute, meine Unix/Linux-Kenntnisse sind auf die Basics beschränkt, daher waren die ersten Versuche mit SED ohne Erfolg gewesen.

Das Szenario: Ich habe eine Eingabe-Datei mit XML-Inhalt, welches alles auf einer einzigen Zeile steht. Diesen Inhalt will ich auf mehrere Datei (z.B. feste 4 Dateien) gleichmäßig mit Zeilenformatierung aufteilen, wobei jede Datei den Header und Footer erhält.

Die Input XML-Datei als Beispiel:
<?xml version="1.0" encoding="UTF-"8" ?><mainpart adds="notplain"><content><id>1</id><one>1</one><two>2</two></content><content><id>2</id><one>1</one><two>2</two><extras><three>3</three></extras></content><content><id>nEntrys</id><one>1</one><two>2</two></content></mainpart>

Problem: Da wie bei id=2 auch flexibel weitere xml-Elemente sein können, kann man nicht jedes geschlossene Tag zur neuen Zeile machen und mit fester Zeilenanzahl arbeiten. Es muss etwas flexibles her.

Meine Idee:
  1. Ich splitte das ganze in 3 Teile mit jeweils einer Zeile: HEADER, CONTENT und FOOTER. Header und FOOTER werden gemerkt und aus der Verarbeitungsdatei entfernt (head/tail), um sie später wieder in den aufgeteilten Dateien angefügt werden. Das Arbeiten mit temporären Dateien wäre eine Optionen (xxx > "$filename"_temp.xml oder so?)

    HEADER = <?xml version="1.0" encoding="UTF-"8" ?><mainpart adds="notplain">
    CONTENT = <content><id>1</id><one>1</one><two>2</two></content><content><id>2</id><one>1</one><two>2</two><extras><three>3</three></extras></content><content><id>nEntrys</id><one>1</one><two>2</two></content>
    FOOTER = </mainpart>

    Hier fällt mir noch kein SED ein. Ich müsste theoretisch nen filter machen, der die eine zeile beendet, wenn das mainpart endet. und das schließende </mainpart> als FOOTER auch noch mal abtrennt.

  2. Die Verarbeitungsdatei, die aus dem reinen CONENT besteht wird nun weiter verarbeitet und jedes schließende </content> soll zur neuen Zeile führen. Dies soll über ein eigenes SED geschehen. Wenn ich es aktuell korrekt gelesen habe, würde ein "s" anfangs für Substitution - also Austausch - stehen, dann der filter für das </content>, danach wodurch das ersetzt wird und am ende ein "g" für beendigung der substitution?

    sed 's/>/>\n/g' data.xml > data_with_newlines.xml ==> Beispiel, was nach jedem Greater-Operator ">" eine neue Zeile generiert.
    Jetzt soll aber </content> als Suchkriterium genutzt werden (wie escape ich das? mit \ vor dem / ?) ... mein Versuch ginge so:
    sed 's/<\ /content>/<\ /content>\ng' data.xml > content_each_on_a_newline.xml

  3. Wenn ich das geschafft habe, könnte ich die Anzahl der Zeichen zählen (wc -l $FILENAME), einen DIVISOR berechne auf die anzahl der verschiedenen Dateien und dann per SPLIT versuche gleichmäßig die aufzuteilen.
    Beim Aufteilen wären HEADER und FOOTER jeweils anzufügen und die liste des content soll nach jedem Ende Tag eine neue Zeile erhalten (keine Doppelzeilen).
Meine Fragen:
  1. Was haltet ihr von dem Vorgehen? Gibt es eine bessere Lösung als Herangehen?
  2. Wie müssten die SED-Befehle dafür aussehen?

---------------- Teil 2 der Fragen -------------
Hey, hab das mit euren Vorschlägen mir angeschaut, leider hatte ich kaum Wahl der Tools für Alternativen. Durch Recherche (angeregt durch die Kommentare) habe ich die bisherigen Anpassungen mit dem geplanten SED hinbekommen. Ich hab noch eine Frage:

Wie kann ich den namespace (Nur Buchstaben und Zahlen) aus einem XML-Element auslesen und in einer Variable speichern?

<?xml><n0:test><n0:testing>1</n0:testing><n0:testing>2</n0:testing></n0:test>

Anmerkung: Ich weiß, dass so etwas eigentlich über XML-Parser geschehen soll für eine saubere Lösung. Da die XML-Struktur bekannt ist, einem festen schema folgt und am Ende immer "</n0:test>" steht, sollte ein grep/sed oder so hoffentlich reichen.

ich probiere grade als Autodidaktischer Unix-Anfänger mit https://www.jdoodle.com/test-bash-shell-script-online etwas zu erstellen, was aber (ohne erfolg bisher) im Ansatz wie ne QuickNDirty Hacklösung aussieht. Dort war mein Ziel sowas zu kriegen:
echo "<?xml><n0:test><n0:testing>1</n0:testing><n0:testing>2</n0:testing></n0:test>" > file
ns=$('werte_folgenden_teil_aus_</.*:test>_und_merke_das_.*_in_ns')
echo 'ns:['$ns']'
beim versuch mit SED habe ich gemerkt, dass es GREEDY ist und sowas wie eine Suche nach "></.*:test>" zum größtmöglichen String führt. und das ich mit SED aktuell nur syntax für den austausch kenne, nicht der reinen Extraktion. Ich merke einfach - mir fehlen da iwie noch die Unix-Erfahrung mit String-Manipulation und selektiver Extraktion >_<
 
Zuletzt bearbeitet:
Ist es für dich eine Option ganz von SED abzurücken und das per XSLT zu machen? Ich habe mir deinen Post nicht vollständig durchgelesen, aber das sieht doch recht umständlich aus, zumindest für jemanden wie mich, der keine Konsolen mag.
 
Ja, parse dein XML mit Perl. Gibt genug Libs auf CPAN. Shell ist für vieles gut, nur dafür nicht so richtig. Ich selber nutze auch SED aber XML damit bearbeiten...*schauder*.
Oder ganz krass..wenn du einen Webserver mit php und xml-Modul hast dann könntest du dir das in php basteln :D
Immernoch bequemer als Shell.
 
Mit einem regulären Ausdruck könnte man die 3-Teile einfangen. Wie gerade schon erwähnt geht das mit "grep -P ...".

Mit "The Regex Coach" unter Windows hat folgender RegEx funktioniert, um sich die drei Blöcke zu beschaffen (Case insensitive, multiline, single line und group aktiviert - entspricht den Buchstaben i, m, s und g). Mit SED habe ich es gerade nicht geprüft.
Zeilenumbrüche einbauen habe ich mir jetzt geschenkt. Ist das wirklich nötig?

Code:
(^?.*<mainpart adds="notplain">)(<content>.*</content>)(</mainpart>$)

Die Teile in Klammern sind jeweils die einzufangenden Gruppen. In SED kann man auf die Gruppe mittels \1 \2 und \3 zugreifen. Man muss bei SED nicht mittels "/" die Befehlsgruppen trenne. Man kann auch andere Zeichenfolgen wie "#" verwenden.
Als statt: sed 's/>/>\n/g' data.xml > data_with_newlines.xml
...sollte auch das möglich sein: sed 's#>#>\n#g' data.xml > data_with_newlines.xml

Ich nutze SED in diversen meiner Bash-Skripte für die automatische Installation und Konfiguration indem z.B. bei Standard Paketen bestimmte Parameter gesetzt werden. In der Regel sind das "Key=Value" Zeilen.

Welcher Anwendungsfall steckt hinter dem Ganzen bei Dir?
 
  • Gefällt mir
Reaktionen: DeusoftheWired
Ein bisschen in XSLT einarbeiten und dann auf der Shell am einfachsten mit xmlstarlet arbeiten. Mit sed wird das sicherlich eine instabile Geschichte.
 
  • Gefällt mir
Reaktionen: Sparta8
obs besser geht? bestimmt :)

Code:
for i in $(seq 1 $(xmlstarlet sel -t -v "count(/mainpart/content)" test.xml)); do echo ""; xmlstarlet ed -d "/mainpart/content[position()!=$i]"  test.xml; done

<?xml version="1.0" encoding="UTF-8"?>
<mainpart adds="notplain">
  <content>
    <id>1</id>
    <one>1</one>
    <two>2</two>
  </content>
</mainpart>

<?xml version="1.0" encoding="UTF-8"?>
<mainpart adds="notplain">
  <content>
    <id>2</id>
    <one>1</one>
    <two>2</two>
    <extras>
      <three>3</three>
    </extras>
  </content>
</mainpart>

<?xml version="1.0" encoding="UTF-8"?>
<mainpart adds="notplain">
  <content>
    <id>nEntrys</id>
    <one>1</one>
    <two>2</two>
  </content>
</mainpart>
 
Zuletzt bearbeitet:
Hey, hab einen Teil2 zugeschrieben. ich würde gerne den namespace "n0" extrahieren. habt ihr da eine empfehlung mit reinen klassischen standard-board-mitteln?

input wäre z.B.:
<?xml><n0:test><n0:testing>1</n0:testing><n0:testing>2</n0:testing></n0:test>


mein Versuch auf https://www.jdoodle.com/test-bash-shell-script-online
echo "<?xml><n0:test><n0:testing>1</n0:testing><n0:testing>2</n0:testing></n0:test>" > file
a) ns=$(sed 's/.*><\/\(.*\):test>/\1/' file)
b) ns=$(sed 's/.*><\/\(n0\):test>/\1/' file)
echo 'ns:['$ns']'

was mir aber noch fehlt ist wie ich das n0 oder .* nun zu sowas wie [\w\d]* mache, was ich denke besser ist.
Als Alternative wäre zu überlegen das ":" auch mit reinzunehmen, um LEERE Variable bei ohne namespace zu erhalten. habt ihr da noch tipps?
 
Zuletzt bearbeitet:
sed (stream editor) ändert Dateien, ist also weniger für Abfragen gedacht. Man kann sed für Abfragen benutzen, indem man uninteressante Daten durch '' ersetzt, das ist aber eher umständlich.

Ich bin mir nicht sicher ob dir dieser grep kombiniert mit sed weiter hilft:
Code:
echo '<?xml><n0:test><n0:testing>1</n0:testing><n0:testing>2</n0:testing></n0:test>' | grep -oP '</[^:]+:[^>]+>' | sed -e 's#</##g' -e 's#>##g'
Ausgabe:
Code:
n0:testing
n0:testing
n0:test
grep -oP '</[^:]+:[^>]+>'
-o only matches
-P perl regexes
'</[^:]+:[^>]+>' schließende Tags mit Namespace

sed -e 's#</##g' -e 's#>##g'
-e expression
's#</##g' '</' entfernen
-e zweite expression
's#>##g' '>' entfernen
 
fhtagn ... das sieht ganz gut aus. es könnte eine vielleicht etwas wenigere dirty-lösung darstellen ;-)
 
Zurück
Oben