# sed, grep, irgendwas - aber keine Zeilen

## musv

Moin, 

das Problem beschäftigt mich schon etwas länger. Bisher hab ich aber noch keine Lösung gefunden. 

Problembeschreibung: 

Ich hab 'n Verzeichnis mit HTML-Files, in dem ich alle CSS-Classenaufrufe rausfiltern und am besten in eine Datei schreiben will. 

Beispiel: 

```
<table cellpadding="2" cellspacing="0" class="vip_start_news">

<td colspan="2"><input type="submit" name="requ_send" class="form_requ_button" value="absenden&nbsp;&raquo;" /></td>
```

Ich will dann 'ne Liste bekommen, die in etwa so aussieht: 

```
vip_start_news

form_requ_button
```

Wo ist jetzt mein Problem?

Irgendwie arbeiten grep und sed nur zeilenweise. Die ganze Zeile nützt mir aber nichts. 

Was will ich?

Einen Befehl, mit dem ich per Regexpression eine Zeichenkombination aus einer Zeile rausziehen kann. D.h. die restlichen Zeichen der Zeile wegschmeiß. 

Gibt's sowas?

----------

## think4urs11

du willst also nur den Inhalt von class und sonst nichts, richtig?

Dann müßte es so funktionieren: sed '/class/!d;s/.*class="\([^"]*\)\{1\}.*/\1/' filename

Das Gebastel mit [^"] u. \{1\} ist notwendig weil sed sonst jeweils alles vom ersten " (nach class=) bis zum letzten " in einer Zeile in \1 packt; durch die zusätzliche Angabe wird genau bis zum zweiten " 'herausgeschnibbelt'.

----------

## toralf

 *Think4UrS11 wrote:*   

> Dann müßte es so funktionieren: sed '/class/!d;s/.*class="\([^"]*\)\{1\}.*/\1/' filename

 Hilfe ! - jetzt weiß ich, wen ich bei regex um Hilfe bitten kann  :Smile:  mir selbst fällt immer nur sowas ein :

```
grep class= <filenname> | sed 's/.* class=//g' | cut -f2 -d '"'
```

----------

## Knieper

Ein paar Schoenheits-OPs fehlen noch, zB. das Split fuer class="ac bc cc dc" oder Zeilenumbrueche innerhalb der Definition oder ...

----------

## musv

 *Think4UrS11 wrote:*   

> Dann müßte es so funktionieren: sed '/class/!d;s/.*class="\([^"]*\)\{1\}.*/\1/' filename

 

Bin beeindruckt. Klappt richtig gut. 

Kannst du mal noch bitte genau erklären, was das was macht in dem Ausdruck?

----------

## think4urs11

 *musv wrote:*   

>  *Think4UrS11 wrote:*   Dann müßte es so funktionieren: sed '/class/!d;s/.*class="\([^"]*\)\{1\}.*/\1/' filename 
> 
> Bin beeindruckt. Klappt richtig gut. 
> 
> Kannst du mal noch bitte genau erklären, was das was macht in dem Ausdruck?

 

Eigentlich ist es ganz einfach  :Wink: 

1. /class/!d - Zeilen die den Suchterm 'class' nicht enthalten werden verworfen (nix anderes macht grep), nur mit dem was da noch übrig bleibt spielen wir suchen und ersetzen

2. .*class=" - ab hier beginnt der Teil den wir haben wollen, alles was davor steht (.*) interessiert uns nicht, wir benötigen nur einen eindeutigen Startpunkt für unser Suchmuster

3. \([^"]*\) - genau das wollen wir später haben und merken es uns daher in einem Muster (die werden einfach nach Auftreten der Klammern hochnummeriert, in dem Fall ist es das erste/einzige)

3a. [^"]* - bedeutet 'jedes Zeichen außer "' oder anders ausgedrückt es 'matcht' bis zum nächsten " ohne dieses zu beinhalten

3b. {1\} bedeutet 'das vorherige Zeichen genau 1x - in diesem Fall ist das Zeichen durch 3a bedingt auf jeden Fall ein " (oder das Zeilenende), hiermit haben wir also ein eindeutiges Ende unseres Suchmusters und zwar an genau der richtigen Stelle

4. .* - der ganze Rest; interessiert nicht, wird daher ignoriert

5. \1 - ausgegeben wird nur was 3a findet, d.h. das was zwischen ( und ) gefunden wurde

-> die ganze 'Magie' steckt in 3a+3b, ohne 3b würde 3a alles ausgeben bis zum letzten " in der aktuellen Zeile

Ist zugegeben nicht ganz offensichtlich aber es funktioniert weil eben 3a als letztes das Zeichen vor dem nächsten in der aktuellen Zeile folgenden " beinhaltet und damit 3b sich das " greift.

@Knieper: Leerzeichen in der class stören nicht; Zeilenumbrüche hingegen schon; weiterhin klappt es nicht mit 2-n class in einer Zeile

allerdings finde ich Zeilenumbrüche inmitten einer class als schlechten Programmierstil, trotzdem gilt: feel free to improve   :Wink: 

----------

## mv

 *Think4UrS11 wrote:*   

> 1. /class/!d - Zeilen die den Suchterm 'class' nicht enthalten werden verworfen (nix anderes macht grep), nur mit dem was da noch übrig bleibt spielen wir suchen und ersetzen

  Das hatte mich gewundert, dass ein sed-Guru wie Du das dem scheinbar einfacheren "sed -n s/..../p file" vorzieht. Hat Letzteres irgendwelche Kompatibilitätsprobleme?

----------

## think4urs11

Naja das hat min. 2 Gründe

1. 'hab ich schon immer so gemacht' 

2. es ist weniger zu tippen (min. ein Zeichen)  :Wink: 

----------

## mv

 *Think4UrS11 wrote:*   

> 2. es ist weniger zu tippen (min. ein Zeichen) 

 

 :Question:  Reden wir über das selbe?

```
sed '/class/!d;s/.*class="\([^"]*\)\{1\}.*/\1/'

sed -n 's/.*class="\([^"]*\)\{1\}.*/\1/p'
```

 Für mich sieht eindeutig die zweite Zeile nach weniger Tipparbeit aus...

Mehr noch: Solange der regexp nicht allzu trivial ist, ist Wiederholung des regexp wohl immer mehr Tipparbeit; deswegen hatte mich dieser Zugang eben verwundert.

----------

## l3u

Wo lernt man sowas? Außer mit man sed?! Ich will sowas auch können ;-)

----------

## mv

 *l3u wrote:*   

> Wo lernt man sowas? Außer mit man sed?!

 [Loriot]Wie: "außer"? Welche Maske?[/Loriot]

----------

## Polynomial-C

awk & sed - Die Profitools zur Dateibearbeitung und -editierung (von Helmut Herold)

Leider ist das Buch fast überall vergriffen...

----------

## think4urs11

 *mv wrote:*   

>   Reden wir über das selbe?
> 
> ```
> sed '/class/!d;s/.*class="\([^"]*\)\{1\}.*/\1/'
> 
> ...

 

Ach so meintest du das. Zugegeben das ist kürzer, hab ich wohl irgendwo geschlafen...    :Embarassed: 

Kommt davon wenn man solche sed's nur mal schnell so hinschludert, da bleibt Platz zum Optimieren wie man sieht. Man könnte es auch eine schlechte Angewohnheit meinerseits nennen.

Aber '-n' + 'p' ist immer noch ein Zeichen mehr als '!d'  :Wink: 

 *l3u wrote:*   

> Wo lernt man sowas? Außer mit man sed?! Ich will sowas auch können 

 

Durch (naja ok, wegen *g*) hübsche(n) Kolleginnen die einem schöne Augen machen weil sie sich mit Excel/Notepad oder sonstwas sonst die Finger brechen würden und man auf die Art immer eine Ausrede hat mal dort vorbeizusehen; wie denn sonst?  :Wink: 

Nein im Ernst, simples learning by doing und irgendwann mal eines der vielen HowTos gelesen. Ich hab zwar awk und sed. UNIX und seine Werkzeuge seit >10 Jahren aber auch sicher 8 Jahre nicht mehr reingeschaut.

----------

## l3u

Ich hab den falschen Job ;-)

----------

## Evildad

 *l3u wrote:*   

> Ich hab den falschen Job 

 

Oder einfach zu wenig hübsche Kolleginnen mit Problemen  :Smile: 

----------

## think4urs11

kleine Verbesserung damit Knieper nicht mehr (so leicht) meckern kann:

```
sed  -n '/class="[^"]*$/{N;s/\n//};s/class="\([^"]*\)\{1\}/\n-=\1=-\n/gp' inputfile | sed -n 's/-=\(.*\)=-/\1/p'
```

damit funktioniert es auch für '2-n class'/Zeile und mit Zeilenumbrüchen innerhalb einer class

Nicht zwingend 'schön' aber funktioniert  :Wink: 

----------

## notHerbert

Das sollte besser so wirken falls die Zeilenumbrüchen irgendwo innerhalb einer class Tags liegen.

```
tr -d '\n' <inputfile | perl -lne 'print for m/class="(\w+)/g'
```

 :Razz: 

----------

## Anarcho

 *notHerbert wrote:*   

> Das sollte besser so wirken falls die Zeilenumbrüchen irgendwo innerhalb einer class Tags liegen.
> 
> ```
> tr -d '\n' <inputfile | perl -lne 'print for m/class="(\w+)/g'
> ```
> ...

 

Sollte auch so gehen:

```
perl -lne 'undef $/; print for m/class="(\w+)/g' <inputfile
```

Dies hier kommt auch mit mehreren classes und Zeilenumbrüchen klar:

```
perl -lne 'undef $/; $,="\n"; print split for m/class="(.+?)"/g' < test.html
```

Man könnte natürlich noch ein "| sort -u" hintendranhängen...

----------

## notHerbert

Nehmen wir zum Beispeil,

```
$ cat inputfile

<table cellpadding="2" cellspacing="0" class="vip_start_news"  class="vip_end_news">

<td colspan="2"><input type="submit" name="requ_send" class="fo

rm_requ_button" value="absenden&nbsp;&raquo;" /></td>
```

Also probieren wir das mal aus,

```
$ perl -lne 'undef $/; print for m/class="(\w+)/g' <inputfile

vip_start_news

vip_end_news

fo
```

```
$ perl -lne 'undef $/; $,="\n"; print split for m/class="(.+?)"/g' <inputfile

vip_start_news

vip_end_news
```

Aber geht's mit 

```
$ tr -d '\n' <inputfile | perl -lne 'print for m/class="(\w+)/g'

vip_start_news

vip_end_news

form_requ_button

```

 :Razz: 

----------

## Anarcho

OK,

an Zeilenumbruch mitten in der Klasse hatte nicht nicht gedacht. Hiermit geht es nun und zusätzlich kann es auch noch mehrere Classes trennen:

<edit>Das funktioniert auch mit Windows-Formatierten Zeilenumbrüchen, was bei deiner Version auch nicht geht.</edit>

```
perl -lne 'undef $/; $,="\n"; s/\r?\n//g; print split for m/class="(.+?)"/g' < test.html
```

Die Testdatei:

```
workstation temp $ cat test.html 

<table cellpadding="2" cellspacing="0" class="vip_start_news second_class"  class="vip_end_news">

<td colspan="2"><input type="submit" name="requ_send" class="fo

rm_requ_button" value="absenden&nbsp;&raquo;" /></td>
```

```
workstation temp $ perl -lne 'undef $/; $,="\n"; s/\r?\n//g; print split for m/class="(.+?)"/g' < test.html

vip_start_news

second_class

vip_end_news

form_requ_button
```

Dagegen dein Code:

```
workstation temp $ tr -d '\n' <test.html | perl -lne 'print for m/class="(\w+)/g' 

vip_start_news

vip_end_news

form_requ_button
```

----------

## notHerbert

Anarcho,

Ja, das funktioniert wunderbar, herzlichen Glückwunsch. 

 :Smile: 

----------

## mv

 *Anarcho wrote:*   

> Man könnte natürlich noch ein "| sort -u" hintendranhängen...

 

Wieso hintendranhängen? Wir sind in perl! Einfach "print split ...." durch "print sort split" ersetzen, und schon kommt es sortiert. OK, uniq müsste man entweder benutzen, oder einen loop um das print setzen. Dazu bin ich jetzt zu faul.

----------

