Aus Linux-Magazin 01/2001

Mehr betrachten mit less

Wer kennt nicht das Problem: Soeben wurde ein File aus dem Internet heruntergeladen, das ein vielversprechendes Programm enthalten soll. Doch bevor man an das Auspacken und Installieren geht, möchte man vielleicht erst einmal das README lesen oder eine zugehörige Manpage betrachten.

Wie war das gleich mit dem Extrahieren von Files? Das tar-Kommando hat man ja meist im Griff, zip-Dateien klappen auch noch mit Nachdenken. Doch wie in aller Welt bekomme ich ein einzelnes File aus einer RPM-Datei? Richtig, da gab es ja noch den Midnight Commander und ähnliche Programme, zumindest wenn man unter Linux arbeitet. Doch was macht man auf anderen UNIX-Plattformen? In der Regel ist dort nichts Vergleichbares installiert. Und wie betrachtet man eine Manpage, die nicht in MANPATH enthalten ist? Für all diese Fragen gibt es natürlich Lösungen, aber in vielen Fällen sind Unix-Benutzer bei solchen Problemen überfordert. Unter Unix fällt einem als Filebetrachter sofort less [1], die bessere Alternative zu more, ein. Und da less in Gestalt der Umgebungsvariablen LESSOPEN durch externe Filter erweitert werden kann, ist dieses Programm als extrem flexibler Browser konfigurierbar. Einige LinuxDistributionen legen bereits ein Skript lesspipe.sh bei, das zur Leistungssteigerung von less beiträgt.

In diesem Artikel soll ein Inputfilter für less vorgestellt werden, der eine Vielzahl gängiger Fileformate beherrscht und relativ leicht erweiterbar ist. Als Skriptsprache wurde eine Korn-shell kompatible Sprache (ksh, bash, zsh) gewählt, da das Vorhandensein der Shell immer gewährleistet ist und in den meisten Fällen vergleichsweise wenige Systemressourcen in Anspruch genommen werden. Ansonsten wäre die Realisierung des gleichen Konzeptes etwa in Perl an vielen Stellen einfacher.

lesspipe in der
Praxis

Das Betrachten einer man-Page in einem tar-Archiv, welches seinerseits in einem RPM-Paket steckt, könnte in folgenden Schritten erfolgen:

$ less file-3.27-43.i386.rpm
[...]
SuSE series: a
-rw-rr  1 root  root    12953 Feb  3 11:45 file-3.27.dif
-rw-rr  1 root  root    123541 Jul  6  1999 file-3.27.tar.gz
-rw-rr  1 root  root    3398 Mar 25 07:31 file.spec

$ less file-3.27-43.i386.spm:file-3.27.tar.gz
[...]
-rw-rw-r christos/christos  8740 1999-02-14 18:16 file-3.27/file.c
-rw-rw-r christos/christos  4886 1999-02-14 18:16 file-3.27/file.h
-rw-rw-r christos/christos 13428 1999-02-14 18:16 file-3.27/file.man
[...]

$ less file-3.27-43.i386.spm:file-3.27.tar.gz:file-3.27/file.man

Der letzte Befehl liefert schliesslich das gewünschte Resultat, die Anzeige von file.man als Manpage. Will man stattdessen den nroff-Quelltext sehen, hängt man einfach einen Doppelpunkt an.

$ less file-3.27-43.i386.spm:file-3.27.tar.gz:file-3.27/file.man:

Auch das Extrahieren einzelner Files aus dem Archiv ist möglich, etwa mit

$ less file-3.27-43.i386.spm:file-3.27.tar.gz:file-3.27/file.c > file.c

Wollte man stattdessen file.man in ein File umlenken, macht es sicher mehr Sinn, die nroff-Quelle als den formatierten Output abzuspeichern. Auch in diesem Fall sollte also ein Doppelpunkt angehängt werden.

Wenn man mit dem tar-File file-3.27.tar.gz gleichermaßen verfährt, erhält man ein unkomprimiertes tar-File, da eine eventuell mögliche Dekompression immer erfolgt. Die Dekomprimierung ist im Normalfall auch wünschenswert. Wenn man aber zum Beispiel die im RPM-Paket enthaltene File file-3.27.tar.gz extrahieren will, stört die Dekompression. Durch Anfügen eines weiteren Doppelpunktes bleibt auch die komprimierte Datei intakt, indem man zum Beispiel schreibt:

less file-3.27-43.i386.spm:file-3.27.tar.gz:: > file-3.27.tar.gz

Das (etwas vereinfachte) Skript lesspipe.sh ist in Listing 1 abgedruckt, eine aktuelle und komplette Version, die bis zur Rekursionstiefe sechs geht und weitere File-Typen berücksichtigt, ist unter [3] zu finden.

In einigen wenigen Fällen erkennt der file-Befehl den falschen Typ (insbesondere beim nroff-Format), dann kann man eine ungefilterte Ansicht durch nachgestellten Doppelpunkt bei der Fileangabe erzwingen. Die Ausgabe der Datei erfolgt in diesem Fall mit dem Befehl cat.

Automagisch

Dem Skript lesspipe.sh liegen zwei Ideen zugrunde. Die Erkennung des Fileformats erfolgt nicht über die Fileendung, diese Methode aus der DOS-Welt ist fehleranfällig und hat einen hohen Pflegeaufwand. Für die Fileformaterkennung unter Unix ist der Befehl file gut geeignet, aktualisierte Formatbeschreibungen sind zudem jederzeit in der aktuellen file-Version zu finden [2]. Die zweite Idee besteht darin, lesspipe eine Hierarchie von Filenamen übergeben zu können, um auch einzelne Files aus Archiven zu betrachten. Da lesspipe.sh aber nur ein Argument übergeben werden kann, wird die hierarchische Liste von Dateinamen durch ein spezielles Trennzeichen verbunden. Als Zeichen wurde der Doppelpunkt gewählt, welcher selten genug in Filenamen vorkommt. Auf jeder Stufe des File-Extrahierens wird der Filetyp bestimmt. Damit wird garantiert, dass auch auf dem untersten Niveau noch eine zum Filetyp passende Anzeige erfolgt.

Listing
1

  1  #!/bin/bash
  2  #==================================================================
  3  # lesspipe.sh, a preprocessor for less
  4  # Author:  Wolfgang Friebel DESY Zeuthen (Wolfgang.Friebel@desy.de)
  5  #==================================================================
  6  tarcmd=gtar;
  7  filecmd='file -L';         # file (recommended file-3.27 or better)
  8  sep=:                              # file name separator
  9  altsep==                   # alternate separator character
 10  if [[ -f "$1" && "$1" = *$sep* || "$1" = *$altsep ]]; then
 11    sep=$altsep
 12  fi
 13  tmp=/tmp/.lesspipe.$$              # temp file name
 14  trap 'rm -f $tmp $tmp.fin $tmp. $tmp.. $tmp.1' 0
 15  trap PIPE
 16  
 17  show () {
 18    file1=${1%%$sep*}
 19    rest1=${1#$file1}
 20    rest11=${rest1#$sep}
 21    file2=${rest11%%$sep*}
 22    rest2=${rest11#$file2}
 23    rest11=$rest1
 24    if [[ $# = 1 ]]; then
 25      type=`$filecmd "$file1" | cut -d : -f 2-`
 26      get_cmd "$type" "$file1" $rest1
 27      if [[ "$cmd" != "" ]]; then
 28        show "-$rest1" "$cmd"
 29      else
 30        isfinal "$type" "$file1" $rest11
 31      fi
 32    elif [[ $# = 2 ]]; then
 33      type=`$2 | $filecmd - | cut -d : -f 2-`
 34      get_cmd "$type" "$file1" $rest1
 35      if [[ "$cmd" != "" ]]; then
 36        show "-$rest1" "$2" "$cmd"
 37      else
 38        $2 | isfinal "$type" - $rest11
 39      fi
 40    elif [[ $# = 3 ]]; then
 41      type=`$2 | $3 | $filecmd - | cut -d : -f 2-`
 42      get_cmd "$type" "$file1" $rest1
 43      if [[ "$cmd" != "" ]]; then
 44        show "-$rest1" "$2" "$3" "$cmd"
 45      else
 46        $2 | $3 | isfinal "$type" - $rest11
 47      fi
 48    elif [[ $# = 4 ]]; then
 49      type=`$2 | $3 | $4 | $filecmd - | cut -d : -f 2-`
 50      get_cmd "$type" "$file1" $rest1
 51      if [[ "$cmd" != "" ]]; then
 52        echo "$0: Too many levels of encapsulation"
 53      else
 54        $2 | $3 | $4 | isfinal "$type" - $rest11
 55      fi
 56    fi
 57  }
 58  
 59  get_cmd () {
 60    cmd=
 61    if [[ "$1" = *bzip* || "$1" = *compress* ]]; then
 62      if [[ "$3" = $sep$sep ]]; then
 63        return
 64      elif [[ "$1" = *bzip* ]]; then
 65        cmd="bzip2 -c -d $2"
 66      else
 67        cmd="gzip -c -d $2"
 68      fi
 69      return
 70    fi
 71      
 72    rest1=$rest2
 73 if [[ "$file2" != "" ]]; then
 74      if [[ "$1" = *tar* ]]; then
 75        cmd="$tarcmd Oxf $2 $file2"
 76      elif [[ "$1" = *RPM* ]]; then
 77        cmd="isrpm $2 $file2"
 78      elif [[ "$1" = *Zip* ]]; then
 79        cmd="iszip $2 $file2"
 80      fi
 81    fi
 82  }
 83  
 84  iszip () {
 85    if [[ "$1" = - ]]; then
 86      rm -f $tmp
 87      cat > $tmp
 88      set $tmp "$2"
 89    fi
 90    unzip -avp "$1" "$2"
 91  }
 92  
 93  isrpm () {
 94    if [[ "$1" = - ]]; then
 95      rm -f $tmp
 96      cat > $tmp
 97      set $tmp "$2"
 98    fi
 99    echo $tmp.1 > $tmp.
100    rm -f $tmp.1
101    rpm2cpio $1|cpio -i quiet rename-batch-file $tmp. ${2##/}
102    cat $tmp.1
103  }
104  
105  isfinal() {
106    if [[ "$3" = $sep || "$3" = $sep$sep ]]; then
107      cat $2
108      return
109    elif [[ "$2" = - && ( "$1" = *RPM* || "$1" = *Zip* ) ]]; then
110      cat > $tmp.fin
111      set "$1" $tmp.fin
112    fi
113    if [[ "$1" = *No such* ]]; then
114      return
115    elif [[ "$1" = *directory* ]]; then
116    echo "==> This is a directory, showing the output of ls -lAL"
117      ls -lAL "$2"
118    elif [[ "$1" = *tar* ]]; then
119    echo "==> use tar_file${sep}contained_file to view a file in the archive"
120      $tarcmd tvf "$2"
121    elif [[ "$1" = *RPM* ]]; then
122    echo "==> use RPM_file${sep}contained_file to view a file in the RPM"
123      rpm -p "$2" -qiv
124      rpm2cpio $2|cpio -i -tv quiet
125    elif [[ "$1" = *roff* ]]; then
126    echo "==> append $sep to filename to view the nroff source"
127      groff -s -p -t -e -Tascii -mandoc ${2#-}
128    elif [[ "$1" = *executable* ]]; then
129    echo "==> append $sep to filename to view the binary file"
130      strings ${2#-}
131    elif [[ "$1" = *Zip* ]]; then
132    echo "==> use zip_file${sep}contained_file to view a file in the archive"
133      unzip -lv "$2"
134    elif [[ "$1" = *HTML* ]]; then
135    echo "==> append $sep to filename to view the HTML source"
136      LESSOPEN=
137      w3m -dump -X -T text/html "$2"
138    elif [[ "$2" = - ]]; then
139      cat
140    fi
141  }
142  
143  show "$a"

Erweiterter Horizont

Eine Erweiterung des Skripts um zusätzliche Fileformate ist relativ leicht möglich, im einfachsten Fall hat man für neue Formate in die Funktion isfinal nach dem Muster aus den Zeilen 125-127 Befehle einzufügen, die auf STDOUT schreiben. Handelt es sich um ein neues Kompressionsformat, wird man in der Funktion get_cmd neue Befehle analog zu den Zeilen 64-65 einfügen müssen.

Etwas aufwendiger wird es mit einem neuen Archivformat. Das Anzeigen des Archivinhaltes geschieht in der Funktion isfinal (siehe Zeile 118 bis 120), während die Extraktion von Files aus dem Archiv in get_cmd nach dem Muster in den Zeilen 74-75 festzulegen ist.

Damit wäre eigentlich die Aufgabe erledigt, beliebige Fileformate zu erkennen und Informationen aus diesen Files gefiltert anzuzeigen. Wie man sowohl an der Benutzung von lesspipe.sh als auch an der häufigen Benutzung von Pipes in der Funktion show sieht, müssen die Filter von STDIN lesen und auf STDOUT schreiben können. Da einige Programme das aber von Haus aus nicht können, muss manchmal ein Umweg über temporäre Files gemacht werden. Wo das erforderlich war, wurden neue Funktionen definiert. Sollte ein solcher Fall neu implementiert werden müssen, kann man sich an der Funktion iszip (Zeilen 84-91) oder isrpm (Zeilen 93-103) orientieren.

Die Funktion lesspipe.sh kann man zum Beispiel nach /usr/local/bin kopieren. Um die damit erweiterte Funktionalität von less nutzen zu können, setzt man die Umgebungsvariable LESSOPEN auf den Wert ” | /usr/local/bin/lesspipe.sh %s

Dieses Skript lesspipe.sh als Erweiterung von less läuft beim Deutschen Elektronensynchrotron seit mehr als 3 Jahren auf einer Vielzahl verschiedener Unix-Plattformen. ( tfr)

Infos

[1] http://home.flash.net/~marknu/less

[2] ftp://ftp.astron.com/pub/file

[3] http://www.ifh.de/~friebel/unix/lesspipe.html

Copyright © 2002 Linux New Media AG

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben