Ich versuche ein Shellscript zu schreiben was die gesamte Logginzeit eines Nutzers an einem Rechner ermittelt werden soll

Hinweis: In dem Thema Ich versuche ein Shellscript zu schreiben was die gesamte Logginzeit eines Nutzers an einem Rechner ermittelt werden soll gibt es 14 Antworten auf 2 Seiten. Der letzte Beitrag () befindet sich auf der letzten Seite.
  • Hallo, ich versuche ein Shellscript zu schreiben das ermitteln soll, wie lange der angegebene Nutzer insgesamt auf diesem Rechner eingeloggt war.


    um erstmal einmal die eingabe vom Nutzer auszuwerten habe ich if schleifen so wie das Kommando ping und finger benutzt


    Meine Frage bezieht sich am Schluss. Wie kann ich die ermittelten Loginzeiten aus Last jetzt zusammenrechnen
    denn hier komme ich mit dem Befehl let nicht weiter. ?(


    Ich danke für die Antworten :thumbup:

    Für den Inhalt des Beitrages 102448 haftet ausdrücklich der jeweilige Autor: Timmi0701

  • ich werde dir nicht recht weiterhelfen können, aber vermutlich wird auch interessant sein, was die Konsole ausgibt.
    Vor allem $ergebnis.


    Wenn ich die Beschreibung von let anschaue



    Zitat von man let


    let arg [arg ...]
    Each arg is an arithmetic expression to be evaluated (see ARITHMETIC EVALUATION above). If the last arg
    evaluates to 0, let returns 1; 0 is returned otherwise.

    sieht das für mich aus, als gäbe es sowieso nur einen boolschen Wert. (oder?)

    Für den Inhalt des Beitrages 102449 haftet ausdrücklich der jeweilige Autor: JeyF123

  • Ich denke mit einer einfachen Summierung ist es nicht getan.
    Es können ohne weiteres mehrere Logins des gleichen Users da sein, dann müsste man Minimum und Maximum finden und die Differenz berechnen.
    Es können Sitzungen über die Datumsgrenze hinweg gehen.
    Es kann crash und andere Einträge geben, die sich rechnerisch nicht bearbeiten lassen.


    Ich denke, wenn man alle Klippen umschifft hat, könnte es mit awk gehen.

    Für den Inhalt des Beitrages 102452 haftet ausdrücklich der jeweilige Autor: Tar Zahn

  • Ich würde da mal schlicht einen Befehl probieren:


    Code
    # erst für das Leben lernen
    man last
    
    
    # und dann andächtig staunen.
    # is ja Weihnachten
    last

    Ich hänge später noch eine kleine vernichtend gigantische Kritik an; da sind einige Konstrukte eher nicht zu empfehlen.

  • Das ist definitv falsch.
    JEDES Kommando gibt einen sogenannten EXIT CODE auch RETURN CODE genannt zurück.
    Das passiert auf Betriebssystemebene, was heißt, dass das der Kernel macht. Die Shell oder die Kommandos haben darauf KEINERLEI Einfluß.
    Man kann diesen Rückgabewert aus der besonderen Shellvariabeln $? lesen, oder ignorieren. Mehr aber auch nicht.


    Es ist also vollkommen OK, das zum Rechnen zu verwenden.
    Fast.
    Es ist "alte" POSIX Syntax.
    Es gibt besseres:
    echo $(( zeit - login ))
    ergebnis=(( zeit - login ))
    Da das doppelte Klammerpaar einen arithmetischen Kontext definiert, muss man innerhalb dieses doppelten Klammerpaares nicht einmal mehr das $- Zeichen zur Expansion bemühen.

  • Teil 1
    corrrected copy:

    Bash
    #!/bin/bash
    #Überprüfe Parametereingabe
    echo " "
    if [ $# == 2 ]
    then
    echo "Parameter wurden erfolgreich eingelesen"
    else
    echo "Parameter fehlt oder wurde nicht gefunden"
    exit
    fi


    Das ist mal ganz schön frech gelogen. Gelesen hast du da nichts. Und unklare Sprache.
    Ein Parameter ist da, oder nicht. Suchen nutzt da ziemlich genau gar nix. Wo wolltest du denn da suchen?
    Das machen wir später korrekt.
    Schlecht jedoch ist, dass du das Script bei dem Parameterfehler mit Exitcode 0 (exit) beendest. (ein schlichtes "exit" ist die Abkürzung für exit 0
    Da es ein Fehler ist, muss das auch "gemeldet" werden. Korrekt wäre also ein exit 1.
    Oder jede andere ganze Zahl bis 255. Es gibt sogar dafür Konventionen. Man solle "Userfehler" immer bei 65 beginnen lassen und Werte von 0 bis 127 den Konventionen entsprechend für die "normalen" Systemfehler nehmen.
    Egal, was du machst: Ein Rückgabewert von 0 im Fehlerfall ist ein Fehler.


    Bash
    #Überprüfen ob Rechner Exestiert.
    echo " "
    ping -c 1 $1 > /dev/null


    Das ist keine gültige Möglichkeit. Obwohl ein System, das sich korrekt nach den RFCs richtet, auf einen ping auch antworten sollte, haben viele vermeintliche Admins den Echoserver abgeschaltet. Da gilt das Vogel-Strauß-Prinzip: Wenn ich meinen Kopf in den Sand stecke, dann kann mich niemand sehen. Das ist heute -leider- sehr viel öfter der Fall, als es von den RFCs erlaubt ist.
    Hat man das Netzwerk unter Kontrolle, ist das schon OK. Aber man sollte das beachten. Mit nmap lassen sich Scans durchführen, die auch laufende Hosts entdecken, die auf keinen Ping antworten. Besser ist es jedoch immer, gleich den gewünschten Server auf dem jeweiligen Port für den jeweiligen Service direkt zu testen.


    Bash
    if [ $? -eq 0 ]
    then
    echo "*****Eingabe Wahr, $1 Existiert*****"
    else
    echo "*****Fehler $1 Exestiert nicht oder wurde nicht Gefunden*****"
    echo " Programm beendet!"
    exit
    fi


    Siehe vorherige Bemerkung zu exit


    Bash
    #Überprüfen ob Nutzer Exestiert.
    echo " "
    finger $2


    Jetzt kommen wir zu dem Denkfehler. Man mag zwar finger dafür verwenden, weil das auch auf dem lokalen Host funktioniert,
    aber auch hier gilt: Oft läuft der finger Dienst gar nicht.
    Da du zwei Parameter als Eingabe verlangst, aber nur einen Parameter (nämlich $2, was ein Username sein soll) verwendest, leite ich aus deinem Text ab, dass das der Hostname sein sollte. Von außen wirst du kaum einen Server finden, der dir anwortet. Du wirst immer sowas als Antwort erhalten:
    "finger: Der Name oder der Dienst ist nicht bekannt". Vergiss dieses Kommando einfach. Das war schon obsolet, als du geboren wurdest.
    Und für die lokale Abfrage ist das auch völlig unnötig.
    Und "Exestiert" existiert nicht einmal in kLEINSCHREIBUNG.
    Ich gehe also im Weiteren davon aus, dass dieses Script auf dem jeweilig zu untersuchenden Host läuft.


    Bash
    #Verzeichnis tmp anlegen
    echo " "
    mkdir tmp | echo $? Verzeichnis tmp wurde angelegt um wtmp zu entpacken
    echo "Kopiere alle log Daten in tmp"
    cp /var/log/wtmp* ~/tmp


    Da hätte ich jetzt noch ein paar eher kosmetische Bemerkungen.
    Einmal gibt es natürlich auf jedem System bereits ein Tempverzeichnis. Es mag aber auch gute Gründe geben, seine Daten dort nicht auszulagern.
    Du erstellst einfach ein Verzeichnis. Das würde schon beim zweiten Aufruf zu einem Fehler führen, weil das Verzeichnis dann ja existiert.
    Man kann dazu mkdir -p something verwenden, was die Fehlerausgabe unterdrückt, und das Script läuft weiter.
    Es gibt aber besseres: Lass das doch einfach das System machen. Es gibt den Befehl mktemp. Der tut genau das. Er erzeugt dir ein temporäres File. Dabei wird vom System sichergestellt, dass es diese Datei noch nicht gibt.



    In Rechenzentren mit NAS (NetworkAttachedStorage) oder SAN (StorageAttachedNetwork) bei dem zahllose Hochleisungsrechner auf den Storagepool einhacken, kann es dennoch vorkommen, dass zwei Maschinen die gleiche Datei erzeugen wollen. Damit keine verliert, ist es sicherer ein Verzeichnis zu erstellen. Die Operation "Erstelle Verzeichnis" ist garantiert atomar. Was heißt, dass wirklich nur ein einziger Prozess das Verzeichnis erstellen kann. Es könnte sonst zu Rechteproblemen oder
    zu nicht gewolltem Informationsabfluss kommen.



    Jetzt kommen ein paar Verwirrungen, und wir zur eigentlichen Frage.
    Also fast gleich. (Das Kopieren der wtmps und Entpacken übergehe ich)

    Bash
    echo "Liste alle log daten vom Nutzer $2 auf"
    $login = last -F -f ~/tmp/wtmp | grep $1 | cut -c 50-58


    Der grep Befehl nimmt einen anderen Parameter, als $2. Was steht denn __wirklich__ in $1?
    Ich nehme ja nur an, dass dort der Host stehen sollte. Du schweigst dich darüber aus.


    Damit führt die nächste Zeile ganz sicher zu einem Command-not-found Fehler,
    weil die Variable "zeit" niemals gesetzt wurde. Nach dem Parsen versucht die Bash also den Befehl:
    <leerzeichen>= date
    auszuführen. Den gibt es aber nirgends. Das ist also kapital falsch:

    Bash
    "$zeit = date"


    Und das eigentliche Problem wird im nächsten Teil gelöst.
    (Grad mal 10 000 Zeichen zulässig! Wie soll ich da anständig labern?)

  • Teil 2


    Aber jetzt endgültig das eigentliche Problem.
    Du schneidest mit deiner Pipe das Login Datum und Uhrzeit aus.
    Die Dauer nicht. Was natürlich nicht genügt.
    Ziemlich einfach kann das mit awk bewerkstelligen. Der folgende Befehl macht das:
    last -F | awk -F '[ ]\+' '/'$user_to_search"'/{print $1,"\"" $4, $6, $7,$8 "\"","\""$10,$11,$12,$13 "\"", $15 }'
    Und das ist etwas erklärungsbedürftig.
    Klar ist der Anfang last -F | awk].
    Das -F '[ ]\+' schon weniger. Wir setzen damit die awk-interne Variable FS (FieldSeparator) auf de RegulärenExpression [ ]\+, der besagt, dass unser Feldtrenner eines oder mehrere Leerzeichen hintereinander ist. Da die "Felder" von last -F verschieden lang sein können, stehen zwischen den tatsächlichen Buchstaben verschieden viele Leerzeichen. awk aber interpretiert standardmäßig JEDES Leerzeichen als einen Feldtrenner, und die einzelnen "Felder" würden ganz verschiedene Nummern haben. (awk nummeriert die Felder jeder gelesenen Zeile einfach von 1 bin NF (interne awk Variable Number-of-Fields) durch. Die $0 wäre die gesamte Zeile. Nicht, das, was wir wollen. Aber mit unserem Regex haben wir das Anmeldedatum und das Abmelddatum an fixen Feldpositionen und können mit dem eigentlichen awk- Programm leicht das Gewünschte ausgeben.
    Ein awk Programm besteht aus Blöcken { dosomething }, dem ein logischer Ausdruck oder eine Adresse oder ein Adressbereich vorangestellt sein kann. Ist eine solcher Ausdruck einem Block vorangestellt, so wird der Block nur ausgeführt, wenn der Ausdruck wahr ergibt. Wir verwenden den Ausdruck [[/someUser/[/tt] und den Block { print........ $15 }. Damit wir in awk Shellvariablen verwenden können, gibt es mehrere Methoden. Ich bevorzuge die "Escape-to-Shell" Methode.
    '/'$user_to_search"'/ Der erste Apostroph beginnt das awk-Script. Und dort eröffnet der "Suchoperator" / den Ausdruck. Der zweite Apostroph beendet das awk-Script gleich wieder für kurze Zeit und "escaped" zur Bash, die jetzt für "$user_to_search" den aktuellen Inhalt der Shellvariablen user_to_search einsetzt. Das Quoting ist wichtig, damit nicht ein Leerzeichen innerhalb des Usernamens (ja, das ist erlaubt, aber dringend davon abzuraten) sofort zu einem Fehler führt. Man sollte (fast) IMMER solche Expansionen quoten.
    Jetzt drucken wir einfach die entsprechenden Felder, nicht ohne zwischen Login und Logout Zeitangaben doppelte Anführungszeichen einzufügen "\"" (awk will Zeichenketten. Innerhalb der "Zeichenkette" steht ein gequotetes ", was dann auch erscheint und eben kein "Hier-beginnt-ein-String"- Zeichen für awk ist.


    Damit erhalten wir Zeilen wie:
    looser "Wed 16 23:14:53 2016" "crash (20:52) " 
    looser "Wed 16 21:45:37 2016" "Wed Nov 16 21:01:58"


    Und das lässt sich nun prima weiterverarbeiten. Wir haben auch noch die Dauer mitangeben. Wie man sieht kann ein Login auch crashen und die Einlogdauer ist dann kein korrektes Datum/Zeit Feld. Diese Behandlung überlasse ich euch.


    Wir machen jetzt aus den beiden Datum/Zeit Angaben rechenbare Sekundenangaben.
    Das liese sich sehr wohl auch in awk regeln. awk ist eine ziemlich mächtige Programmiersprache, die nicht nur Floatingpointrechnungen kann, was die Bash nicht kann, sondern auch allerlei Funktionen bereitstellt. Selbst Userdefined Functions und nachladbare awk-Bibilotheken kann man sich schreiben, laden und ausführen.
    Wir machen das dennoch in der Bash.
    Der Befehl date kann wesentlich mehr, als nur das aktuelle Datum ausgeben. Man kann sehr viele Datums und Zeitangaben basteln lassen.

    Es war mir nicht klar, was du mit dem Einlogzeitpunkt wolltest.
    Wenn du nur an der Gesamtzeit interessiert bist, geht das ganze natürlich viel einfacher. Ohne besondere Tests für Tages, Monats und Jahreswechsel, die hier gar nicht erscheinen.
    Ein schlichtes

    Bash
    last -F | awk '/'"$user_to_search"'/{print $NF }'

    gibt dir alle Logindauereinträge für den User user_to_search. (Und interessiert sich auch nicht für evtl. aufgetretene Crashes oder sonstige Ungebilde)
    Die haben das Format (tage+stunden:minuten)

    Natürlich sollte man die maximale Grösse von Integer, die die Bash verarbeiten kann berücksichtigen:

    Bash
    echo dieses Script könnte Probleme machen, wenn Leute länger als
     $(( $(getconf INT_MAX) / (24 * 60*  60 *365 )  )) Jahre  eingeloggt bleiben.
    echo und es macht ganz sicher Probleme wenn wir kurz vor dem $( date -d  @$( getconf INT_MAX )  ) stehen.
  • Zitat


    Wie man sieht kann ein Login auch crashen und die Einlogdauer ist dann kein korrektes Datum/Zeit Feld. Diese Behandlung überlasse ich euch.

    Faulpelz!

    Für den Inhalt des Beitrages 102475 haftet ausdrücklich der jeweilige Autor: Tar Zahn

  • Der Liebe Gott ist ein weiser Mann.
    Er gab mir meine überbordende Intelligenz,
    damit ich meine endlose Faulheit ausleben kann.


    Jeder hat mal Pech.
    In meiner Inkarnation ihr.