Script-Programmierung

1.Beispiel für ein komplexeres bash-Script

Es stand die Aufgabe, meinen Laptop etwas alltagtauglicher zu machen. Es handelt sich um einen ASUS-eeepc, auf dem ich ein SLAX-6.1.2 installiert habe und auf dem sich meine Präsentationen und Demonstrationsprogramme befinden. Sehr oft muss dieser mit unterschiedlichen Beamern zusammenarbeiten und die können mit der Bildschirmauflösung des eeepc (800x480) meist wenig anfangen, sodass oftmals nichts zu sehen wäre. Es bestand also der Wunsch, den X-Server während des Betriebs auf möglichst einfache Weise und ohne root-Zugriff auf eine verfügbare Beamer-Auflösung umzustellen. Auch ein langes suchen einer passenden Auflösung mit <strg><alt><+> wollte ich mir ersparen, da einige Beamermodelle da ganz allergisch reagieren. Manche schalten dann ganz auf stur und man sieht an der Wand nur noch den Hinweis "Unbekannte Auflösung".
Meine Idee war schon lange, die xrand-Schnittstelle zu nutzen, um die Auflösungen des Beamers oder externen Monitors direkt auszulesen und dann eine Tabelle der verfügbaren Modi zu generieren, aus der ich dann eine geeignete Auflösung auswählen kann.

Was soll das Script können?Bildschirmfoto videoswitch

  1. Die Auflösungen des X-Servers soll zu jedem beliebigen Zeitpunkt an einen Beamer/externen Monitor angepasst werden können. Die Anpassung soll ohne Neustart des X-Servers und ohne root-Rechte erfolgen.

  2. Die vom Beamer angebotenen Auflösungen sollen automatisch ausgelesen werden und übersichtlich in einer Tabelle dargestellt werden.

  3. Durch einfache Auswahl aus einem Menü soll es möglich sein, die gewünschte Auflösung auszuwählen und zu aktivieren. Zur Menüdarstellung soll zwischen Xdialog oder als Alternative dialog gewählt werden. (siehe Bild rechts, die Darstellung mit Xdialog)

  4. Ein Wechsel auf einen anderen Beamer soll während des Betriebs möglich sein.

  5. Die Abschaltung der externen VGA-Schnittstelle und die Wiederherstellung der ursprünglichen Bildschirmauflösung soll möglich sein.

Das Script benutzt im Wesentlichen die beiden Kommandos xrandr und dialog/Xdialog. Mit xrandr werden die entsprechenden Informationen zu den verfügbaren Video-Schnittstellen aus dem X-Server gelesen und entsprechende Steuerbefehle zur Modifizierung der Auflösung an diesen gesendet. Mit dialog oder Xdialog (ist schöner) wird die komplette Menüsteuerung realisiert. Der Rest ist bash-Script. Hier nun der Quellcode der Scriptes videoswitch:

Nr.Quelltext des ScriptsErläuterung
1#!/bin/bashShebang - zum Aufruf der bash
2SCREEN=();leeres Array SCREEN deklarieren
3SCREENLEN=0;Variable SCREENLEN deklarieren
4  
5if ! XRANDR="$( which xrandr 2>/dev/null )"; thenTest, ob xrandr installiert ist
6 echo "Das Tool xrandr ist nicht installiert!" 
7 exit 1 
8fi 
9if ! TDIALOG="$( which Xdialog 2> /dev/null )"; thenTest, ob Xdialog installiert ist
10 if ! TDIALOG="$( which dialog 2> /dev/null )"; thenTest, ob dialog installiert ist
11  echo "Kein dialog gefunden!" 
12  exit 1 
13 fi 
14fi 
15  
16function scan(){Definition der Funktion scan
17 local DUMMY DEVICE SCREENNR RETURN LINE;locale Variablen der Funktion declarieren
18 RETURN=0Variablen initialisieren
19 SCREEN=() 
20 SCREENLEN=0 
21 while read LINE die Ausgabe von xrandr zeilenweise einlesen
22 do 
23  case "$LINE" inTest des Zeileninhalts
24   Can\'t\ open\ display*)xrandr findet kein X-Window
25    RETURN=1 
26   ;; 
27   Screen\ [0-9]:\ *)xrandr hat einen Screen im X-Window gefunden
28    DUMMY="${LINE#Screen\ }" 
29    SCREENNR="${DUMMY%%:*}"die Screennummer wird abgespeichert
30   ;; 
31   ?*\ connected\ *)xrandr hat ein nutzbares Video-Device gefunden
32    DEVICE="${LINE%%\ connected*}"der Devicename wird abgespeichert
33    # Das DEVICE ist on" 
34    SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_off" 
35    ((SCREENLEN++)) 
36    SCREEN[${SCREENLEN}]=" --output ${DEVICE} --off --screen ${SCREENNR}"die xrandr-Option zum Abschalten des Devices wird im Array SCREEN gespeichert
37   ((SCREENLEN++)) 
38   ;; 
39   ?*\ disconnected\ *) 
40    DEVICE="${LINE%%\ disconnected*}"xrand hat ein nicht nutzbares Video-Device gefunden
41    # Das DEVICE ist off" 
42   ;; 
43   [0-9][0-9][0-9]*x[0-9][0-9][0-9]*[\ ]**[0-9]*\.[0-9]\**)das angeschlossene Gerät meldet eine Auflösung mit der es momentan benutzt wird
44    MODE="${LINE%%\ *}" 
45    SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}_aktiv" 
46    ((SCREENLEN++)) 
47    SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}"die xrandr-Option zum Einschalten dieser Auflösung wird im Array SCREEN abgespeichert
48    ((SCREENLEN++)) 
49    # ModeLine in das Array SCREEN eingetragen 
50   ;; 
51   [0-9][0-9][0-9]*x[0-9][0-9][0-9]*)das angeschlossene Gerät meldet eine unterstützte Auflösung
52    MODE="${LINE%%\ *}" 
53    SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}" 
54    ((SCREENLEN++)) 
55    SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}"die xrandr-Option zum Einschalten dieser Auflösung wird im Array SCREEN abgespeichert
56    ((SCREENLEN++)) 
57    # ModeLine in das Array SCREEN eingetragen 
58   ;; 
59   *) 
60    # unbekannte xrandr-Zeileunbekannte xrandr-Meldung
61    echo "Zeile $LINE unbekannt!"Ausgabe einer Fehlermeldung im Terminal
62   ;; 
63  esac 
64 done < <( ${XRANDR} 2>&1 )Übergabe der Meldungen von xrandr an die while-Schleife
65 return $RETURNÜbergabe des return-Status
66} 
67 Hauptprogramm
68if scan ; then 
69 EXIT=0 
70 while [ ${EXIT} -eq 0 ] 
71 do 
72  DSTRING="" 
73  X=0 
74  COUNT=0 
75  for X in $( seq 0 2 $((SCREENLEN-1)) ) 
76  do 
77   DSTRING="${DSTRING} ${COUNT} ${SCREEN[$X]}" 
78   ((COUNT++)) 
79  done 
80  NEWSCAN="${COUNT}" 
81  DSTRING="${DSTRING} ${NEWSCAN} Neuscan " 
82  if RESULT="$( ${TDIALOG} --title 'Videoswitch V1.1' \
                        --cancel-label 'Beenden' \
                        --menu 'F.Häßler (C)2011' \
                         20 70 15 ${DSTRING} 3>&1 1>&2 2>&3 3>&- )"; then
 
83   if [ "${RESULT}" = "${NEWSCAN}" ]; then 
84    scan 
85    EXIT="$?" 
86   else 
87    ((RESULT=2*RESULT+1)) 
88    XRANDROPT="${SCREEN[$RESULT]}" 
89    ${XRANDR} ${XRANDROPT} 
90    scan 
91   fi 
92  else 
93   EXIT=1 
94  fi 
95 done 
96else 
97 echo "Kein X-Window gefunden!" 
98fi 
Download videoswitch

Schauen wir uns die Einzelheiten des Scripts nun genauer an.

  1. Deklarationsteil

    Zeile 1 bis 3
    Nr.Quelltext des Scripts
    1#!/bin/bash
    2SCREEN=();
    3SCREENLEN=0;
     

    In den Zeilen 1 bis 3 beginnt das Ganze recht unspektakulär. In Zeile 1 ist der sogenannte Shebang-Operator angegeben. Dieser legt fest, dass das Script mit der bash als Kommandointerpreter ausgeführt werden soll. Das ist auch wichtig, denn eine Reihe von Expansionen in diesem Script werden z.B. von der sh nicht unterstützt.
    In der zweiten Zeile wird ein leeres Array mit dem Namen SCREEN deklariert. Das geschieht an dieser Stelle, weil das Hauptprogramm seine Informationen zur Menüdarstellung aus diesem Array ausliest, das durch die Funktion scan mit Daten gefüllt wurde. Damit es sich garantiert um eine globale Variable handelt, sollte der erste Aufruf außerhalb der Funktion liegen. Ähnliches gilt für die Variable SCREENLEN, die in der dritten Zeile deklariert wird. Sie soll ebenfalls global gelten und enthält die Anzahl der Einträge im Array SCREEN.

    4 

  2. Toolsuche

    Zeile 5 bis 14
    In diesem Abschnitt wird getestet, ob die notwendigen Kommandos xrandr und Xdialog oder dialog auf dem System installiert sind. Dazu wird das Kommando which verwendet. Dem Kommando which wird der Name des zu suchenden Tools übergeben, nach dem dann in den Suchpfaden der Shell gesucht wird. Wenn das Kommando gefunden wird, wird es mit seinem kompletten Pfad auf stdout ausgegeben und der exit-Status 0 (true) erzeugt. Wird das Kommando nicht gefunden, so erzeugt which eine Fehlermeldung auf stderr und den exit-Status 1 (false). Schauen wir uns das im Terminal mal an und geben den exit-Status mit der Parametersubstitution $? aus.
    bash-3.1$ which xrandr
    /usr/bin/xrandr
    bash-3.1$ echo $?
    0
    bash-3.1$ which kasperletheater
    which: no kasperletheater in (/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/share/texmf/bin:.)
    bash-3.1$ echo $?
    1
    bash-3.1$
    Das Kommando xrandr wurde unter /usr/bin/xrandr gefunden, der exit-Status von which ist 0. Das Kommando kasperletheater wurde nicht gefunden, eine Fehlermeldung wird ausgegeben und der exit-Status von which ist 1. Im nächsten Schritt fangen wir nun die Ausgabe des Pfades zu Kommando xrandr in der Variablen XRANDR auf, sodass in der Variablen der genaue Ort des Kommandos abgespeichert ist, wenn es installiert ist. Das erreicht man mit einer Kommandosubstitution der Form $(.....), deren Expansion man der Variablen XRANDR zuweist. Das sieht dann so aus:
    XRANDR="$( which xrandr )"
    Die Ausgabe des Kommandos which auf stdout wird so abgefangen, landet nicht auf dem Bildschirm, sondern wird in der Variable abgelegt. Das können wir leicht mit echo $XRANDR testen.
    bash-3.1$ XRANDR="$( which xrandr )"
    bash-3.1$ echo $XRANDR
    /usr/bin/xrandr
    bash-3.1$ XRANDR="$( which kasperletheater )"
    which: no kasperletheater in (/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/share/texmf/bin:.)
    bash-3.1$ echo $XRANDR
    
    bash-3.1$
    Man kann an der echo-Ausgabe sehr schön sehen, das nach der Suche von xrandr der Pfad samt Kommandoname in der Variable XRANDR abgespeichert ist. Bei der Suche nach kasperletheater ist die Variable XRANDR leer und which hat seine Fehlermeldung ausgegeben. Diese wird nicht in der Variablen XRANDR gespeichert, da sie nicht auf stdout sondern stderr ausgegeben wird. Da solche Fehlermeldungen unsere Scriptausgabe auf dem Terminal nur unnötig stören würden, leiten wir sie mit 2>/dev/null (2, weil stderr der zweite Datenkanal ist) nach /dev/null, dem Mülleimer um. Das sieht dann im Terminal mit der Ausgabe des exit-Status und des Variableninhalts so aus:
    bash-3.1$ XRANDR="$( which xrandr 2>/dev/null )"
    bash-3.1$ echo $?
    0
    bash-3.1$ echo $XRANDR
    /usr/bin/xrandr
    bash-3.1$ XRANDR="$( which kasperletheater 2>/dev/null )"
    bash-3.1$ echo $?
    1
    bash-3.1$ echo $XRANDR
    
    bash-3.1$

    Da es nun keinen Sinn macht das Script weiter auszuführen, wenn xrandr nicht installiert ist, wird die Suche nach dem Tool nun mit einer if..then..else..fi-Verzweigung verknüpft. Eine solche Anweisung hat die Form
    if Kommando1 ; then
      Kommando2
    else
      Kommando3
    fi

    Das Kommando1 wird ausgeführt und wenn der exit-Status des Kommandos1 gleich 0 (true) ist, wird das Kommando2 ausgeführt und danach die Programmausführung nach fi fortgesetzt. Ist der exit-Status des Kommandos1 gleich 1 (false) wird das Kommando3 ausgeführt und dann nach dem fi weiter gemacht. Es handelt sich also um eine WENN-DANN-SONST-Verzweigung, die durch den exit-Status eines Kommandos gesteuert wird. Mit dem Kommando ! (boolsche Negation) kann man eine boolsche Negation des exit-Status bewirken, sodass eine Umkehrung des Verhaltens erzeugt wird. Allgemein sieht das dann so aus:
    if ! Kommando1 ; then
      Kommando2
    else
      Kommando3
    fi

    Das Kommando1 wird ausgeführt und der exit-Status negiert. Wenn der exit-Status des Kommandos1 also gleich 0 (true) ist, wird das Kommando3 ausgeführt und wenn der exit-Status des Kommandos1 gleich 1 (false) ist, wird das Kommando2 ausgeführt. Wenn also das Kommando xrandr existiert, gibt ja der Aufruf von
    XRANDR="$( which xrandr 2>/dev/null )"
    den exit-Status 0 zurück, ansonsten 1. Für den Code in den Zeilen 5 bis 8 bdeutet das also:
    Nr.Quelltext des Scripts
    5if ! XRANDR="$( which xrandr 2>/dev/null )"; then
    6 echo "Das Tool xrandr ist nicht installiert!"
    7 exit 1
    8fi
     

    Es wird mit which getestet, ob das Kommando xrandr existiert, der Pfad zu dem Kommando in der Variable XRANDR abgelegt und danach der exit-Status von which negiert. Wenn xrandr also nicht gefunden wurde, wird der Code nach then ausgeführt. Wenn xrandr nicht existiert, wird eine Fehlermeldung ausgegeben und danach das Script mit dem exit-Status 1 sofort beendet. Wenn xrandr installiert ist, wird die Programmausführung nach dem fi fortgesetzt.

    9if ! TDIALOG="$( which Xdialog 2> /dev/null )"; then
    10 if ! TDIALOG="$( which dialog 2> /dev/null )"; then
    11  echo "Kein dialog gefunden!"
    12  exit 1
    13 fi
    14fi
     

    Der Code in den Zeilen 9 bis 14 arbeitet ähnlich. Zuerst wird getestet, ob das Kommando Xdialog existiert. Wenn ja, dann wird der Pfad zum Kommando Xdialog in der Variable TDIALOG abgespeichert und das Programm bei Zeile 15 fortgesetzt. Existiert Xdialog nicht, dann wird getestet, ob wenigstens dialog als Alternative installiert ist. Wenn ja, dann wird der Pfad zu dialog in der Variablen TDIALOG abgespeichert, ansonsten wird das Script mit einer Fehlermeldung und dem exit-Status 1 (false) sofort beendet.

    15 
     

    Wenn das Script also bei Zeile 15 angekommen ist, dann sind folgende Bedingungen erfüllt:

    • Die wichtige Variable SCREENLEN und das Array SCREEN sind als global deklariert und mit Startwerten vorbelegt.
    • Das Kommando xrandr ist installiert, der Pfad zum Kommando steht in der Variablen XRANDR.
    • Es wurde ein Kommando zur Menügestaltung (dialog oder Xdialog) gefunden. Der Pfad zu dem Kommando steht in TDIALOG.

  3. Funktion scan

    Zeile 16 bis 66
    Die Aufgabe der Funktion scan besteht darin, das Kommando xrandr aufzurufen und seine Ausgabe nach gültigen Einträgen zu scannen, die Angaben über die unterstützten Auflösungen der angeschlossenen Beamer und Monitore machen. Eine Ausgabe des Kommandos xrandr im Terminal sieht z.B. so aus:
    bash-3.1$ xrandr
    Screen 0: minimum 320 x 200, current 800 x 480, maximum 800 x 800
    VGA connected (normal left inverted right x axis y axis)
       800x600        72.2     75.0     60.3     56.2  
       640x480        75.0     72.8     66.7     60.0  
       720x400        70.1  
       640x350        70.1  
    LVDS connected 800x480+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
       800x480        60.0*+
       640x480        85.0     72.8     75.0     59.9  
       720x400        85.0  
       640x400        85.1  
       640x350        85.1  
    TV disconnected (normal left inverted right x axis y axis)

    Es ist zu erkennen, dass die Grafikkarte über einen externen VGA-Anschluss verfügt, an dem ein Monitor/Beamer angeschlossen ist, der die Auflösungen 800x600, 640x480, 720x40 und 640x350 unterstützt. An der LVDS-Schnittstelle (Digitalschnittstelle für LCD-Monitore) ist das interne LCD-Display des eeepc angeschlossen, das die Auflösungen 800x480, 640x480, 720x400, 640x400 und 640x350 unterstützt. Weiterhin verfügt die Grafikkarte über einen TV-Anschluss, der nicht nutzbar ist. In der Konfiguration des X-Servers ist nur Screen 0 definiert, in dem momentan die beiden Schnittstellen VGA/extern und LVDS/intern nutzbar sind. An dem * in der Ausgabezeile "800x480   60.0*+" kann man erkennen, das dieser Modus momentan aktiv ist.
    Die Funktion scan liest diese Ausgabe nun zweilenweise ein und erzeugt aus allen Angaben zu verfügbaren Auflösungen der Geräte jeweils zwei Einträge im Array SCREEN. Unter den geraden Indexnummern (0, 2, 4, ...) des Arrays wird der Wortlaut des Menüeintrages für dialog hinterlegt, unter den ungeraden Indexnummern (1, 3, 5, ...) die entsprechende xrandr-Option um diesen Modus im X-Server zu aktivieren.
    Der Quellcode der Funktion ist schon recht komplex und ich werde nun versuchen, die einzelnen Anweisungen und deren Funktionsweise zu erläutern. Shellfunktionen sind im Prinzip Unterprogramme, denen beim Aufruf genau wie Kommandos eine Reihe von Parametern übergeben werden kann und die mit return einen exit-Status erzeugen können.

    Nr.Quelltext des Scripts
    16function scan(){
     

    In Zeile 16 wird die Funktion scan deklariert. Durch das Konstrukt function scan(){ ...... } wird die Funktion definiert. In den geschweiften Klammern werden die Kommandos abgelegt, die beim Aufruf der Funktion ausgeführt werden sollen. Die Definition der Funktion scan endet in der Zeile 66 mit der schließenden geschweiften Klammer. In den Zeilen 17 bis 20 befindet sich nun ein Deklarationsteil in der Funktion.

    17 local DUMMY DEVICE SCREENNR RETURN LINE;
    18 RETURN=0
    19 SCREEN=()
    20 SCREENLEN=0
     

    In der Zeile 17 werden die Variablen DUMMY, DEVICE, SCREENNR, RETURN und LINE als locale Variablen der Funktion definiert. Diese Variablen sind nur innerhalb der Funktion gültig und existieren außerhalb der Funktion nicht. In Zeile 18 wird die locale Variable RETURN mit dem Startwert 0 belegt. Der Wert dieser Variable wird den exit-Status der Funktion enthalten und so anzeigen, ob die Funktion ordnungsgemä&szli; ausgeführt wurde (0 true) oder nicht (1 false). In den Zeilen 19 und 20 werden die globalen Variablen SCREEN und SCREENLEN wieder mit den Startwerten (leeres Array und 0) belegt, um eventuell vorhandene Einträge eines vorhergehenden Aufrufs der Funktion scan zu löschen.

     
     

    Achtung!, jetzt wird es etwas knifflig. Die Ausgaben des Kommandos xrandr müssen nun Zeile für Zeile gelesen und interpretiert werden, um dann die entsprechenden Einträge im Array SCREEN zu erzeugen. Das zeilenweise Einlesen erledigt eine while-Schleife zusammen mit dem Kommando read. Das Kommando read liest jeweils eine Zeile (bis zum Enter) von stdin und hinterlegt sie in einer Variable, in unserem Fall die Variable LINE. Wenn read Daten lesen konnte, gibt es den exit-Status 0 (true) zurück. Konnten keine Daten mehr gelesen werden (Timeout oder EOF-end of file), so wird der exit-Status 1 (false) zurückgegeben. Deshalb wird read einfach als Abbruchbedingung für die while-Schleife eingefügt, so dass die while-Schleife-Schleife so lange ausgeführt wird, wie read Zeilen von stdin lesen kann. Ist der Datenstrom an stdin beendet, so beendet sich auch die while-Schleife. Das sieht dann also so aus
    while read LINE
    do
       ...

       Schleifenkörper

       ...
    done

    Innerhalb der Schleife, also im Schleifenkörper enthält die Variable LINE jetzt bei jedem Durchlauf die Folgezeile der von read gelesenen Datenquelle. Nun muss man nur noch dafür sorgen, dass die Ausgaben von xrandr auch den stdin von read erreichen. Um den stdout eines Kommandos auf den stdin eines anderen Kommandos umzuleiten, gibt es im Prinzip zwei Möglichkeiten. Entweder man benutzt eine Pipe oder durch eine Datenstromumleitung. Mit einer Pipe sieht das dann so aus:
    xrandr ¦ while read LINE
    do
       ...

       Schleifenkörper

       ...
    done

    Die Ausgaben des Kommandos xrandr auf stdout werden an die Schleife umgeleitet und landen am stdin von read. Das funktioniert auch super, ABER die Schleife befindet sich in der Pipe und wird deshalb in einer Subshell ausgeführt. Das bedeutet, dass alle Variablenwerte, die in der Schleife geschrieben wurden mit dem Beenden der Schleife verloren gehen. Alle Einträge in SCREEN wären also verloren, wenn die Schleife beendet ist.
    Die zweite Möglichkeit der Lösung des Problems besteht in einer Datenstromumleitung. Da Datenstromumleitungen am Ende eines Kommandos festgelegt werden, sieht die ganze Sache dann so aus:
    while read LINE
    do
       ...

       Schleifenkörper

       ...
    done < xrandr

    Das funktioniert so leider aber nicht, weil die Shell bei der Zerlegung des Programmtextes als erstes ein Kommando und danach nur noch Optionen, Parameter und Umleitungsoperatoren erwartet und keine weiteren ausführbaren Kommandos, die gestartet werden sollen. Deshalb müssen wir für xrandr eine Subshell öffnen, in der das Kommando dann ausgeführt wird und deren Ausgaben umleiten und das sieht in der bash dann so aus:
    while read LINE
    do
       ...

       Schleifenkörper

       ...
    done < <(xrandr)

    Nun wird xrandr in einer Subshell ausgeführt und alle Änderungen der Arbeitsumgebung durch xrandr in der Subshell gehen verloren. Das ist aber uninteressant, weil uns nur die Ausgabe von xrandr interessiert und die wird an den stdin der while-Schleife umgeleitet und von read zeilenweise eingelesen. Da die while-Schleife nun in der parent-Shell läuft, bleiben alle Veränderungen der Arbeitsumgebung (Variablenänderungen) innerhalb des Schleifenkörpers im gesamten Script lesbar, denn while läuft nun nicht in einer Subshell sondern xrandr.

    21 while read LINE
    22 do
     

    Im Schleifenkörper der while-Schleife haben wir jetzt also die jeweils nächste Ausgabezeile des Kommandos xrandr in der Variable LINE zur Verfügung und können diese jetzt nacheinander nach interessanten Mustern scannen und die Einträge im Array SCREEN erzeugen.

    23  case "$LINE" in
     

    Das scannen der Zeileninhalte übernimmt die Mehrfachverzweigung case, die relativ komfortable Möglichkeiten des Stringvergleichs durch reguläre Ausdrücke bietet und Mehrfachverzweigungen im Gegensatz zur if-Verzweigung viel übersichtlicher darstellt. Die case-Verzweigung hat die folgende Struktur:
    case "$VAR" in
      Suchmuster2) Kommando2 ;;
      Suchmuster3) Kommando3 ;;
      ......
      SuchmusterN) KommandoN ;;
    esac

    Das Kommando case untersucht jetzt den Inhalt der Variablen VAR mit dem Suchmuster1 auf eine Übereinstimmung. Wenn das zutrifft, wird das Kommando1 (oder auch mehrere Kommandos) ausgeführt und die Programmabarbeitung nach esac fortgesetzt. Wurde keine Übereinstimmung festgestellt, so wird der Inhalt der Variable mit dem Suchmuster2 verglichen und bei Übereinstimmung das Kommando2 ausgeführt und danach die Programmabarbeitung nach esac fortgesetzt. Wenn wieder keine Übereinstimmung gefunden wurde, wird mit dem Vergleich mit Suchmuster3 fortgesetzt usw. usf. Die Suchmuster können recht leistungsfähig sein, da auch die Jokerzeichen *, ? und [..] zulässig sind. Die Jokerzeichen haben folgende Bedeutung:

    • * - steht für ein beliebiges Zeichen, das nicht oder mehrfach vorkommt
    • ? - steht für ein beliebiges Zeichen, das mindestens einmal vorkommt
    • [...] - steht für ein Zeichen, das aus einem bestimmten Bereich kommt, z.B.
      [0-9] - es handelt sich um eines der Ziffernzeichen 0, 1, 2, 3, 4, 5, 6, 7, 8 oder 9
      [a-d] - es handelt sich um einen der Kleinbuchstaben a, b, c oder d
      [aeYW] - es handelt sich um einen der Buchstaben a, e, Y oder W

    Die case-Verzweigung in der Funktion scan erstreckt sich über die Zeilen 23 bis 63.

    24   Can\'t\ open\ display*)
    25    RETURN=1
    26   ;;
     

    In diesem Abschnitt wird nach einem Text "Can't open display", gefolgt von beliebig vielen oder keinem weiteren Zeichen (Jokerzeichen*) in der Variablen LINE gesucht. xrandr gibt diese Meldung aus, wenn kein X-Window-System erreichbar ist. Wenn das zutrifft, wird der Wert der Variable RETURN die den exit-Status der Funktion enthält, auf 1 (false) gesetzt und das Script nach esac fortgesetzt.

    27   Screen\ [0-9]:\ *)
    28    DUMMY="${LINE#Screen\ }"
    29    SCREENNR="${DUMMY%%:*}"
    30   ;;
     

    Hier wird der Inhalt der Variable LINE mit dem Suchmuster "Screen\ [0-9]:\ *" verglichen. Gesucht wird also nach dem Wort Screen, gefolgt von einem Leerzeichen und einer Ziffer im Bereich von 0 bis 9, einem Doppelpunkt, einem weiteren Leerzeichen und beliebig vielen oder keinem weiteren Zeichen (Jokerzeichen*). Das entspricht der Ausgabe des definierten Screens im X-Server durch xrandr. Bei Übereinstimmung wird dieser String nun zerlegt, um die Nummer des Screens zu ermitteln. Dazu werden hier die Parametersubstitutionen der bash verwendet. Prinzipiell ginge das auch mit dem Kommando cut, der an dieser Stelle aber keinerlei Vorteile bringen würde, im Gegenteil. Da cut ein externes Kommando ist, müsste die bash erst eine eigene Subshell starten und das kostet Zeit, RAM und Performance. Die hier verwendete Substitution hat die Form ${parameter#word}.
    In Zeile 28 steht dazu: DUMMY="${LINE#Screen\ }"
    Dieser Code bewirkt, dass der Variable DUMMY der Inhalt der Variable LINE übergeben wird, aus dem der größtmögliche linksseitige Teil, der mit "Screen " übereinstimmt, entfernt wurde. In der Variable DUMMY befindet sich dann also nur die Nummer des Screens, gefolgt von einem Doppelpunkt, einem Leerzeichen und eventuell weiteren Zeichen. Der Teil "Screen " wurde durch die Expansion entfernt. In Zeile 29 findet nun eine weitere Parametersubstitutionen statt. Sie hat die Form ${parameter%%word} und der Code lautet: SCREENNR="${DUMMY%%:*}"
    Er bewirkt, dass der Variable SCREENNR der Inhalt der Variable DUMMY zugewiesen wird, aus dem der größtmögliche rechtsseitige Teil entfernt wurde, der mit einem Doppelpunkt beginnt und danach von weiteren beliebigen oder keinem Zeichen gefolgt wird. Dadurch befindet sich nach diesen Substitutionen nur noch die Nummer des Screens, der von xrandr ausgegeben wurde. Diese Nummer ist nun in der Variable SCREENNR gespeichert, solange bis die nächste Zeile mit dieser Struktur in der xrandr-Ausgabe gefunden wird.

    31   ?*\ connected\ *)
    32    DEVICE="${LINE%%\ connected*}"
    33    # Das DEVICE ist on"
    34    SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_off"
    35    ((SCREENLEN++))
    36    SCREEN[${SCREENLEN}]=" --output ${DEVICE} --off --screen ${SCREENNR}"
    37   ((SCREENLEN++))
    38   ;;
     

    Der Inhalt der Variable LINE wird nun dem Suchmuster "?*\ connected\ *" verglichen. Gesucht wird also nach Mindestens einem beliebigen Zeichen, gefolgt von beliebig vielen oder keinem weiteren Zeichen (das ist der Name der Videoschnittstelle), auf die ein Leerzeichen, das Wort connected, ein weiteres Leerzeichen und beliebig viele oder keine weiteren Zeichen folgen. Das entspricht der Ausgabe einer benutzten Schnittstelle der Grafikkarte durch xrandr. Das Wort vor " connected" ist der Name der Schnittstelle, das wieder durch Parametersubstitutionen der bash herausgetrennt wird. Die verwendete Substitution hat wieder die Form ${parameter%%word}. In der Zeile 32 steht dazu der Code: DEVICE="${LINE%%\ connected*}"
    Er bewirkt, dass der Variable DEVICE der Inhalt der Variable LINE zugewiesen wird, aus dem der größtmögliche rechtsseitige Teil entfernt wurde, der mit dem String " connected", gefolgt von beliebig vielen oder keinem Zeichen übereinstimmt. Dadurch befindet sich nach dieser Substitutionen in der Variable DEVICE der Name der kontaktierten Videoschnittstelle auf die sich die folgenden xrandr-Ausgaben nun beziehen. Dieser Name ist nun solange in der Variable DEVICE gespeichert, bis in der xrandr-Ausgabe eine weitere kontaktierte Videoschnittstelle aufgelistet wird.
    Die Zeile 33 enthält nur einen Kommentar, der durch das Symbol # eingeleitet wird.
    Da es sich bei dem gefundenen Device um eine Schnittstelle handelt die aktiv ist, weil ein Gerät angeschlossen wurde, wird jetzt ein Eintrag im Array Screen erzeugt, der später dazu dienen soll, diese Schnittstelle zu deaktivieren. Die Variable SCREENLEN zeigt auf die nächste freie Indexnummer des Arrays SCREEN und dort wird jetzt der dialog-Menüeintrag gespeichert, der dem User verdeutlichen soll, was dieser Menüpunkt bewirkt. In der Zeile 34 steht dazu der Code: SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_off"
    Dabei handelt es sich um eine Wertzuweisung in ein Array, wobei der Wert durch Parametersubstitutionen erzeugt wird. Auf der rechten Seite der Zuweisung steht SCREEN[${SCREENLEN}], dabei handelt es sich um ein Feld des Arrays SCREEN, der Index des Feldes wird durch Substitution von ${SCREENLEN} erzeugt, das bedeutet es wird der nächste freie Index im Array benutzt, in den geschrieben wird. Auf der rechten Seite wird mit "SCREEN:${SCREENNR}_DEVICE:${DEVICE}_off" der Werte bestimmt, der in dem Array-Feld abgelegt werden soll. Dabei handelt es sich wieder zum Teil um Parametersubstitutionen, alle einträge der Form ${parameter} werden dabei durch die bash durch den Wert der Variablen parameter substituiert. In dem Array-Feld wird also ein Text gespeichert, der sich aus folgenden Teilen zusammensetzt:

    1. Das Wort SCREEN, gefolgt von einem Doppelpunkt.
    2. Die Nummer des Screens, die vorher aus der xradr-Ausgabe herausgeschnitten wurde und in der Variable SCREENNR abgespeichert wurde.
    3. Ein Unterstrich, gefolg von dem Wort DEVICE und ein Doppelpunkt.
    4. Der Name der kontaktierten Videoschnittstelle, die ebenfalls vorher bereits aus der xrandr-Ausgabe separiert wurde.
    5. Ein weiterer Unterstrich und das Wort off.

    In der Zeile 35 wird nun der Inhalt der Variablen SCREENLEN nun durch eine arithmetische Substitution um 1 erhöht und zeigt damit auf das nächste freie Feld des Arrays SCREEN.
    In der Zeile 36 sehen wir nun eine weitere Wertzuweisung in das freie Feld des Arrays SCREEN. Dier wird jetzt jedoch kein Menütext abgelegt, sondern eine xrandr-Option, die xrandr veranlasst, die Schnittstelle deren Name in DEVICE steht im Screen, dessen Nummer in SCREENNR steht, abzuschalten. Danach wird der Wert der Variable SCREENLEN wieder um 1erhöht, damit sie wieder auf ein freies Feld des Arrays SCREEN verweist. Damit wurde ein vollständiger Eintrag im Array SCREEN erzeugt, der aus zwei Feldinhalten besteht. Im ersten Feld (gerade Indexnummer) ist der Wortlaut des Menüpunktes in dialog abgelegt. Im zweiten Feld (ungerade Indexnummer) befindet sich die dazu passende xrandr-Option, um die entsprechende Operation auch auszuführen, wenn der User den entsprechenden Menüpunkt auswählt.

    39   ?*\ disconnected\ *)
    40    DEVICE="${LINE%%\ disconnected*}"
    41    # Das DEVICE ist off"
    42   ;;
     

    Ähnlich wie in Zeile 31 wird nun nach Einträgen über nicht kontaktierte oder unbenutzbare Videoschnittstellen gesucht. Der Name des Devices wird zwar herausgetrennt und in DEVICE gespeichert, aber Menüeinträge werden nicht erzeugt. Vielleicht fällt mir dazu später noch etwas ein, was man sinnvolles tun könnte.

    43   [0-9][0-9][0-9]*x[0-9][0-9][0-9]*[\ ]**[0-9]*\.[0-9]\**)
    44    MODE="${LINE%%\ *}"
    45    SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}_aktiv"
    46    ((SCREENLEN++))
    47    SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}"
    48    ((SCREENLEN++))
    49    # ModeLine in das Array SCREEN eingetragen
    50   ;;
     

    Nun wird das Suchmuster schon etwas komplexer. Gesucht wird nach den Zeilen der xrandr-Ausgabe, in denen eine Auflösung des angeschlossenen Gerätes angegeben wird, die momentan auch aktiv ist. Dazu wird das Suchmuster "[0-9][0-9][0-9]*x[0-9][0-9][0-9]*[\ ]**[0-9]*\.[0-9]\**" verwendet, was im Einzelnen bedeutet, dass die Variable LINE einen Text enthalten muss, der aus drei Ziffern im Bereich von 0 bis 9, gefolgt von beliebig vielen oder keinem weiteren Zeichen (eventuell weitere Ziffern bei vierstelligen Auflösungen oder höher), dann muss das x folgen und es müssen mindestens drei weitere Ziffern folgen. Eventuell am Zeilenanfang auftretende Leerzeichen müssen nicht baechtet werden, da sie bereits durch read entfernt wurden. Das wäre also das Format, mit dem eine aufgelistete Auflösung erkannt wird. Jetzt muss noch ein Freiraum aus mindestens einem Leerzeichen folgen und dann eine Zahl mit Dezimalpunkt (vertikale Refreshrate des Gerätes), auf die ein Stern * folgt. Damit handelt es sich beim Inhalt der Variable LINE um eine momentan aktive Auflösung. Nun wird wieder die aus Zeile 32 bekannte Parametersubstitutionen der Form ${parameter%%word} angewendet, um den Inhalt der Variablen LINE ohne den rechtsseitigen Teil nach der Angabe der Auflösung zu expandieren. In der Variable MODE steht dann also die verfügbare und momentan aktive Auflösung in der Form "<horizontale Auflösung in Pixeln>x<vertikale Auflösung in Pixeln>"". Daraus wird nun, wie in den Zeilen 34 bis 37 gesehen, ein Menüeintrag und ein Eintrag mit der xrandr-option für diesen Modus in den nächsten freien Feldern des Arrays SCREEN hinterlegt.

    51   [0-9][0-9][0-9]*x[0-9][0-9][0-9]*)
    52    MODE="${LINE%%\ *}"
    53    SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}"
    54    ((SCREENLEN++))
    55    SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}"
    56    ((SCREENLEN++))
    57    # ModeLine in das Array SCREEN eingetragen
    58   ;;
     

    Diese Scriptzeilen unterscheiden sich von den Zeilen 43 bis 50 nur durch den leicht veränderten Suchstring. Hier werden nun alle Zeilen gefunden, die eine Auflösung liefern, egal ob aktiv oder nicht. Da die aktiven Zeilen aber schon mit dem vorhergehenden Suchmuster herausgefiltert werden, werden sie hier nicht nochmals bearbeitet, denn case bricht nach der Bearbeitung eines Suchtreffers die Arbeit und setzt die Programmabarbeitung nach esac fort.

    59   *)
    60    # unbekannte xrandr-Zeile
    61    echo "Zeile $LINE unbekannt!"
    62   ;;
     

    Nun erfolgt noch eine Suchanfrage für alle Zeilen, die bisher nicht erkannt wurden. Das Suchmuster "*" bedeutet im Prinzip, dass LINE irgendetwas enthalten kann und diese Suchanfrage erzeugt nur dann einen Treffer, wenn keines der anderen Suchmuster erfolg gebracht hat. In diesem Fall wird einfach eine kleine Fehlermeldung auf dem Terminal ausgegeben, das etwas nicht erkannt wurde.

    63  esac
    64 done < <( ${XRANDR} 2>&1 )
     

    In diese Zeile habe ich nun nicht, wie oben beschrieben, den Code done < <( xrandr ) eingetragen, sondern ein etwas komplizierteres Konstrukt. Das hat zwei Gründe:

    1. Rufe ich nicht einfach das Kommando xrandr an dieser Stelle auf, sondern habe eine Parametersubstitution der Form ${XRANDR} eingefügt. Dadurch wird an dieser Stelle die Zeichenfolge ${XRANDR} durch den Inhalt der Variable XRANDR ersetzt (substituiert). In dieser Variable steht ja bereits der komplette Pfad für den Aufruf des Kommandos xrandr, der durch which bereits in Zeile 5 in der Variable abgespeichert wurde. Dadurch muss die bash das Kommando xrandr nicht jedesmal neu in seinen Standardsuchpfaden suchen sondern kann es sofort starten. Das spart Zeit, RAM und Performance.

    2. Benutze ich eine Datenstromumleitung der Form 2>&1 für das Kommando xrandr. Dadurch werden alle Fehlermeldungen des Kommandos xrandr auf stderr (Datenkanal 2) auf stdout (Datenkanal 1) mit umgeleitet. Da ja der Datenkanal 1 stdout in die while-Schleife umgeleitet wird, landen so auch die Fehlermeldungen von xrandr (z.B. "Can't open display") dort und können von read gelesen und von case ausgewertet werden.

    65 return $RETURN
    66}
     

    Durch die while-Schleife durchläuft das Script diese case-Verzweigung mit dem Inhalt jeder Ausgabezeile von xrandr in der Variablen LINE. Nachdem alle Zeilen abgearbeitet wurden, beendet sich die Funktion scan und gibt den Inhalt der Variablen RETURN als exit-Status zurück.

    67 
  4. Hauptprogramm

    Zeile 68 bis 98

    Nr.Quelltext des Scripts
    68if scan ; then
     

    Zuerst einmal wird zum Programmstart die Funktion scan aufgerufen und die Ausgabe von xrandr gescannt. Sollte dabei ein Fehler auftreten, weil xrandr kein X-Window-System kontaktieren kann, wird der exit-Status 1 (false) zurückgegeben und if führt sofort den else-Zweig ab Zeile 96 aus, erzeugt eine Fehlermeldung und das Script ist beendet. Lief der scan ohne Probleme, geht es weiter mit der Erzeugungdes Menüs.

    69 EXIT=0
     

    Die Variable EXIT ist ein Flag das angibt, ob die while-Schleife, die das Menü erzeugt, verlassen werden soll oder nicht. Sie wird hier auf den Wert 0 gesetzt.

    70 while [ ${EXIT} -eq 0 ]
    71 do
     

    Hier testet while nun den Zustand der Variable EXIT. Dazu erwartet while ein Kommando und dessen exit-Status entscheidet, ob der Schleifenkörper abgearbeitet wird (bei 0-true) oder ob die Schleife verlassen wird (bei 1-false). Hier wird für den Test das Kommando [ ... ] verwendet. Ist der Wert der Variable EXIT gleich 0, wird der Schleifenkörper ausgeführt, wenn nicht, wird die die while-Schleife bei Zeile 95 verlassen. Die Variable EXIT wird im Schleifenkörper auf 1 gesetzt, wenn ein Aufruf der scan-Funktion den exit-Status 1 (false) erzeugt oder (X)dialog mit der Taste Beenden oder Exit geschlossen wird, also wenn der User das Programm offensichtlich beenden möchte.

    72  DSTRING=""
    73  X=0
    74  COUNT=0
     

    Hier werden nur ein paar Variablen initialisiert. In der Variablen DSTRING werden die Menüeinträge als dialog-Option zusammengesetzt. In Ihr wird ein String mit Null Zeichen gespeichert Mit COUNT wird gezählt, wie viele Menüeinträge es gibt und X ist eine einfache Zählvariable für die folgende for-Schleife. Beide werden auf Null gesetzt.

    75  for X in $( seq 0 2 $((SCREENLEN-1)) )
    76  do
     

    Die for-Schleife nutzt X als Zählvariable. Das Kommando seq 0 2 $((SCREENLEN-1)) erzeugt eine Werteliste von Null bis zum Wert der Variablen SCREENLEN minus 1 mit einer Schrittweite von 2, also 0, 2, 4, 6, ..... Da die Variable SCREENLEN nach dem Ausführen der scan-Funktion auf das erste freie Feld des Arrays SCREEN zeigt, erzeugt seq damit eine Liste aller geraden Indexnummern des Arrays SCREEN. In den Feldern mit geraden Indexnummern wurden ja durch die scan-Funktion bereits die Einträge für die Menüpunkte abgespeichert. Bei jedem Durchlauf der for-Schleife nimt X nun jeweils den nächsten Wert aus der Werteliste an, durchläft also die geraden Indexnummern des Arrays SCREEN.

    77   DSTRING="${DSTRING} ${COUNT} ${SCREEN[$X]}"
    78   ((COUNT++))
     

    Hier werden nun die Einträge für die dialog-Optionsliste vorgenommen. Jedem Menüpunkt ist eine Nummer voranzustellen, die dann auch als Kurzwahl verwendet werden kann und die von (X)dialog auf stdout ausgegeben wird, wenn der entsprechende Menüpunkt durch den User ausgewählt wurde. Danach folgt der Wortlaut des Menüeintrags. Das passiert hier durch den Aufruf DSTRING="${DSTRING} ${COUNT} ${SCREEN[$X]}". Die Variable DSTRING soll ja als Speicher für die Optionsliste dienen und wurde bei der Initialisierung in Zeile 72 mit einem Nullstring geladen. Hier wird der Variable DSTRING nun ein Wert zugewiesen, der sich aus dem ursprünglichen Inhalt von DSTRING, einem Leerzeichen, dem Inhalt der Variable COUNT, einem weiteren Leerzeichen und dem Inhalt des Feldes aus dem Array SCREEN, das die Indexnummer besitzt, die in der Variablen X gespeichert ist. Danach wird der Inhalt der Variable COUNT um 1 erhöht.

    79  done
     

    Diese Anweisungen werden so oft abgearbeitet, bis die ganze von seq erzeugte Liste abgearbeitet ist. Das bedeutet, dass in der Variable DSTRING alle Einträge des Arrays SCREEN mit gerader Indexnummer (von seq erzeugt) mit einer fortlaufenden Nummer (COUNT-Zählvariable) versehen, aufgelistet werden.

    80  NEWSCAN="${COUNT}"
    81  DSTRING="${DSTRING} ${NEWSCAN} Neuscan "
     

    Hier wird der (X)dialog-Optionsliste noch ein weiterer Menüpunkt "Neuscan" hinzugefügt. Wenn der User diesen Menüpunkt anwählt, soll die scan-Funktion nochmals aufgerufen werden, so dass erneut nach externen Geräten gesucht wird. In der Variable NEWSCAN wird einfach die Nummer dieses Menüeintrages abgespeichert.

    82  if RESULT="$( ${TDIALOG} --title 'Videoswitch V1.1' \
                            --cancel-label 'Beenden' \
                            --menu 'F.Häßler (C)2011' \
                             20 70 15 ${DSTRING} 3>&1 1>&2 2>&3 3>&- )"; then
     

    So, diese Zeile ist der Hammer und das aus mehreren Gründen:

    1. Die if..then..else..fi-Verzweigung sieht sehr kompliziert aus.

      Genau genommen ist es recht einfach. Als Bedingungskommando wird eine Kommandosubstitution der Form RESULT="$( kommando )" ausgeführt, nur ist eben das Kommando etwas komplexer. Dabei wird die Ausgabe des Kommandos auf stdout abgefangen und in der Variable RESULT abgespeichert. Dabei handelt es sich einfach um die Nummer des Menüpunkts, den der User ausgewählt hat. Den exit-Status des Kommandos kommando nutzt if für die Entscheidung zur Verzweigung. Da es sich dabei um den exit-Status des Kommandos (X)dialog handelt, teilt er lediglich mit, ob der User einen Menüpunkt ausgewählt hat (exit-Status 0-true) oder ob er das dialog-Fenster geschlossen hat (1 false).

    2. Es wird die Sonderbedeutung der Zeichenkombination \<enter> benutzt.

      Mit dieser Zeichenkombination wird die Eingabezeile auf die Folgezeile erweitert. Wird also eine Befehlszeile übermäßig lang, so kann man mit dem backslash die Sonderbedeutung des Enter-Zeichens quoten und die Eingabezeile einfach in der folgenden Scriptzeile fortsetzen. Dadurch wird der Quelltext wesentlich übersichtlicher.

    3. Die Zeile ist voller Substitutionen

      Lösen wir das mal auf. Die if..then..else..fi-Verzweigung hat die allgemeine Form
      if Wenn-Kommando; then
        Dann-Kommando
      else
        Sonst-Kommando
      fi

      Der exit-Status des Wenn-Kommandos (Bedingungskommando) legt fest, ob das Dann-Kommando (bei 0-true) oder das Sonst-Kommando (bei 1-false) ausgeführt wird. Das Wenn-Kommando besteht in dieser Zeile aus einer Variablenzuweisung der Form RESULT="$( kommando parameter umleitungen )".
      Bei dem Code $( kommando parameter umleitungen ) handelt es sich um eine Kommandosubstitution. Dabei wird der Code durch die Ausgabe des Kommandos kommando parameter umleitungen auf stdout (Datenkanal 1) substituiert wird. Das bedeutet, dass die Ausgabe auf Datenkanal 1 (stdout) in der Variablen RESULT gespeichert wird.
      Das Kommando selbst setzt sich aus den Kommandoaufruf kommando, den Parametern parameter zu diesem Kommando und einigen Datenumleitungen umleitungen zusammen.
      Im Quellcode der Zeile 82 sieht das Kommando also so aus:
      ${TDIALOG} --title ´Videoswitch V1.1´ \
                              --cancel-label ´Beenden´ \
                              --menu ´F.Häßler (C)2011´ \
                              20 70 15 ${DSTRING}
       3>&1 1>&2 2>&3 3>&-

      Der Kommandoname kommando wird durch eine Parametersubstitutionen eingefügt. Der Code ${TDIALOG} wird durch den Inhalt der Variable TDIALOG substituiert (ausgetauscht/ersetzt) und da durch which bereits in der Zeile 9 oder Zeile 10 der komplette Pfad zum Kommando Xdialog oder dialog eingetragen wurde, wird dieser Kommandoname an dieser Stelle eingetragen.
      Dann folgen die Parameter parameter zum Kommando (X)dialog mit folgenden Bedeutungen:

      • --title 'Videoswitch V1.1' - legt den Titel des dialog-Fensters fest
      • --cancel-label 'Beenden' - legt die Beschriftung des Cancel-Buttons fest
      • --menu 'F.Häßler (C)2011' - legt den Titel des dialog-Menüs fest
      • 20 70 15 - legt die Größe des dargestellten dialog-Fensters in der Form Höhe Breite Menühöhe fest
      • ${DSTRING} - legt die Menüeinträge in der Form Kurzzeichen1 Menüeintrag1 Kurzzeichen2 Menüeintrag2 ... KurzzeichenN MenüeintragN fest und wird durch Parametersubstitutionen der Variable DSTRING erzeugt.
    4. Am Ende der Zeile lauert eine verwirrende Liste von Datenstromumleitungen

      Ok, was bedeutet nun also die Datenstromumleitung 3>&1 1>&2 2>&3 3>&-. Die Syntax n>&m bedeutet ganz allgeimein, dass der Datenkanal n das momentane Ziel des Datenkanals m kopiert, also auf das selbe Ziel zeigt. Das Kommando (X)dialog gibt die Bildschirmmenüs auf stdout (Datenkanal 1) aus und den Wert des Ergebnisses der Menüauswahl durch den User auf stderr (Datenkanal 2). Das ist ungünstig, denn wir wollen das Ergebnis ja durch eine Kommandosubstitution der Form RESULT="$( kommando )" in der Variablen RESULT auffangen. Dabei wird aber der stdout in der Variabeln abgelegt, also die Bildschirmausgabe und nicht das Ergebnis. Deshalb müssen stdout und stderr ausgetauscht werden. Die momentanen Deskriptoren sehen so aus:
      UmleitungsoperatorenDatenkanal ZielErläuterungen
      Zustand der Umleitungen durch die Kommandosubstitutionstdout (Datenkanal 1)RESULTDer stdout des Kommandos ist in die Variable RESULT umgeleitet, stderr zeigt auf das Terminal und Datenkanal 3 ist noch nicht definiert.
      stderr (Datenkanal 2)tty-Terminal
      (Datenkanal 3)none
      Umleitungsoperator 3>&1stdout (Datenkanal 1)RESULTDer Datenkanal 3 kopiert das momentane Ziel des Datenkanals 1 (stdout) und zeigt damit in die Variable RESULT.
      stderr (Datenkanal 2)tty-Terminal
      (Datenkanal 3)RESULT
      Umleitungsoperator 1>&2stdout (Datenkanal 1)tty-TerminalDer Datenkanal 1 (stdout) kopiert das momentane Ziel des Datenkanals 2 (stderr) und zeigt damit auf das Terminal (Bildschirm).
      stderr (Datenkanal 2)tty-Terminal
      (Datenkanal 3)RESULT
      Umleitungsoperator 2>&3stdout (Datenkanal 1)tty-TerminalDer Datenkanal 2 (stderr) kopiert das momentane Ziel des Datenkanals 3 und zeigt damit in die Variable RESULT.
      stderr (Datenkanal 2)RESULT
      (Datenkanal 3)RESULT
      Umleitungsoperator 3>&-stdout (Datenkanal 1)tty-TerminalDer Datenkanal 3 wird geschlossen.
      stderr (Datenkanal 2)RESULT
      (Datenkanal 3)none

    Insgesamt bewirkt diese Zeile, dass das Kommando (X)dialog mit all seinen Optionen aufgerufen wird und wenn der User einen Menüpunkt auswählt, dessen Nummer in der Variable RESULT abgespeichert wird. Außerdem gibt der exit-Status 1 dieser Zuweisung an, ob der User das Kommando (X)dialog abgebrochen hat (Beenden- oder Exit-Taste gedrückt). In diesem Fall wird sofort zu Zeile 92 gesprungen. War der exit-Status jedoch 0, so bedeutet dies, dass ein Menüpunkt ausgewählt wurde und das Programm wird in Zeile 83 fortgesetzt.

    83   if [ "${RESULT}" = "${NEWSCAN}" ]; then
     

    Es findet einfach nur ein Test statt, ob die Nummer des ausgewählten Menüpunktes mit dem Inhalt der Variable NEWSCAN übereinstimmt, also ob der User den Menüpunkt "Neuscan" ausgewählt hat oder nicht.

    84    scan
    85    EXIT="$?"
     

    Wenn der Menüpunkt "Neuscan" ausgewählt wurde, wird die scan-Funktion erneut gestartet und mit der Parametersubstitutionen der Form $? der exit-Status der scan-Funktion in der Variablen EXIT gespeichert. Wenn also xrandr in der scan-Funktion einen Fehler festgestellt hat, wird EXIT auf 1 gesetzt und dadurch die while-Schleife in Zeile 70 beim nächsten Test der Variable EXIT die Schleife verlassen und das Script sich beenden. Wurde durch xrandr kein Fehler ausgelöst, wird in EXIT eine 0 gespeichert und die while-Schleife wird erneut durchlaufen, d.h. das Menü wird mit den neuen Ergebnissen der scan-Funktion erstellt.

    86   else
     

    Wenn ein anderer Menüpunkt als "Neuscan" ausgewählt wurde, dann geht es hier weiter.

    87    ((RESULT=2*RESULT+1))
    88    XRANDROPT="${SCREEN[$RESULT]}"
    89    ${XRANDR} ${XRANDROPT}
    90    scan
     

    In der Variable RESULT befindet sich die Nummer des vom User ausgewählten Menüeintrags. Jetzt muss nur die Indexnummer des dazu passenden Eintrags der xrandr-Optionen im Array SCRENN berechnet werden. Die Nummer in RESULT muss dazu einfach verdoppelt werden und um 1 erhöht werden. Das wird in Zeile 87 durch den arithmetischen Ausdruck ((RESULT=2*RESULT+1)) erreicht. Der Inhalt der Variablen RESULT wird mit 2 multipliziert und 1 dazu addiert. Das Ergebnis wird wieder der Variablen RESULT zugewiesen.
    Dirch den Code XRANDROPT="${SCREEN[$RESULT]}" in Zeile 88 wird aus dem Array SCREEN das Feld mit der Indexnummer die in der Variable RESULT steht, ausgelesen und der Wert der Variablen XRANDROPT zugewiesen. Dabei handelt es sich um die xrandr-Optionen, die notwendig sind, um den vom User ausgewählten Modus einzuschalten. Deshalb wird nun in Zeile 89 auch xrandr aufgerufen. Dies geschieht hier genau wie in der Zeile 64 durch eine Parametersubstitutionen, da der komplette Pfad zum Tool xrandr ja bereits in Zeile 5 durch which ermittelt und in der Variable abgelegt wurde. Dieser direkte Aufruf spart Zeit, RAM und Performance. Durch eine weitere Parametersubstitutionen werden xrandr auch die Parameter aus der Variable XRANDROPT übergeben.
    Zum Abschluss wird wieder die scan-Funktion aufgerufen, um die aktuellen Videoeinstellungen neu einzulesen.

    91   fi
    92  else
     

    Wenn das (X)dialog-Fenster beendet wurde (Beenden- oder Exit-Button), dann geht es hier weiter. Es wird lediglich die Variable EXIT auf den Wert 1 gesetzt, so dass die while-Schleife in Zeile 70 beim nächsten Test der Variable EXIT die Schleife verlässt und das Script sich beendet. nicht der Menüpunkt "Neuscan" ausgewählt wurde, dann geht es hier weiter.

    93   EXIT=1
    94  fi
    95 done
     

    Mit done gehts zurück zu Zeile 70 und der nächste Durchlauf der while-Schleife beginnt.

    96else
    97 echo "Kein X-Window gefunden!"
    98fi