Artikel in Linux-Magazin 1/2001 Seiten 172-174 ============================================== (nur die englische Übersetzung english.txt wird von mir aktualisiert) 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. Doch 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 less als extrem flexibler Browser konfigurierbar. Einige Linux-Distributionen 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. Dem vorzustellenden 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, dem 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 Filenamen 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, daß auch auf dem untersten Niveau noch eine zum Filetyp passende Anzeige erfolgt. 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.spm gibt folgenden Output: ... SuSE series: a -rw-r--r-- 1 root root 12953 Feb 3 11:45 file-3.27.dif -rw-r--r-- 1 root root 123541 Jul 6 1999 file-3.27.tar.gz -rw-r--r-- 1 root root 3398 Mar 25 07:31 file.spec less file-3.27-43.i386.spm:file-3.27.tar.gz liefert u.a.: ... -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 liefert dann das gewünschte Resultat, die Anzeige von file.man als Manpage. Will man stattdessen die nroff Quelle sehen, hängt man 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 das im RPM 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 z.B. 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 6 geht und weitere Filetypen berücksichtigt, ist unter [3] zu finden. In einigen wenigen Fällen erkennt der File-Befehl den falschen Typ (insbesondere nroff-Format), dann kann man eine ungefilterte Ansicht durch nachgestellten Doppelpunkt bei der Fileangabe erzwingen. Dann erfolgt die Ausgabe des Files mit dem Befehl cat. 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 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 z.B. Zeilen 118-120), während die Extraktion von Files aus dem Archiv in get_cmd nach dem Muster in 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, muß 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 z.B. nach /usr/local/bin kopieren. Um die damit erweiterte Funktionalität von less nutzen zu können, setzt man in sh-ähnlichen Shells (sh, ksh, zsh, bash) LESSOPEN ="|/usr/local/bin/lesspipe.sh %s"; export LESSOPEN In der csh und tcsh schreibt man stattdessen setenv LESSOPEN "|/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. [1] http://www.greenwoodsoftware.com/less/ [2] ftp://ftp.astron.com/pub/file [3] http://www.ifh.de/~friebel/unix/lesspipe.html Listing 1 ========= 1 #!/bin/bash 2 #================================================================== 3 # lesspipe.sh, a preprocessor for less 4 # Author: Wolfgang Friebel, DESY 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"