Shell Bsh / Vergleiche
 
StartSeite | ShellBsh/ | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern

Newsgroup-Artikel (Frage):
ich versuche, mit awk ein Logfile auszuwerten. Ziel ist am Ende
herauszufinden, welches Protokoll welchen Traffic verursacht. Das
Protokoll ist mit proto= gekennzeichnet, der Datentransfer mit rcvd=
und sent=. Leider stehen diese nicht immer in der gleichen Spalte,
sondern das variiert.

Gibt es eine Möglichkeit, mit awk soetwas auszudrücken:

Addiere für alle Elemente der Spalte 5 alle Beträge, die hinter sent=
stehen und das gleiche für rcvd= mit Ausgabe von zwei Endsummen pro
Protokoll?
Hinzugefügte Detailspezifikation:
proto=NAME sent=POSITIVE_DEZ_ZAHLx rcvd=POSITIVE_DEZ_ZAHLx
proto sent rcvd: Groß-/Kleinschreibung wird ignoriert.
Jeweils irgendwo in der Zeile.
Trenner: >= 1 Leerzeichen|TAB
NAME: [a-zA-Z_0-9]; Groß-/Kleinschreibung wird ignoriert.
x: Anhängsel [^0-9], wie bei atoi() bewertet.
Es kann auch z.B. lproto=xxx geben!
Nur Zeilen bewerten, die alle drei Parameter gültig enthalten.

Newsgroup-Artikel (Antwort):
Subject: Re: Logfile auswerten mit awk, wenn Inhalt in Spalten wechselt

> while read LINE ; do
             ^^^^
Falscher Namespace benutzt, siehe SUSv[23], Base Definitions,
«Environment Variables». Typischer PC-DOS-Umsteiger-Fehler.

>   PROTO=$(echo $LINE | tr " " "\n" |
>   grep -E "^msg=|^proto=|^rcvd=|^send=" | sed 's/^msg=.*/proto=vpn/' |
> args  )
>   echo "$PROTO"
> done < MyLogFile

Ah, wie sagenhaft performant, mindestens vier Prozesse pro
Zeile eines Logfiles! Der OP hat nicht ohne Grund geschrieben,
daß er awk dafür nehmen wollte.

echo '...' |
        sed '   :loop
                s/"\([^ "]*\)"/\1/
                s/\("[^"]*\) \([^"]*"\)/\1_\2/
                t loop' |
        awk '{
                proto = "VPN"
                send = 0
                rcvd = 0
                for (i = 1; i <= NF; i++) {
                        if ($i ~ /^send=/)
                                send = substr($i, 6)
                        else if ($i ~ /^rcvd=/)
                                rcvd = substr($i, 6)
                        else if ($i ~ /^proto=/)
                                proto = substr($i, 7)
                }
                send_total[proto] += send
                rcvd_total[proto] += rcvd
        }
        END {
                for (proto in send_total)
                        printf("proto=%s send=%d rcvd=%d\n", proto,
                                send_total[proto], rcvd_total[proto])
        }'

bsh-Lösung:
<logfile
while readl Z
do
   expr "$Z" =:p '%<proto=%([^ %t]%{1,}%)' || continue
   expr "$p"  :: '[^a-zA-Z_0-9]'           && continue
   expr "$Z" =:r '%<rcvd=%([0-9]%{1,}%)'   || continue
   expr "$Z" =:s '%<sent=%([0-9]%{1,}%)'   || continue
   conv -u p
   expr "$P" :: "[rs]_$p" || P="$P r_$p s_$p"
   let "r_$p+=r" "s_$p+=s"
done
><
for pname in $P; do echo $pname: ${{pname}}; done
Mit bsh sind solche Algorithmen plötzlich ganz einfach und übersichtlich;
und: 0 Subprozesse, 0 falscher Namespace, 10..50mal schneller.

--HelmutSchellong

Ohne die Details zu kennen, hier etwas Ähnliches in SprachePerl:

#!/usr/bin/perl

# Format, wobei felder in beliebiger Reihenfolge definiert werden können:
# proto=<protoname> send=<bytes sent> rcvd=<bytes received>

use warnings;
use strict;

my (%count);
LINE: while (<>)
{
  unless (m/proto=(\S*)/)
  {
    warn "proto nicht gefunden: $_";
    next LINE;
  }
  my $proto=lc($1);

  unless (m/sent=(\d*)/)
  {
    warn "sent nicht gefunden: $_" 
    next LINE;
  }
  my $sent = $1;

  unless (m/rcvd=(\d*)/)
  {
    warn "rcvd nicht gefunden: $_";
    next LINE;
  }
  my $rcvd = $1;

  $count{$proto}{'sent'} += $sent;
  $count{$proto}{'rcvd'} += $rcvd;
}

for my $proto (sort keys %count)
{
  print "proto=", $proto , " sent=", $count{$proto}{'sent'}, 
    " rcvd=", $count{$proto}{'rcvd'}, "\n";
}

Und der Rest ist Geschmacksfrage ...

Ich würde es in Perl eher so schreiben (ohne Funktionsgarantie):

#!/usr/bin/perl

use strict;
no strict 'refs';

use vars qw(%scount %rcount %phash $proto $sent $rcvd);

foreach (<>) {
  $sent=$rcvd=$proto='';
  s/(proto|sent|rcvd)=(\S*)/${$1}=$2;/ge;
  if($proto ne '') {
    $phash{$proto}++;
    $scount{$proto}+=$sent;
    $rcount{$proto}+=$rcvd;
  }
}

foreach $proto (sort keys %phash) {
  print "proto=$proto sent=$scount{$proto} rcvd=$rcount{$proto}\n";
}

Um zu beweisen, das perl eine write only sprache ist? zum einen sind hier ebenfalls nur numerische Protokolle zugelassen und zum anderen ist die Lesbarkeit nur marginal besser als das ShellBsh Originalbeispiel. (Vielleicht bin ich auch aufgrund der ungewohnten Syntax nur voreingenommen.)

Nein, dann würde ich es kürzer schreiben. Die nichtnumerischen Protokolle habe ich korrigiert. Bist du sicher, dass in deinem Beispiel die Doppelhashes so funktionieren? IMO sind die Hashes und das print um einiges klarer geworden. Regex ist halt eine Sprache für sich. -- hl

Helmut, Deine Lösung ist wirklich geschickt, aber IMHO alles andere als flüssig lesbar. Was mir aber am meisten Bauchschmerzen bereitet ist, dass die Variablennamen an das Format der Log-Datei gekoppelt sind - d.h., wenn sich das Format ändert (z.B. rcvd->received), müssen Variablennamen angepasst werden! 8-O -- IljaPreuß

Na ja, ich gebe zu, dass das in der Wartung fehleranfällig sein kann (es ging hier aber auch nicht um Projektcode). Es gibt aber auch andere Möglichkeiten, z. B.

#!/usr/bin/perl
use strict;
use vars qw(%phash %scount %rcount);

LINE: foreach (<>) {
  my $proto;
  my $sc;
  s/\bproto=([a-zA-Z_0-9]+)\b/$proto=lc($1)/ei or next LINE;
  s/\bsent=(\d+)\b/$sc=$1/ei or next LINE;
  s/\brcvd=(\d+)\b/$rcount{$proto}+=$1/ei or next LINE;
  $scount{$proto}+=$sc;
  $phash{$proto}++;
}

foreach (sort keys %phash) {
  print "proto=$_ sent=$scount{$_} rcvd=$rcount{$_}\n";
}

Ich bin ja nur Perl-Anfänger. Die Afficionados schreiben das vermutlich in einer Zeile. :-) -- hl

Gefällt mir besser als Deine erste Variante (und ist auch wesentlich kompakter als meine). --DavidSchmitt

#!/usr/bin/perl
use strict;
use warnings;

my %proto;

while(<>) {    #kein! foreach(<>), keine temporäre Liste (Speicher schonen!)

        my ($p,$s,$r); #proto, sent, received

        # reguläre Ausdrücke mit /x für Lesbarkeit
        # s///e ist dabei overhead
        
        m/ \b proto = ( \w+ ) \b /ix    # [a-zA-Z_0-9] == \w
                and $p = lc( $1 )
                and

        m/ \b sent = ( \d+ ) \b /ix
                and defined( $s = $1 )
                and

        m/ \b rcvd = ( \d+ ) \b /ix
                and defined( $r = $1 )

        or next; #alle drei müssen vorhanden sein


        unless( exists $proto{$p} ){
              #Dieser Block könnte auch weggelassen werden
              # da Perl autovivification betreibt, aber naja

                $proto{ $p } = [ $s, $r ]; # HoA-struktur der Einfachheit halber
        }
        else {
                $proto{ $p }[0] += $s;
                $proto{ $p }[1] += $r;
        }
}

foreach (sort keys %proto) {
        printf
                "proto=%s sent=%d rcvd=%d\n",
                $_ , @{ $proto{$_} }
}

Ich habe kommentiert, warum ich das als günstigsten Mittelweg empfinde. --RichardVoß

Wie üblich bei perl: Niemand ist mit der Lösungsvariante des jeweils Anderen zufrieden, während ich immer mit meiner bsh-Lösung zufrieden bin. ;-) --hs [Betonung korrigiert.]
Da ist einiges an Selbstironie drin: Mit bsh operiert eh nur ein Mensch - ich...--hs

Die bsh-Lösung kann ich nicht kommentieren, da ich mit der Sprache nicht vertraut bin. Die letzte Perl-Lösung finde ich aber schon ganz ordentlich. -- ip
Allerdings erfüllt -und erfüllte- keine der perl-Lösungen die [geheimen] Spezifikationen. (Ich meine jetzt nicht \d \S)
SpracheBsh? ist übrigens SpracheKsh? (Vorbild und Unix-Standard-Shell) sehr ähnlich.--hs

Was den perl-Beispielen noch fehlte:
  1. Es wird nur auf den 'proto'-Param. geprüft.
  2. Es werden auch leere Inhalte akzeptiert.
  3. Der proto-Inhalt wird nicht vollständig geprüft. [Auf was?]
    1. [a-zA-Z_0-9]
  4. Kein Parameter wird auf Wortanfang justiert.
  5. Z.B. Proto= oder PROTO= werden nicht erkannt.
  6. Beim proto-Inhalt ist kein ignore-case implementiert.
    1. Ich lese den proto-Inhalt komplett ein bis zum Delimiter
      oder Zeilenende und prüfe dann [a-zA-Z_0-9].
Jetzt sollten alle Bedingungen erfüllt sein, oder?
Aber sicher. Zuvor jedoch waren die Unterschiede so groß, daß man gar nicht von einem Vergleich sprechen konnte.

Ein Fazit kann man hier schon ziehen: Daß mit bsh oder perl bei weitem bessere Lösungen möglich sind als (portabel) mit Unix-Shells+Tools. Die Lösungen aus der Newsgroup sind -gemessen am Problem- grausam. Besonders, wenn man berücksichtigt, daß sie die jetzt vorhandenen (und notwendigen) Detailspezifikationen nur zu geringen Teilen erfüllen.--hs


Frage: Helmut, wie siehst du die Performancesituation bzw. die relativen Stärken und Schwächen zwischen SprachePerl und und ShellBsh? -- hl

Ein Vergleich mit anderen Shells ist recht einfach. Da ist allenfalls ksh ein nennenswerter sportlicher Gegner.

Ein Vergleich mit dem perl-System ist schwieriger. perl ist auf jeden Fall vergleichsweise ein Monster.
bsh kann auch mit 50KB Größe auf kleinen Embedded-Systemen auf 8086-Basis arbeiten. bsh ist eine Shell mit Kommandozeilen-Editor.

Wer perl in fortgeschrittener Weise beherrscht, kann anhand meines harmful-Artikels und des bsh-Manuals Unterschiede leicht erschließen.

Bei einfachen Test-Scripts mit relativ kleinen Schleifen ist perl meist etwa 20% schneller. Das ist wohl so, weil perl kompiliert. Es ist vorstellbar, daß bsh bei anders zusammengesetzten Scripts schneller ist, da der Kompiliervorgang bei perl ja auch Zeit braucht.

bsh ist Stück für Stück aus der konkreten Praxis geboren und gewachsen. Es sind extrem viele Buildins vorhanden, für nahezu jeden denkbaren Zweck. Das mag der Hauptvorteil der bsh gegenüber allen anderen Interpretern sein. Denn man kann letztlich zum Beispiel C voll durch bsh ersetzen - mit Ausnahme der Geschwindigkeit und Gleitkomma.

bsh unterstützt als Schwerpunkt die Kommandoform cmd arg arg arg ... anstelle von zuviel kryptischer Syntax:
Z.B. ifset [-opt] var [var ...] && cmd

(Fortsetzung folgt)


KategorieProgrammierBeispiele KategorieSchellong
StartSeite | ShellBsh/ | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 7. September 2003 18:15 (diff))
Suchbegriff: gesucht wird
im Titel
im Text