Siduction Forum

Siduction Forum => Scripting & Kernelhacking => Topic started by: bluelupo on 2015/01/22, 19:23:19

Title: Zeilen zusammenfassen
Post by: bluelupo on 2015/01/22, 19:23:19
Hallo zusammen,

ich würde gerne folgende extrahierte Zeilen aus /var/log/apt/history.log jeweils in einer zusammenfassen:

So siehst es aus:
Code: [Select]
Start-Date: 2015-01-12  17:11:15
Commandline: apt-get dist-upgrade
Start-Date: 2015-01-14  16:58:30
Commandline: apt-get dist-upgrade

Soll dann so aussehen:
Code: [Select]
Start-Date: 2015-01-12  17:11:15 Commandline: apt-get dist-upgrade
Start-Date: 2015-01-14  16:58:30 Commandline: apt-get dist-upgrade

Wie kann ich das in einem Shellscript-Einzeiler erledigen?
Title: Re: Zeilen zusammenfassen
Post by: bluelupo on 2015/01/22, 20:27:26
Hab's gelöst und gebe mir selbst die Antwort ;-)

Code: [Select]
egrep '(Start-Date:.*|Commandline: apt-get )' /var/log/apt/history.log | sed '$!N;s/\n/ /'

Der sed-Befehl fügt immer paarweise die Zeilen zusammen. Die Lösung kam von dieser Seite (http://sed.sourceforge.net/sed1line_de.html) unter "Füge Zeilenpaare nebeneinander zusammen".

Okay, jetzt habe ich die Lösung, aber verstehen tue ich den sed-Ausdruck nicht :-( Nur soviel das irgendwas durch ein Leerzeichen ersetzt wird. Vielleicht gibt es hier Spezialisten die das auflösen können.
Title: Re: Zeilen zusammenfassen
Post by: hefee on 2015/01/22, 21:04:44
also "s/\n/ /" ist die normale ersetzung von  newlines durch leerzeichen, denn \n ist ein newline Zeichen und das wird durch ein Leerzeichen ersetzt. Jetzt ist aber leider sed so gestrickt, dass es nur zeilenweise arbeitet, also müssen wir dafür sorgen das immer zwei Zeilen zusammen in den Puffer kommen, dann ereichen wir mit N;

Sauberer wäre es wenn du so was schreibst: N; s/\nCommandline/ Commandline/, dann stellst du sicher, das wirklich die richtigen Zeile zusammengefasst werden.
Title: Re: Zeilen zusammenfassen
Post by: bluelupo on 2015/01/22, 21:09:43
Danke für die Erklärung hefee  :) :)
Title: Re: Zeilen zusammenfassen
Post by: musca on 2015/01/23, 23:56:10
Hallo,

guter Hinweis von hefee, das blinde paarweise Zusammenfügen reicht vermutlich nicht. Denn ich habe in meinem history.log auch Einträge dieser Art (also ohne die folgende "Commandline"):
Start-Date: 2015-01-22  18:11:10
End-Date: 2015-01-22  18:11:11

Start-Date: 2015-01-22  18:11:33
End-Date: 2015-01-22  18:11:33

Ich vermute, dass ich hier den Solver von Aptitude bei der Sicherheitsabfrage "Wollen Sie wirklich?" abgebrochen habe.
Dadurch kann also die paarweise Verkettung aus dem Takt kommen.


Mit perl könnte eine Lösung so aussehen ( Newline nur vor dem Wort Commandline durch Space ersetzen)
Code: [Select]
perl -0ne 's/\RCommandline/ Commandline/g; print;' /var/log/apt/history.log | grep Commandline


Nach einer kleinen Optimierung entfällt sowohl das Multiline-Reading (-0) als auch noch das anschliessende grep:
Code: [Select]
perl -ne 'chomp;print$l=(/^Com/)?"$l$_\n":"";$l="$_ "' /var/log/apt/history.logDurch die Optionen -n wird dabei implizit folgendes ausgeführt:
#!/usr/bin/perl
# concatenate all lines beginning with "Commandline" with the previous line
 use strict;
 use warnings;  # -w switch

{
  while (<>) {               # The -n switch reads filename arguments or STDIN
    chomp;                   # remove trailing newline
    if (/^Commandline/) {    # actual record does begin with "Commandline"
      print "$last$_\n"      # append previous record with actual record
    } else {                 # not yet found a Commandline!
      print ""               # so print nothing in this case (avoid grep)
    }
    $last = "$_ "            # remember actual record until next loop
  }
}


Okay, den else-Teil läßt man nun weg.
Schließlich kann man die -l Option verwenden, um chomp und print zu vereinfachen.
Code: [Select]
perl -wlne 'print "$last $_" if (/^Command/); $last=$_' /var/log/apt/history.log
oder
perl -wlne '/^Command/ && print "$last $_"; $last=$_' /var/log/apt/history.log

Die Syntax ist auf einmal wieder recht ähnlich zu awk:
Code: [Select]
awk '/^Command/ { print last " " $0;} { last=$0; }' /var/log/apt/history.log
oder sed:
Code: [Select]
sed '/^Com/!{h;d};/^Com/{x;G;s/\n/  /}' </var/log/apt/history.log
[edit: hier noch ein Oneliner in bash:]
Code: [Select]
while read line; do [[ $line =~ Commandline ]] && printf "%s  %s\n" "$last" "$line"; last="$line"; done < /var/log/apt/history.log

perlish greetings
musca