• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

sed negation des Suchmusters

stka

Guru
Hi Leute,

ich hab da mal folgendes Problem. Ich habe eine datei mit Zeilenummern, als Beispiel hier mal die passwd:
Code:
1 root:x:0:0:root:/root:/bin/bash
2 daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3 bin:x:2:2:bin:/bin:/bin/sh
4 sys:x:3:3:sys:/dev:/bin/sh
5 sync:x:4:65534:sync:/bin:/bin/sync
6 games:x:5:60:games:/usr/games:/bin/sh
7 man:x:6:12:man:/var/cache/man:/bin/sh
8 lp:x:7:7:lp:/var/spool/lpd:/bin/sh
9 mail:x:8:8:mail:/var/mail:/bin/sh
10 news:x:9:9:news:/var/spool/news:/bin/sh
11 uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
12 proxy:x:13:13:proxy:/bin:/bin/sh
13 www-data:x:33:33:www-data:/var/www:/bin/sh
14 backup:x:34:34:backup:/var/backups:/bin/sh
15 list:x:38:38:Mailing List Manager:/var/list:/bin/sh
16 irc:x:39:39:ircd:/var/run/ircd:/bin/sh
17 gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
18 nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
19 libuuid:x:100:101::/var/lib/libuuid:/bin/sh
20 Debian-exim:x:101:105::/var/spool/exim4:/bin/false
21 statd:x:102:65534::/var/lib/nfs:/bin/false
22 messagebus:x:103:108::/var/run/dbus:/bin/false
23 avahi:x:104:109:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
24 gdm:x:105:112:Gnome Display Manager:/var/lib/gdm:/bin/false
25 haldaemon:x:106:114:Hardware abstraction layer,,,:/var/run/hal:/bin/false
26 hplip:x:107:7:HPLIP system user,,,:/var/run/hplip:/bin/false
27 linux:x:1000:1000:linux,,,:/home/linux:/bin/bash
ich möchte folgendes ich möchte alle Zeilen sehen die in den Zeilnummern keine "1" haben. Ich habe folgendes probiert:
Code:
cat userlist2.txt | awk '{ print $1 }' | sed -n '/[^1]/p'
Ich sehe aber immer alle Zeilen mit Ausnahme der Zeile 1 und 11 :???:
Will ich mir alle Zeilen mit einer "1" anzeigen lassen mache ich das so:
Code:
cat userlist2.txt | awk '{ print $1 }' | sed -n '/1/p'
Das funktioniert so wie es soll.

Das folgenden funktioniert auch und gibt das gewünschte Ergebnis:
Code:
cat userlist2.txt | awk '{ print $1 }' | sed -n '/1/!p'
Ich möchte das aber auch mit der negierten Liste des Musters, also mit [^1] machen können. Wer hat da mal eine Lösung UND eine gute Quelle im Netz wo ich das mal nachvollziehen kann.
 

framp

Moderator
Teammitglied
Code:
egrep "^[^1]+ " t.dat
liefert das gewünschte Ergebnis. Wenn ich den regulären Ausdruck in sed reinfüttere kommt nichts. Offensichtlich kennt sed den + Operator nicht.

Update: Geht doch: Muss man nur escapen:
Code:
cat t.dat | awk '{ print $1 }' | sed -n '/^[^1]\+$/p'
Der reguläre Ausdruck verlangt dass der Match

1) in einer neuen Zeile anfängt -> ^
2) gefolgt von allen Zeichen ausser 1 -> [^1]
3) und das wenigstens 1 oder mehrere Male -> + und dann der String zu Ende ist -> $

Das dumme ist das der Parser aufhört sobald ein Match da ist. Deshalb bekommst Du die 21 wenn Du kein Stringende im Ausdruck verlangst.
 

spoensche

Moderator
Teammitglied
Wenn sed mit -r aufgerufen wird kann man sich das Escapen des + sparen. -r ist extended Regex, ohne -r ist Posix.
 

framp

Moderator
Teammitglied
spoensche schrieb:
Wenn sed mit -r aufgerufen wird kann man sich das Escapen des + sparen. -r ist extended Regex, ohne -r ist Posix.
Guter Hinweis. Hier ist der Unterschied von regulären und extendended regulären Ausdrücken beschrieben (english)
 
A

Anonymous

Gast
stka schrieb:
ich möchte folgendes ich möchte alle Zeilen sehen die in den Zeilnummern keine "1" haben. Ich habe folgendes probiert:
.....
Ich möchte das aber auch mit der negierten Liste des Musters, also mit [^1] machen können. Wer hat da mal eine Lösung UND eine gute Quelle im Netz wo ich das mal nachvollziehen kann.

sed :
Warum denn Suchmuster mittels Regex negieren. sed bietet mit dem ! vor dem Befehl die Möglichkeiten der Negation und hat auch vollkommen gegensätzliche Befehle, p== print d==delete, das macht eine Vielzahl von möglichen Kombinationen die funktionieren würden.

Jedoch beim genauen Hinsehen nach deiner Aufgabenstellung, alle "Zeilennummern ohne 1" wird es extrem schwierig einen komplexren regulären Ausdruck zu schreiben, der das negieren würde. Also warum den kompliziertesten Weg nehmen der möglich wäre ?

Ich würde es so hier machen, mal am Beispiel der /var/log/messages (durch das automatische Einrücken der Zeilennummer ist der Reguläre Ausdruck am Beginn entprechend angepasst worden.
Code:
cat -b /var/log/messages | sed '/^ *[2-9]*[1][0,2-9]*/!d'
Einige hoffentlich hilfreiche Beispiele für das zusammenstellen sollten unter sed zu finden sein.

awk :
noch einfacher geht es ganz alleine mit awk
Code:
cat -b /var/log/messages | awk '$1 ~ /1/'
hier würde sich auch die Negation einfacher werden, da hier die Möglichkeiten bestehen den Regulären Ausdruck mit Operationen direkt zu vergleichen.
Das Gegenteil der obrigen Listes würde zB awk wie folgt ausfiltern.
Code:
awk '$1 !~ /1/'
Auch hier die awk Beispiele sollten hoffentlich helfen sich eigene Filter aufzubauen.

robi
 
A

Anonymous

Gast
framp schrieb:
Code:
egrep "^[^1]+ " t.dat
liefert das gewünschte Ergebnis. Wenn ich den regulären Ausdruck in sed reinfüttere kommt nichts. Offensichtlich kennt sed den + Operator nicht.
muss in sed entwertet werden \+ sonst wird es als normales Zeichen "+" angesehen.

robi
 
OP
S

stka

Guru
Das ist ja mal wieder sehr gut was ihr hier zusammen schreibt. Das ganze ist für einen meiner Teilnehmer hier der wollte das unbedingt mit der negation testen. Das es mit "!p" geht hatte ich ja geschrieben. Aber wir werden das morgen mal testen mit dem "+". Ich danke euch schon mal. Ach so die Anleitung in englisch ist keine Thema, sprechen eh alle englisch ;-)
 

framp

Moderator
Teammitglied
stka schrieb:
... Das es mit "!p" geht hatte ich ja geschrieben. ...
:eek:ps: Hatte ich übersehen. Liegt aber auch daran dass ich den ! bei sed noch nicht kannte :roll:
stka schrieb:
...Aber wir werden das morgen mal testen mit dem "+". Ich danke euch schon mal. ...
Habe ich auch ... mit regulären Ausdrücken bekommt man manchmal andere Ergebnisse als man erwaret. Deshalb habe ich schon alles getestet. Würde mich wundern wenn Du nicht dieselben Ergebnisse bekommst, Wichtig ist aber auch das zweite ^ und das $.
 
A

Anonymous

Gast
abgdf schrieb:
Am einfachsten ist wohl:
Code:
grep -v "^1" userlist2.txt
;)
So so, und seit wann hat die 21 und die 613 keine 1 :???:
Code:
echo -e "1\n11\n21\n613\n33\n45\n" | grep  -v "^1"
21
613
33
45
robi
 

abgdf

Guru
Hast recht; sorry, hatte ich nicht genau genug gelesen.
Aber warum will er denn die 21 und die 613 nicht sehen? Verstehe den Grund für eine solche Aufgabenstellung nicht wirklich. Was soll das bringen, alle Zeilennummern, außer denen, die irgendwo eine 1 haben, zu finden?.

Wenn's denn wirklich sein müßte, würd' ich's dann wiederum in einer Skriptsprache machen: Jede Zeile nacheinander durchgehen, Zeile splitten, Stelle 0 des Splitarrays nach 1en untersuchen, wenn keine gefunden, ausgeben.
Code:
#!/usr/bin/perl

use warnings;
use strict;

open(FH, "<userlist2.txt");
my @a = <FH>;

foreach (@a) {
    my @b = split;
    unless ($b[0] =~ m/1/){
        print;
    }
}
Gruß

Edit2: Oder nochmal zum Einzeiler verkürzt:
Code:
perl -ne '@b=split;unless($b[0]=~/1/){print}' userlist2.txt
 

framp

Moderator
Teammitglied
abgdf schrieb:
Hast recht; sorry, hatte ich nicht genau genug gelesen.
Aber warum will er denn die 21 und die 613 nicht sehen? Verstehe den Grund für eine solche Aufgabenstellung nicht wirklich. Was soll das bringen, alle Zeilennummern, außer denen, die irgendwo eine 1 haben, zu finden?.
Ging mir auch so - bin ich auch erst drüber gestolpert. Warum? Er wird seinen Grund haben - dieses Problem ist eben etwas schwieriger.
 

RME

Advanced Hacker
Hallo,

Also, dass die gestellte Aufgabe auch anders (und besser) gelöst werden kann wurde bestens dokumentiert, aber:

der wollte das unbedingt mit der negation testen
Daher hier mein Vorschlag mit "^1"

Für die Darstellung der ganzen Zeile:

Code:
cat datei.txt | sed -n '/^[^1\ ]*\ /p'
und die Variante wo nur die Zeilennummer ausgegeben werden soll:

Code:
cat datei.txt | awk '{ print $1 }' | sed -n '/^[^1]*$/p'

Beispiel:

Code:
> cat datei.txt | sed -n '/^[^1\ ]*\ /p'
2 daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3 bin:x:2:2:bin:/bin:/bin/sh
4 sys:x:3:3:sys:/dev:/bin/sh
5 sync:x:4:65534:sync:/bin:/bin/sync
6 games:x:5:60:games:/usr/games:/bin/sh
7 man:x:6:12:man:/var/cache/man:/bin/sh
8 lp:x:7:7:lp:/var/spool/lpd:/bin/sh
9 mail:x:8:8:mail:/var/mail:/bin/sh
20 Debian-exim:x:101:105::/var/spool/exim4:/bin/false
22 messagebus:x:103:108::/var/run/dbus:/bin/false
23 avahi:x:104:109:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
24 gdm:x:105:112:Gnome Display Manager:/var/lib/gdm:/bin/false
25 haldaemon:x:106:114:Hardware abstraction layer,,,:/var/run/hal:/bin/false
26 hplip:x:107:7:HPLIP system user,,,:/var/run/hplip:/bin/false
27 linux:x:1000:1000:linux,,,:/home/linux:/bin/bash
>
Gruss,
Roland
 

framp

Moderator
Teammitglied
RME schrieb:
Code:
cat datei.txt | awk '{ print $1 }' | sed -n '/^[^1]*$/p'
Der Unterschied zu meinem Vorschlag ist dass Du * und ich + vorschlage. Das führt dazu, dass bei * auch Zeilen angezeigt werden, die keine führenden Nummern haben. Im folgenden Beispiel habe ich noch die erste Zeile dupliziert und die 1 im Datensatz entfernt.
Code:
framp@obelix:/home/framp/scripts> cat t.dat | awk '{ print $1 }' | sed -n '/^[^1]*$/p'
mooroot:x:0:0:root:/root:/bin/bash
2
3
4
5
6
7
8
9
20
22
23
24
25
26
27
Da gemäß der Aufgabenstellung immer eine Nummer voransteht liefern beide RegEx dasselbe Ergebnis. Es besteht aber ein semantische Unterschied und der ist, dass bei + nur die Zeilen matchen wo eine Nummer ohne 1en voranstehen muss während bei * auch Zeilen matchen wo keine Nummer voransteht.
 

framp

Moderator
Teammitglied
abgdf schrieb:
... Oder nochmal zum Einzeiler verkürzt:
Code:
perl -ne '@b=split;unless($b[0]=~/1/){print}' userlist2.txt
Da Perl auch die gesamte Funktionalität von sed beinhaltet funktioniert das natürlich auch. Allerdings finde ich, dass egrep das adäquate Linux Tool dafür ist:
framp schrieb:
Code:
egrep "^[^1]+ " t.dat
denn Linux/Unix vertritt ja die Philosophie, diverse kleine Tülchen zur Verfügung zu stellen, die ihre kleinen angegrenzten Aufgaben effizient erledigen, und diese dann per | zu verbinden um damit komplexere Probleme zu lösen - und das alles ohne jegliches Programmieren. D.h. auch Benutzer ohne Programmierkenntnisse können relativ einfach auch komplexe Aufgaben bearbeiten und lösen durch einfache zusammenstöpseln der kleinen Linux Tülchen.

Weitere Infos zur UnixPhilosophy:
(1) Unix Philosophy
(2) Die Unix-Philosophie

Kernsatz:
Doug McIlroy schrieb:
This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
Auf Deutsch: Das ist die Unix Philosophie: Schreibe Programme die genau eine Sache machen - und das sehr gut. Schreibe Programme so, dass sie zusammenarbeiten können. Schreibe Programme so dass sie Textdateien benutzen können, denn das ist die allgemeine allverständliche Schnittstelle

Mittlerweile scheint das eher xml zu sein - aber das ist OT.
 

abgdf

Guru
framp schrieb:
Allerdings finde ich, dass egrep das adäquate Linux Tool dafür ist:
framp schrieb:
Code:
egrep "^[^1]+ " t.dat
Stimmt schon. In Perl ginge entsprechend auch noch:
Code:
perl -ne 'if(/^[^1]+ /){print}' userlist2.txt
Obwohl ich solche Sachen schon irgendwie zusammenbauen kann, hab' ich neulich mal Perl-Code gesehen, da war eine RegEx, die ich beim besten Willen einfach nicht mehr verstehen konnte.
Deswegen vermeide ich reguläre Ausdrücke so weit wie möglich.
Aber egrep ist schon ganz passend für das "Problem". :)
 

framp

Moderator
Teammitglied
RME schrieb:
...Du hast absolut recht! Ich habe Deinen Vorschlag zuwenig genau beachtet (will heissen, meiner war überflüssig). Sorry :eek:ps:
Sehe ich nicht ganz so - von wegen überflüssig. Bei RegEx kommt es leider wirklich auf jedes einzelne Zeichen drauf an. Und das zeigt Dein Beispiel sehr deutlich - obwohl - für die gegebene Aufgabenstellung und Datenvoraussetzungen - keine Auswirkung hat, denn man kommt dasselbe Ergebnis.
 

framp

Moderator
Teammitglied
abgdf schrieb:
... Obwohl ich solche Sachen schon irgendwie zusammenbauen kann, hab' ich neulich mal Perl-Code gesehen, da war eine RegEx, die ich beim besten Willen einfach nicht mehr verstehen konnte.
Deswegen vermeide ich reguläre Ausdrücke so weit wie möglich.
Mit den RexEx kann man sehr komplexe Dinge machen, aber ab einer gewissen Komplexität bin ich auch der Meinung dass man es lassen sollte. Denn ich bin mir sicher dass der Autor der komplexen RegEx dieses auch nach einer gewissen Zeit nicht mehr versteht - ausser er hat die RegEx ausgiebig dokumentiert - aber wer tut das schon. Ich vergleiche RegEx immer mit APL - sehr mächtig - aber sehr schnell auch nicht mehr les- und wartbar. Beides ist deshalb mit gesundem Menschenverstand einzusetzen :D

Nur mal so ein Beispiel zu APL: Conways game of life in APL
 
Oben