Linux und Perl - Institute of Bioinformatics - Georg

Transcription

Linux und Perl - Institute of Bioinformatics - Georg
Linux und Perl
für Studierende der Biologie und (Molekularen) Medizin
Maike Tech – Torsten Crass – Martin Haubrock
Perl-Teil
von Torsten Crass
Version 1.3.2 [ Build 1073 ] vom 23. Juni 2009
Abteilung Bioinformatik (Medizinische Fakultät)
Georg-August-Universität Göttingen
Goldschmidtstraße 1, 37077 Göttingen
Kontakt:
[email protected]
ii
Inhaltsverzeichnis
I
Linux
5
II
Perl
9
1 Einführung
11
1.1
Algorithmen und Programme . . . . . . . . . . . . . . . . . .
11
1.2
Programmiersprachen . . . . . . . . . . . . . . . . . . . . . .
14
1.3
Erste Schritte in Perl . . . . . . . . . . . . . . . . . . . . . . .
16
1.4
Literale und Operatoren . . . . . . . . . . . . . . . . . . . . .
20
1.5
Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
1.6
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
1.7
Eingabe von der Konsole . . . . . . . . . . . . . . . . . . . . .
29
2 Verzweigungen und Schleifen
33
2.1
Kontrollfluss – Die bedingte Verzweigung . . . . . . . . . . .
33
2.2
Programmierpraxis . . . . . . . . . . . . . . . . . . . . . . . .
40
2.3
Mehr Kontrollfluss – Schleifen . . . . . . . . . . . . . . . . . .
47
3 Listen
51
3.1
Mit Listen arbeiten . . . . . . . . . . . . . . . . . . . . . . . .
51
3.2
Noch mehr Kontrollfluss – Noch mehr Schleifen . . . . . . . .
57
4 Arbeiten mit Dateien
63
4.1
Lesen aus Dateien . . . . . . . . . . . . . . . . . . . . . . . .
63
4.2
Schreiben in Dateien . . . . . . . . . . . . . . . . . . . . . . .
69
4.3
Navigieren in Dateien . . . . . . . . . . . . . . . . . . . . . .
70
4.4
Noch mehr zu Dateien . . . . . . . . . . . . . . . . . . . . . .
73
4.5
Recycling 1 – Ausführen externer Programme . . . . . . . . .
75
1
2
INHALTSVERZEICHNIS
5 Reguläre Ausdrücke
5.1
79
Teilstring-Suche . . . . . . . . . . . . . . . . . . . . . . . . . .
79
5.1.1
Gruppierungen und Zeichenklassen . . . . . . . . . . .
80
5.1.2
Positions- und Wiederholungsangaben . . . . . . . . .
83
5.2
Das Suchergebnis weiterverwenden . . . . . . . . . . . . . . .
86
5.3
Suchen und ersetzen . . . . . . . . . . . . . . . . . . . . . . .
89
5.4
Weitere Funktionen zur Zeichenketten-Manipulation . . . . .
90
6 Strukturierte Programmierung
97
6.1
Recycling 2 - Subroutinen . . . . . . . . . . . . . . . . . . . .
6.2
Lokale Variablen und Sichtbarkeit
6.3
Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
7 Hashes und Zeiger
97
. . . . . . . . . . . . . . . 103
115
7.1
Erstellen von Hashes . . . . . . . . . . . . . . . . . . . . . . . 115
7.2
Zeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
8 Biomolekulare Datenformate
133
8.1
Biomolekulare Datenbanken . . . . . . . . . . . . . . . . . . . 133
8.2
Biopolymersequenzen . . . . . . . . . . . . . . . . . . . . . . . 135
8.3
Proteinstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . 141
9 Modularisierung
153
9.1
Recycling 3 - Module . . . . . . . . . . . . . . . . . . . . . . . 153
9.2
BioPerl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
9.3
Web-Clients . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
10 Mehr Biologie
173
10.1 Zufall und Mutation . . . . . . . . . . . . . . . . . . . . . . . 173
10.2 Sequenzvergleich . . . . . . . . . . . . . . . . . . . . . . . . . 186
A Perl
191
A.1 Spezial-Variablen . . . . . . . . . . . . . . . . . . . . . . . . . 191
A.2 Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . 192
A.3 Perldoc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
B Molekularbiologische Abkürzungen
197
B.1 IUPAC-Codes . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
INHALTSVERZEICHNIS
3
B.2 Der genetische Standard-Code . . . . . . . . . . . . . . . . . . 200
C Nützliche Websites
201
D Buchempfehlungen
203
4
INHALTSVERZEICHNIS
Teil I
Linux
5
7
Der Linux-Teil des Kurses ist als separates Skript erhältlich.
8
Teil II
Perl
9
Kapitel 1
Einführung
Nach Studium dieses Kapitels können Sie
• die Begriffe Algorithmus und Programm erklären
• den Unterschied zwischen Compiler- und Interpretersprachen erläutern
• den Perl-Interpreter aufrufen
• Perl-Programme schreiben, die Benutzereingaben von der Kommandozeile abfragen, einfache Berechnungen durchführen und deren Ergebnis
auf der Konsole ausgeben.
1.1
Algorithmen und Programme
Was ist eigentlich ein Computerprogramm? Na, eine Implementierung eines
Algorithmus 1 !
Definition Algorithmus:
Eine Schritt-für-Schritt-Anleitung zur Lösung eines bestimmten Problems/einer Klasse von gleichartigen Problemen. Der Abstraktionsgrad
der Beschreibung ist dabei abhängig vom jeweiligen Problem und den zu
seiner Lösung zur Verfügung stehenden Mitteln.
1
vom arabischen Mathematiker Muhammad Ibn Musa Al Chwarismi, der um 900 n.
Chr. ein Lehrbuch Über die Regeln der Wiedereinsetzung und Reduktion von Gleichun”
gen“ schrieb
11
Algorithmus
12
KAPITEL 1. EINFÜHRUNG
Ein aus dem täglichen Leben gegriffenes Beispiel für eine solche Anleitung wäre zum Beispiel ein Kochrezept:
Beispiel: Kochrezept Bobotie
• 500 g Zwiebeln und 8 Zehen Knoblauch schälen, würfeln und in
etwas Öl andünsten.
• 250 g Reis zugeben und anbraten, bis er glasig wird.
• 500 g Schweinemett zugeben, mit reichlich Curry und etwas
Cayenne-Pfeffer bestäuben, salzen, pfeffern und anbraten.
• Das gewürfelte Fleisch eines kleinen Kürbis (oder 2 Gläser eingelegten, gut abgetropften Kürbis), je 200 g Rosinen und kleingeschnittene getrocknete Aprikosen sowie eine kleine Packung gewürfeltes
Toastbrot dazugeben.
• Alles in eine feuerfeste Form geben und mit 800 ml Sahne, verquirlt
mit 10 Eiern, Salz und Pfeffer, übergießen.
• Im Backofen bei 200 Grad ca. 60 – 90 Minuten garen; ggfs.
Flüssigkeit nachgießen oder die Form mit Alufolie abdecken.
Bobotie ist ein südafrikanischer Kürbisauflauf und ein prima PartyGericht; schmeckt auch Leuten, die sonst kein Trockenobst mögen! Statt
der feuerfesten Form kann man auch den ausgehöhlten Kürbis nehmen,
was natürlich besonders hübsch aussieht.
Hier liegt ein eher geringer Abstraktionsgrad vor, und die zur Problemlösung verfügbaren Mittel sind hinreichend bekannt. In Bezug auf die Programmierung von Computern – die ja universelle symbolverarbeitende Automaten darstellen – würde ein Algorithmus auf einer deutlich höheren Abstraktionsebene formuliert werden und beschreiben, nach welchen Regeln
bestimmte Daten/Symbolen manipuliert werden sollen. Aus der Mathematik kennen Sie den Gauß-Algorithmus zum Lösen linearer Gleichungssysteme
(oder, was äquivalent ist, dem Invertieren einer Matrix); ein etwas einfacher
gelagertes mathematisches Beispiel stellt die Vorschrift zur Berechnung des
Mittelwertes dar:
1.1. ALGORITHMEN UND PROGRAMME
13
Beispiel: Mittelwertbildung
• Für alle zu mittelnden Zahlen xi mit i = 1...n:
– Addiere xi zur bisherigen Summe von x1 ...xi−1 .
P
• Dividiere die erhaltene Summe n1 xi durch die Anzahl n der zu
mittelnden Zahlen.
Ein weiteres, eher bioinformatisches Beispiel wären die Regeln, nach denen zwei Zeichenketten (Sequenzen) optimal gegenübergestellt (aliniert, von
engl. to align, ausrichten“) werden können.
”
Beispiel: Sequenzalignment
• Hier wird es beliebig kompliziert... wir verweisen auf die einschlägigen Vorlesungen Algorithmen der Bioinformatik 1...n
Bevor wir nun in medias res gehen, sei noch eine kurze Rückblende
bezüglich des Aufbaus eines typischen PC2 gestattet: ein solcher besteht ja
aus den Komponenten CPU , Arbeitsspeicher , Massenspeicher (wie z.
B. Festplatte) sowie Ein- und Ausgabegeräten. Ein Computerprogramm
wäre dann etwa wie folgt zu definieren:
Definition Programm:
Eine Folge von Anweisungen, die den Computer/die CPU instruieren,
Daten/Symbole einem Algorithmus gemäß zu verarbeiten. Dazu gehört
auch das Einlesen von Eingabedaten von Eingabegeräten oder Massenspeichern sowie das Ausgeben der Ergebnisse auf Ausgabegeräten bzw.
auf Massenspeicher.
Wie nun erhält die CPU Kenntnis von den Anweisungen, die im Programm stehen? Beim Starten eines Programms weist der/die NutzerIn das
Betriebssystem3 an, den Programmcode vom Massenspeicher (wo er in einer
sogenannten ausführbaren Datei oder executable file, kurz executable
steht) in den Arbeitsspeicher zu laden und die einzelnen Anweisungen ab2
Von englisch personal computer, persönlicher Computer“; ursprünglich als allgemei”
nes Antonym zur typischen Großrechner/Terminal-Architektur gedacht, inzwischen jedoch
oft etwas enger gefasst und als Bezeichnung für IBM-kompatible“ Personal-Computer mit
”
Intel-CPU verwendet. Dabei sind z. B. Apple-Computer mit PowerPC-Prozessor genau so
persönlich einsetzbar...
3
das selbst ein Programm ist...
ausführbare Datei
executable file
14
Prozess
KAPITEL 1. EINFÜHRUNG
zuarbeiten. Das Programm steht dabei als eine Folge von Zahlen (die die
verschiedenen Anweisungen codieren) im Arbeitsspeicher und teilt sich diesen mit den Daten (und meist auch noch mit anderen Programmen sowie
deren Daten). Wie Sie im Linux-Teil des Kurses gelernt haben, nennt man
die Folge aller Zustände, die die CPU und der dem Programm zugewiesene
Arbeitsspeicher bei der Programmausführung durchlaufen, einen Prozess.
1.2
Programmiersprache
Maschinensprache
operation codes
executable binary file
Assemblerspachen
Mnemonics
Quellcode
source code
Assembler
Höhere
Programmiersprachen
Programmiersprachen
Die Anweisungen eines Programms werden in einer genau spezifizierten
Sprache niedergeschrieben – einer Programmiersprache eben. Und derer
gibt es viele... Eine erste Einteilung mag danach erfolgen, wie stark verschiedene Programmiersprachen von den Spezifika verschiedener HardwarePlattformen abstrahieren:
• Maschinensprache: Darunter versteht man eine Folge von Zahlencodes (operation codes, kurz OpCodes), die von der CPU als Anweisungen interpretiert werden, die dann ausgeführt werden – eine sehr
maschinennahe, aber für Menschen sehr unbequem Darstellung. Eine
Datei, die Maschinencode enthält (executable binary file), ist von
der CPU direkt ausführbar, sobald sie vom Betriebssystem in den Arbeitsspeicher geladen wird.4 Allerdings unterscheiden sich verschiedene
CPU-Typen darin, welche OpCodes sie verstehen; Maschinenprogramme sind also nicht plattform-unabhängig.
• Assemblerspachen: Die Zahlencodes der Maschinensprache werden
hier durch kurze Buchstabenfolgen ( Mnemonics“) repräsentiert (et”
wa ADD, JUMP etc.), die man in eine Textdatei (Quellcode, source code) schreibt – was besser menschenlesbar, aber für die CPU
nicht mehr unmittelbar verständlich ist. Der Quellcode eines in einer
Assemblersprache geschriebenen Programms muss daher von einem
speziellen, Assembler genannten Programm in die jeweilige Maschinensprache übersetzt werden, bevor es ausgeführt werden kann. Die
Ausgabe eines Programmlaufs des Assemblers ist eine dem Quellcode entsprechende Folge von OpCodes. Diese wird in der Regel in eine
Datei geschreiben, wodurch man ein ausführbares binary enthält.
• Höhere Programmiersprachen: Assemblersprachen haben den Nachteil, dass sie CPU-Typ-abhängig sind (die ausführbaren Dateien sind
nicht plattformunabhängig!) und sich der/die ProgrammiererIn dementsprechend selbst sehr detailiert darum kümmern muss, wie die vom
4
Genau genommen muss noch ein Binden“ (linking) genannter Schritt durchgeführt
”
werden, falls der auszuführende Code über mehrere Dateien verteilt ist.
1.2. PROGRAMMIERSPRACHEN
15
Programm zu verarbeitenden Daten eingelesen, zwischengespeichert
und ausgegeben werden sollen. Sogenannte höhere Programmiersprachen abstrahieren davon und stellen Anweisungen zur einfachen Datenein- und -ausgabe bereit und erlauben es, auf Speicherplatz mittels symbolischer Namen zuzugreifen (etwa: X“ statt Speicheradres”
”
sen 4897 – 4899“) – ohne dass sich der/die ProgrammiererIn darum
kümmern muss, wie die jeweilige Betriebssystem/CPU-Kombination
den Arbeitsspeicher verwaltet. Natürlich versteht die CPU ein in einer höheren Programmiersprache geschriebenes Programm noch weniger als ein Assemblerprogramm – auch hier ist eine Übersetzung des
Quellcodes in ausführbaren Code nötig. Dabei sind mehrere prinzipiell
verschiedene Methoden denkbar:
– Übersetzung vor Ausführung: Das Übersetzer-Programm heißt
hier Compiler ; wie bei Assemblersprachen erhält man als Ausgabe ein ausführbares binary. Gerade bei größeren Programmierprojekten kann das Compilieren ziemlich lange dauern – dafür ist
die Ausführung des Programms dann aber sehr schnell, da der
erzeugte Maschinencode auf die jeweilige Betriebssystem/CPUKombination zugeschnitten ist. Daraus ergibt sich aber auch unmittelbar der Nachteil, dass die binaries nicht plattformunabhängig
sind. Beispiele: C, C++, Pascal, Fortran...
– Übersetzung während Ausführung: Hier führt die CPU das Programm nicht direkt aus, sondern startet ein Interpreter genanntes Programm, das seinerseits den Quellcode erst während der
Programmausführung (zur Laufzeit) Schritt für Schritt übersetzt
(interpretiert). Daher ist die Ausführung i. d. R. etwas langsamer als bei compilierten Programmen. Vorteile: Die Compilation
entfällt (interaktive Programmentwicklung möglich) und die Programme sind plattformunabhängig, sofern es für die Zielplattformen auch den entsprechenden Interpreter gibt. Beispiele: Basic,
Shell...
– Heute werden verstärkt auch Mischformen verwendet: Erst übersetzt ein Compiler den Quellcode in einen kompakteren, für die
maschinelle Analyse optimierten Zwischencode, der dann von einer Art (plattformspezifischem) Interpreter (manchmal Virtual
Machine genannt) ausgeführt wird. Beispiele: Java, Perl, Python...
Alternativ können höhere Programmiersprachen auch nach ihren verschiedenen Philosophien, wie Algorithmen formuliert werden sollten,
eingeteilt werden (Programmierparadigmen)5 :
5
Wir erwähnen die verschiedenen Programmierparadigmen hier nur der Vollständigkeit
halber – für das Verständnis des Kurses sind sie nicht weiter wichtig.
Compiler
Interpreter
Laufzeit
Virtual Machine
16
KAPITEL 1. EINFÜHRUNG
Prozedurale Sprachen
– Prozedurale oder Imperative Sprachen: Anweisungsfolgen werden direkt in der Reihenfolge angegeben, in der sie ausgeführt
werden sollen. Beispiele: C, Pascal, Fortran, Basic, Shell, Perl...
Imperative Sprachen
Objektorientierte
Sprachen
– Objektorientierte Sprachen: Daten und Anweisungen werden
zu sogenannten Objekten zusammengefasst. Beispiele: Java, C++,
Smalltalk...
Objekt
Logische Sprachen
– Logische Sprachen: Programme werden durch logische Aussagen repräsentiert und dann deren Wahrheitsgehalt untersucht.
Beispiele: Prolog, diverse Inferenzsysteme...
Funktionale Sprachen
– Funktionale Sprachen: Man programmiert durch Definition
von Funktionen (im mathematischen Sinne) und deren Anwendung auf Daten. Beispiele: Lisp, Logo...
1.3
Erste Schritte in Perl
Perl ist eine compiliert-interpretierte imperative Sprache, die 1987 von Larry
Wall entwickelt wurde und derzeit in Version 5.8 vorliegt; Version 6.0 steht
zum Zeitpunkt der Schriftlegung dieses Skriptes kurz vor der Fertigstellung. Doch warum haben wir uns unter den – wortwörtlich – Hunderten von
Programmiersprachen für das Bioinformatik-Programmierpraktikum gerade
Perl ausgesucht? Nun, für Perl sprechen mehrere Gründe:
Open Source
• Perl ist komplett frei, einschließlich des Quellcode des Perl-Compilers/Interpreters6 (Open Source)
• Perl ist verfügbar für viele Plattformen (siehe http://www.perl.org; für
die Windows-Version siehe http://www.activestate.com).
• Perl interpretiert den Quellcode je nach Kontext (meist) so, wie es
der/die ProgrammiererIn intendiert, ohne zu viele Angaben explizit
ausschreiben zu müssen (Do what I mean-Philosphie).
syntaktische Analyse
Parsing
• Perl hat mächtige Textanalyse- und Manipulationsfähigkeiten direkt
in die Sprache eingebaut; daher ist Perl gut geeignet zum Extrahieren
und Aufbereiten von Informationen aus strukturierten Textdateien –
ein Vorgang, der auch als syntaktische Analyse oder Parsen bezeichnet wird und unabdingbar für die weitere Verarbeitung von in den
Textdateien gespeicherten Informationen ist. Nicht umsonst steht das
Akronym PERL ja für practical extraction and report language! Und
gerade in der Bioinformatik stehen praktisch alle Daten (Sequenzen,
Strukturen, Expressionsprofile, biomolekulare Netzwerke...) in Form
6
Der Kürze halber werden wir im Folgenden nur noch vom Perl-Interpreter sprechen
– es sei denn, wir beziehen uns explizit auf den Compiler-Anteil
1.3. ERSTE SCHRITTE IN PERL
17
von Textdateien zur Verfügung; zudem können Sequenzen selbst als
Texte/Zeichenketten aufgefasst werden.
• Perl ist gut modularisierbar: Es stehen zahlreiche Bibliotheken mit fertig programmiertem Perl-Code zur Verfügung, die in eigene Programme eingebunden werden können – darunter auch zahlreiche Bioinformatik-Module. (Merke: Was immer Du auch in Perl programmierst –
schon morgen wirst Du ein Modul finden, das genau das leistet, was
Du gestern erst mühsam implementiert hast! )
Bevor wir nun mit dem Programmieren in Perl beginnen, laden Sie bitte
das Kursmaterial als gzip-gepacktes tar-Archiv von
http://www.bioinf.med.uni-goettingen.de/teaching/lehrmaterial/med molmed bio linuxperl/
herunter und speichern es in Ihrem Home-Verzeichnis7 . Vergewissern
Sie sich, dass nun eine Datei namens perl4bio.tar.gz in Ihrem HomeVerzeichnis existiert. Entpacken Sie nun das Archiv, z. B. durch Angabe
von
~$ gunzip perl4bio.tar.gz Enter ~$ tar xvf perl4bio.tar Enter Nach dem Entpacken bemerken Sie ein zusätzliches Verzeichnis namens
~/perl4bio in Ihrem Home-Verzeichnis. Es enthält dieses Skript im pdf-
Format, alle Beispiel-Listings als einzelne Text-Dateien sowie Musterlösungen zu allen Übungen und Aufgaben. Zudem enthält das Unter-Unterverzeichnisse ~/perl4bio/lib ein paar Programmbibliotheken, die von einigen
der Musterlösungen benötigt werden, während ~/perl4bio/data typische
Beispiele von Daten-Dateien enthält, wie sie in der Bioinformatik verwendet
werden.
Nun aber sollen Sie endlich ihre ersten Schritte in Perl machen! Erstellen
Sie mit Hilfe eines Texteditors Ihrer Wahl eine Text-Datei namens hello.pl8
mit folgendem Inhalt:
Listing 1.1: Hello world
print ( " Hello world !\ n " )
7
Achten Sie genau darauf, dass Sie die Datei wirklich in Ihrem Home-Verzeichnis speichern; je nach Voreinstellung tendieren manche Browser dazu, Dateien in ~/Desktop o.
ä. zu speichern oder gar gleich zu öffnen!
8
Seit Veröffentlichung des Lehrbuch-Klassikers The C Programming Language von Kernighan und Ritchie (Prentice-Hall, 1978) beginnen praktisch alle Einführungen in eine
Programmiersprache mit einem Hello World!-Beispiel
18
Ausgabe
print
KAPITEL 1. EINFÜHRUNG
Erklärung: Das Programm enthält nur eine einzige Anweisung: den
zwischen den Anführungszeichen angegebenen Text auf dem Standardausgabegerät (i. d. R. der Konsole) auszugeben (print). Das abschließende n
– von newline, neue Zeile – steht übrigens für einen Zeilenumbruch.
Nun geben Sie an der Kommandozeile bitte folgendes ein:
~$ perl hello.pl Enter Erklärung: perl startet den Perl-Interpreter, und der Name der auszuführenden Datei – hier hello.pl – wird als Argument übergeben.
Als Ausgabe erhalten Sie:
Hello World!
Übungen
1.1 Was geschieht, wenn Sie den Zeilenumbruch weglassen?
Nun besteht ein Programm in den seltensten Fällen aus nur einer einzigen Anweisung; normalerweise folgen in einem Programm eine Hand voll bis
zu Hunderttausende Anweisungen aufeinander. Nacheinander auszuführende
Anweisungen schreiben Sie in Perl einfach der Reihe nach auf, wobei die einzelnen Anweisungen durch Semikolons voneinander getrennt werden müssen9 :
Listing 1.2: Mehrere Befehle
print ( " Hello world !\ n " ) ;
print ( " How are you ?\ n " ) ;
print ( " I am fine .\ n " )
Am Ende des Programms darf auch ein Semikolon stehen – muss aber
nicht.
Übungen
1.2 Schreiben Sie ein Perl-Programm, das eine (mehrzeilige) GrußBotschaft Ihrer Wahl ausgibt! Achten darauf, dem Programm
(und allen weiteren Programmen, die Sie schreiben werden!)
einen sinnvollen Namen zu geben, der bereits andeutet, welchem
Zweck das Programm dient.
Noch einmal kurz zurück zum ursprünglichen hello.pl-Programm. Es
mag etwas lästig sein, zum Starten eines Perl-Programms immer erst den
9
Wir verwenden in diesem Skript die Begriffe Anweisung und Befehl synonym.
1.3. ERSTE SCHRITTE IN PERL
19
Perl-Interpreter aufrufen zu müssen. Abhilfe schafft folgende Ergänzung in
hello.pl:
# !/ usr / bin / perl
print ( " Hello world ! " )
Speichern Sie die Datei und setzen Sie ihre Attribute mit
~$ chmod u+x hello.pl Enter auf ausführbar. Nun kann hello.pl wie jedes andere Programm durch
Eingabe seines Namens
~$ pfad/zu/hello.pl Enter bzw. (falls es sich im Arbeitsverzeichnis befindet)
~$ ./hello.pl Enter an der Kommandozeile gestartet werden.
Erklärung: Die Shell, die ja die Benutzereingaben interpretiert und
ggfs. die zum Starten eines Programms nötigen Betriebssystemfunktionen
aufruft, analysiert die erste Zeile einer als ausführbar markierten Datei dahingehend, ob sie mit der Zeichenfolge #! beginnt, gefolgt von einer Pfadangabe (Shebang-Zeile 10 ). Falls dem so ist, wird das im Pfad (dem Interpreter-Pfad ) angegebene Programm ausgeführt und diesem der Name der
fraglichen Datei als Argument übergeben – das kennen Sie ja bereits aus dem
Linux-Teil, wo Sie für Shell-Skripte /bin/sh als Pfad zum Shell- Interpreter“
”
angegeben hatten. Für Perl-Programm muss an dieser Stelle dementsprechend der Pfad zum Perl-Interpreter angegeben werden – und das ist auf
UNIX-artigen Systemen gewöhnlich /usr/bin/perl. Unter Windows funktioniert das freilich nicht so – Perl-Programme können hier jedoch z. B. per
Doppelklick ausgeführt werden, wenn man die Endung .pl mit perl.exe
als Anwendung verknüpft. Falls dies nicht schon automatisch bei der Installation von Perl geschehen ist, sollte Windows beim Doppelklick nachfragen, mit welchem Programm die .pl-Datei geöffnet werden soll – dann
Programm aus einer Liste auswählen → Durchsuchen... wählen und den Pfad zu perl.exe
angeben. Oder im Explorer unter Extras → Ordneroptionen → Dateitypen → Neu pl
als neuen Dateityp anlegen und dann unter Ändern wie oben mit perl.exe
verknüpfen.
Da die Shebang-Zeile so ungemein nützlich ist, werden wir im Verlauf des
Kurses davon ausgehen, dass sie am Begin eines jeden Perl-Programms steht
– auch wenn wir sie in den abgedruckten Listings nicht explizit aufführen.
10
Etymologie unsicher, wahrscheinlich Hacker-Sprache für hash (#) und bang (!), evtl.
auch von shell-bang
Shebang-Zeile
Interpreter-Pfad
20
KAPITEL 1. EINFÜHRUNG
1.4
Literale
Literale und Operatoren
Zur Datenverarbeitung benötig man Zweierlei: Daten und Verarbeitung. Eine einfache Möglichkeit, Daten in ein Perl-Programm hineinzubekommen,
besteht darin, sie als sog. Literale direkt in den Quelltext zu schreiben.
Literale sind Zeichenkette, die einer bestimmten Syntax gehorchen und für
verschieden Arten von Daten stehen können:
2
2.7
-10
6.022 e23
"A"
" Eine Zeichenkette "
" 2.7 "
numerische Daten
Zeichenketten
Strings
Numerische Daten (Zahlenwerte) werden demnach einfach durch die
Angabe von Ziffern, ggfs. mit Dezimalpunkt (nicht Komma!), Vorzeichen
und Zehner-Exponent (ähnlich wie die EE-Taste beim Taschenrechner), angegeben. Einzelne Zeichen und Zeichenketten (Strings) werden direkt
ausgeschrieben und in Anführungszeichen gesetzt (kennen wir ja schon von
hello.pl):
print("Hello world!\n")
Auch Zahlen können so einfach mittels der print-Anweisung ausgegeben
werden:
print(6.022e23)
Hier macht sich allerdings der fehlende Zeilenumbruch in der Ausgabe
unangenehm bemerkbar. Um dies zu beheben, könnten wir einfach eine weitere print-Anweisung anhängen – wobei wir die Anweisungen wiederum, wie
unter Perl üblich, durch ein Semikolon trennen:
print (6.022 e23 ) ;
print ( " \ n " )
Wahlweise erlaubt es die print-Anweisung auch, mehrere, durch Kommata getrennte Elemente auszugeben (wobei Zahlen und Zeichenketten beliebig
gemischt werden dürfen):
print("Avogadro-Konstante: ", 6.022e23, "\n")
Operatoren
Ausdrücke
Kommen wir nun zur Verarbeitung von Daten – was letztenendes immer darauf hinausläuft, mit den Daten irgendwelche (Rechen-)Operationen
durchzuführen. Dafür stellt Perl die verschiedensten Operatoren zur Verfügung, die zusammen mit Daten zu sogenannten Ausdrücken zusammengefügt werden können – das sind Zeichenfolgen, die der Perl-Interpreter
während der Ausführung einer Anweisung auswertet ( ausrechnet“) und
”
dann so weiter verfährt, als stünde im Quelltext statt des Ausdrucks das
1.4. LITERALE UND OPERATOREN
21
Ergebnis seiner Auswertung. So können wir uns z. B. das Ergebnis der Auswertung eines Ausdrucks mittels print ausgeben lassen – hier demonstriert
am Beispiel einiger Perl-Operatoren, die sich auf numerische Daten beziehen:
Listing 1.3: Numerische Operatoren
print (3 + 2 , " \ n " ) ;
print (3 - 2 , " \ n " ) ;
print (3 * 2 , " \ n " ) ;
print (3 / 2 , " \ n " ) ;
print (3 ** 2 , " \ n " ) ;
print (23 % 7 , " \ n " )
#
#
#
#
#
#
Addition
Subtraktion
Multiplikation
Division
Potenzierung
Rest nach ganzzahliger Teilung
Als Ausgabe erhalten wir:
5
1
6
1.5
9
2
Ein #-Zeichen leitet in Perl übrigens einen Kommentar ein – alles ab
dem # bis zum Zeilenende wird vom Perl-Interpreter ignoriert. Hier lassen
sich dann hilfreiche Kommentare zur Funktion der jeweiligen QuellcodeZeile unterbringen, was gerade zur Dokumentation größerer Programmprojekte unerläßlich ist. Übrigens können Anweisungen, Literale, Operatoren
und Kommentare durch beliebig viele Leerzeichen und -zeilen voneinander
getrennt werden – wovon man zur Verbesserung der Lesbarkeit des Quellcodes auch reichlich gebrauch machen sollte!
Wie auch in der Mathematik ( Punktrechnung vor Strichrechnung“),
”
haben in Perl verschiedene Operatoren verschiedene Priorität bei der Auswertung eines Ausdrucks. Durch Klammerung kann die Auswertung von
Teilausdrücken niederer Priorität erzwungen werden:
#
Kommentar
Priorität
Klammerung
Listing 1.4: Operator-Prioritaet
print (3 + 4 * 2 , " \ n " ) ;
print ((3 + 4) * 2 , " \ n " ) ;
# Punkt vor Strich
# Strich vor Punkt
ergibt
10
14
Beachten Sie, dass Perl zur Klammerung von Teilausdrücken lediglich
runde Klammern () zulässt!
()
22
KAPITEL 1. EINFÜHRUNG
Tipp 1.1: Hier werden Sie geholfen...
Über die von Perl zur Verfügung gestellten Operatoren sowie deren Prioritätenfolge können Sie sich mittles des Befehls perldoc informieren: Einfach an der Kommandozeile
~$ perldoc perlop Enter eingeben. Geben Sie perldoc hingegen perltoc (von table of contents,
Inhaltsverzeichnis) als Argument mit auf den Weg, bekommen Sie eine
Liste der (zahlreichen!) Hilfe-Themen. perldoc perlfunc zeigt Ihnen eine erschöpfende Liste aller Perl-Befehle mit Erklärung von Syntax und
Funktion an. Um sich speziell üeber einen bestimmten Perl-Befehl zu informieren, rufen Sie perldoc mit der Option -f auf, gefolgt von eben
diesem Befehl; so springt
perldoc -f print
direkt zur entsprechenden Stelle in perlfunc.
Alternativ (und optisch vielleicht etwas hübscher aufbereitet) finden Sie
die Perl-Dokumentation auch auf der Website http://perldoc.perl.org.
Falls Sie ActivePerl für Windows benutzen, wird die Perl-Hilfe
gleich im HTML-Format mitinstalliert; die Startseite finden Sie unter
X:\Pfad\zu\Perl\html\index.html.
.
Konkatenation
Auch für die Verarbeitung von Zeichenketten stellt Perl verschiedene
Operatoren zur Verfügung. So können Strings mittels des Punkt-Operators
. verkettet (konkateniert) werden:
print("Hell" . "o world!" . "\n")
ergibt wiederum
Hello world!
Der Unterschied zum arithmetischen + wird deutlich, wenn man folgendes
Beispiel-Programm laufen lässt:
Listing 1.5: Addition vs. Konkatenation
print (17 + 4 , " \ n " ) ;
print ( " 17 " . " 4 " , " \ n " ) ;
beschert uns
21
174
als Ausgabe. Man mag sich nun fragen, was geschieht, wenn man versehentlich versucht, eine Zeichenkette in einer numerischen Berechnung zu verwenden – oder umgekehrt eine Zahl als Zeichenkette behandelt. Tatsächlich
ist Perl da recht tolerant – Zeichenketten, die keinem gültigen Zahlenformat
entsprechen, werden einfach wie 0 behandelt, und andererseits werden Zahlenwerte in Stringoperationen einfach in entsprechende Zeichenketten umge-
1.5. VARIABLEN
23
wandelt. Das entspricht zwar der Do what I mean-Philosophie von Perl, ist
aber in den seltensten Fällen sinnvoll und sollte daher weitgehend vermieden
werden.
Ein weiterer interessanter String-Operator ist der Polymerisations-O”
perator“ x, der den linksseitigen String so oft hintereinander hängt, wie die
rechtsseitige (ganze) Zahl angibt:
x
Listing 1.6: Polymerisation
print ( " Deutschland " , " balla " x 2 , " \ n " )
ergibt
Deutschland balla balla11
Auch Zeichenketten-Operatoren können beliebig geklammert werden.
Ein dreifaches Hipp-Hipp-Hurra!“ließe sich zum Beispiel durch
”
Listing 1.7: Klammerung bei Zeichenketten-Operationen
print ( " Ein dreifaches \ n " , (( " Hipp - " x 2) . " Hurra !\ n " ) ➘
x 3)
erreichen:
Ein dreifaches
Hipp - Hipp - Hurra !
Hipp - Hipp - Hurra !
Hipp - Hipp - Hurra !
1.5
Variablen
Für die allermeisten Algorithmen werden die gleichen Daten in mehreren
Verarbeitungsschritten benötigt, so dass es nicht ausreicht, sie an nur einer
Stelle im Quelltext explizit aufzuschreiben. Zudem sind Algorithmen ja i. d.
R. daraufhin ausgelegt, gleich eine ganze Klasse von gleichartigen Problemen zu lösen, wobei die konkret zu verarbeitenden Daten als Nutzereingabe
erwartet werden und somit ebenfalls nicht direkt im Programmtext kodiert
werden können. Sowohl für die Eingabe von Daten als auch für deren Wiederverwendung benötigen wir also eine Möglichkeit, diese Daten irgendwo
zwischenzuspeichern und später wieder darauf zuzugreifen. Wie praktisch
alle höheren Programmiersprachen bietet auch Perl die Möglichkeit, vom
Programm aus Platz im Arbeitsspeicher zu reservieren, mit Daten zu füllen
und diese Daten später wieder auszulesen.
Solche Speicherplätze nennt man Variablen – ein Begriff, der den Va11
Schlagzeile der BILD-Zeitung zur Fußball-Weltmeisterschaft 1990
Variablen
24
Datentypen
skalare Variable
Skalar
$
Zuweisungs-Operator
=
KAPITEL 1. EINFÜHRUNG
riablen der Mathematik angelehnt ist, die ja auch Platzhalter für beliebige (einzusetzende) Werte darstellen. Perl kennt Variablen für verschiedene
Arten von Daten (Datentypen), die wir im Verlauf des Kurses auch alle
kennen lernen werden; die bisher behandelten Datentypen (numerisch, Zeichenketten) werden in sogenannten skalaren Variablen (kurz: Skalaren)
gespeichert. Verschiedene skalare Variablen werden durch jeweils einen vom
Nutzer/von der Nutzerin vergebenen Namen identifiziert, dem zusätzlich
noch ein $-Zeichen vorangestellt wird. Mittels des Zuweisungs-Operators
= kann einer Variablen dann nach dem Schema $Variablenname = Wert ein
Wert zugewiesen werden:
Listing 1.8: Wertzuweisung an Variablen
$x = 1;
$zahl = 2;
$zahl2 = 3;
$avogadroKonstante = 6.022 e23 ;
$noch_eine_Zahl = 42;
$vorname = " Donald " ;
$nachname = " Duck " ;
$text = " Hello world !\ n " ;
...
Bezeichner
Identifier
Alle Dinge, die in Perl einen solchen nutzervergebenen Namen (einen
Bezeichner oder Identifier ) tragen können (und außer den skalaren Variablen werden wir derer noch einige kennen lernen...), können mit einer fast
beliebigen Kombination aus Groß- und Kleinbuchstaben, Ziffern und dem
Unterstrich _ bezeichnet werden; es gilt lediglich die Einschränkung, dass
ein solcher Name nicht mit einer Ziffer beginnen darf.
Skalare Variablen dürfen im Quelltext überall stehen, wo auch Literale
für numerische Werte oder Zeichenketten stehen dürfen; so können wir Skalare einfach per print ausgeben (etwa in Zeilen 2, 3 oder 4) oder auch in
Ausdrücke einsetzen (wie z. B. in den Zeilen 5 oder 8):
1.5. VARIABLEN
25
Listing 1.9: Verwendung von Variablen
1
2
3
4
5
6
7
8
9
...
print ( $x , " \ n " ) ;
print ( $text ) ;
print ( " Avogadro - Konstante : " , $avogadroKonstante , " \ n " )➘
;
$ergebnis = $zahl + $zahl2 ;
print ( $zahl , " + " , $zahl2 , " = " , $ergebnis , " \ n " ) ;
print ( " Oder lieber multiplizieren : " , $zahl * $zahl2 , "➘
\n");
$name = $vorname . " " . $nachname ;
print ( $name , " \ n " )
Lassen Sie ein Programm, dessen Quellcode sich aus den beiden vorherigen Listings zusammensetzt, laufen, erhalten Sie folgende Ausgabe:
1
Hello world !
Avogadro - Konstante : 6.022 e +23
2 + 3 = 5
Oder lieber multiplizieren : 6
Donald Duck
Bitte stellen Sie sicher, dass Sie, bevor Sie weiterlesen, verstanden haben,
wie diese Ausgabe zustande kommt!
Noch ein paar Bemerkungen zum Zuweisungsoperator: Obgleich dem
mathematischen = ähnlich, hat der Zuweisungsoperator = eine völlig andere
Bedeutung! Er fordert den Perl-Interpreter nämlich auf, zunächst den Ausdruck, der rechts des = steht, auszuwerten und das Ergebnis der Variablen,
die links davon steht, zuzuweisen. Demnach sind Ausdrücke wie
$x = $x + 1
erlaubt (und auch sinnvoll), die mathematisch nun wirklich keinerlei Sinn
ergeben... Für Perl hingegen bedeutet dieser Ausdruck lediglich, den aktuellen Wert der Variablen $x zu nehmen, 1 hinzuzuzählen und das Ergebnis
in einer Variablen – hier eben zufällig“ wieder in $x – zu speichern. Hier
”
wird auch deutlich, dass ein und derselben Variablen durchaus mehrfach ein
Wert zugewiesen werden kann – dabei wird ein eventuell zuvor darin gespeicherter Wert überschrieben. Die erstmalige Zuweisung eines Wertes an eine
Variable nennt man übrigens auch deren Initialisierung .
Häufig wird man sich genötigt sehen, in einem Programm abwechselnd
fest kodierten Text und variable Berechnungsergebnisse auszugeben – so
etwa in folgendem Programm:
Initialisierung
26
KAPITEL 1. EINFÜHRUNG
Listing 1.10: Kochsalz
$Mw = 58.5;
$n = 50 e -3;
$m = $Mw * $n ;
print ( $n * 1000 ,
#
#
#
"
Molare Masse von Kochsalz [ g / mol ]
50 Millimol davon
Masse ausrechnen
mmol NaCl wiegen " , $m , " g .\ n " )
Perl erlaubt es hier, den Schreibaufwand zu verringern, indem die Namen
von Variablen direkt in ein Zeichenketten-Literal hineingezogen werden:
print($n * 1000, " mmol NaCl wiegen $m g.n")
Interpolation
{...}
Der zu berechnende Ausdruck $n * 1000 muss weiterhin draußen blei”
ben“, aber $m können wir mit in die doppelten Anführungszeichen " einschließen; der Perl-Interpreter identifiziert $m trotzdem als Variablennamen
und fügt an seiner Stelle den Variablenwert ein. (Man sagt auch, Perl interpoliere die Variable.) Falls in einer Zeichenkette auf eine Variable weitere
Buchstaben, Ziffern oder ein Unterstrich folgen, kann Perl natürlich nicht
wissen, wo der Variablenname zuende ist; in solchen Fällen kann man Perl
durch Setzen von geschweiften Klammern {...} auf die Sprünge helfen:
print("Ein ${anzahl}eck hat $anzahl Ecken.\n")
Verwenden wir in Listing 1.10 einfache (’) statt doppelter (") Anführungszeichen
print($n * 1000, ’ mmol NaCl wiegen $m g.n’)
so erhalten wir
50 mmol NaCl wiegen $m g.\n
\
Aufhebungszeichen
escape character
als Ausgabe. Offenbar wurden weder die Variable $m noch die Zeilenumbruchs-Sequenz \n vom Perl-Interpeter berührt, sondern wortwörtlich so
ausgegeben, wie sie in der Zeichenkette stehen – ein Verhalten, dass gelegentlich beabsichtigt ist und durch geeignete Wahl von ’ oder " ein- und
ausgeschaltet werden kann. In einem mit " umschlossenen String lässt sich
übrigens ein " einfügen, indem man ihm einen backslash \ voranstellt (also
\") – entsprechend gilt auch für ’-umschlossene Strings, dass ein ’ durch \’
darin eingefügt werden kann. Generell wird \ in Perl benutzt, um die Wirkung des folgenden Zeichens in Zeichenketten-Literalen aufzuheben – daher
nennt man es auch ein Aufhebungszeichen oder escape character . Und
wie fügt man das Aufhebungszeichen selbst in eine Zeichenkette ein? Na,
indem man es aufhebt: \\
1.6. FUNKTIONEN
27
Übungen
1.3 Was passiert, wenn Sie dem Variablennamen $m in
print($n * 1000, " mmol NaCl wiegen $m g.\n";)
einen backslash voranstellen?
1.4 Schreiben Sie ein Perl-Programm, das die Anzahl Wassermoleküle in 50 µL Wasser ausrechnet! (Hinweis: N = nNA , n =
m/MW , ρ = m/V , MW = 18 g/mol)
1.6
Funktionen
Aus der Mathematik kennen wir den Begriff der Funktion – eine Rechenvorschrift, die einem Wert (oder einer Kombination mehrerer Werte) einen
Ergebniswert zuordnet. Darunter befinden sich so bekannte Vertreter wie
die sin-, die cos- oder die ln-Funktion – für die Perl selbstverständlich entsprechende Pendants bereithält. Perl bietet eine ganze Reihe arithmetischer
Funktionen (sqrt, sin, cos, log, exp, int etc.), die einen numerischen Wert
als Argument annehmen und bei der Auswertung des jeweiligen Ausdrucks
durch den berechneten Funktionswert – auch Rückgabewert der Funktion
genannt, nicht zu verwechseln mit der Ausgabe eines Wertes z. B. auf dem
Bildschirm – ersetzt werden:
Listing 1.11: Arithmetische Funktionen
$PI = 3.1415926;
print ( " Die Wurzel aus 64 ist " , sqrt (8**2) , " .\ n " ) ;
print ( " Der Sinus von 60 Grad ist " , sin (60* $PI /180) , "➘
.\ n " ) ;
print ( " Der Kosinus von 0 Grad ist " , cos (0) , " .\ n " ) ;
print ( " Der natürliche Logarithmus von 1 ist " , log (1) , ➘
" .\ n " ) ;
print ( " e hoch ln ( pi ) ist " , exp ( log ( $PI ) ) , " .\ n " ) ;
print ( " Der ganzzahlige Anteil von $PI ist " , int ( $PI ) , ➘
" .\ n " )
ergibt als Ausgabe:12
Die Wurzel aus 64 ist 8.
Der Sinus von 60 Grad ist 0.5.
Der Kosinus von 0 Grad ist 1.
12
Beachten Sie, dass die trigonometrischen Funktionen sin und cos die Angabe des
Winkels in Radiant erwarten, nicht in Grad! Die Umrechnung kann gem. α[rad] =
α[grad] × π/180 erfolgen.
Funktion
sqrt
sin
cos
log
exp
int
Rückgabewert
28
KAPITEL 1. EINFÜHRUNG
Der natürliche Logarithmus von 1 ist 0.
e hoch ln ( pi ) ist 3.1415926.
Der ganzzahlige Anteil von 3.1415926 ist 3.
In Zeile 6 sehen Sie übrigens, dass Funktionen in Perl – genau wie in der
Mathematik – auch geschachtelt verwendet werden dürfen. Damit können
Sie unter Verwendung von Literalen, Operatoren, Variablen und Funktionen
beliebig komplexe Ausdrücke formulieren!
Konstante
uc
lc
Eine Variablen wie $PI in obigem Beispiel, die einmal einen Wert zugewiesen bekommen und dann nicht mehr verändert wird, kann man übrigens
als Konstante ansehen – und nach ungeschriebenem Programmier-Gesetz
wählt man für Konstanten Bezeichner in GROSSBUCHSTABEN.
Zurück zu den Funktionen. Perl bietet nicht nur Funktionen für numerische Daten, sondern auch für Zeichenketten an. So stellt Perl u. a. die
Zeichenketten-Funktionen uc (upper case, Großbuchstaben) und lc (lower
case, Kleinbuchstaben) zur Verfügung:
Listing 1.12: Umwandlung Gross-/Kleinschreibung
print ( uc ( " In Newsgroups bedeutet Großschreibung , dass ➘
man brüllt .\ n " ) ) ;
print ( lc ( " Kleinschreibung gilt in manchen Kreisen als ➘
modern .\ n " ) )
führt zu
IN NEWSGROUPS BEDEUTET GROßSCHREIBUNG , DASS MAN BR ÜLLT .
kleinschreibung gilt in manchen kreisen als modern .
length
Mittels der Funktion length lässt sich die Länge einer Zeichenkette ermitteln:
print(length("abcdefghijklmnopqrstuvwxyz"), "\n")
ergibt korrekterweise 26 als Ergebnis.
substr
Eine weitere interessante Funktion ist substr, die es erlaubt, aus einer
Zeichenkette beliebige Teilstrings zu extrahieren:
Listing 1.13: Teile aus Zeichenketten extrahieren
$alphabet = " a b c d e f g h i j k l m n o p q r s t u v w x y z " ;
print ( substr ( $alphabet , 0 , 3) . " \ n " ) ;
print ( substr ( $alphabet , 25 , 1) . " \ n " ) ;
print ( substr ( $alphabet , length ( $alphabet ) - 1 , 1) . " \ n " )➘
;
print ( substr ( $alphabet , 13) . " \ n " )
ergibt
abc
1.7. EINGABE VON DER KONSOLE
29
z
z
nopqrstuvwxyz
Beachten Sie, dass die Zählung der Zeichen im String bei null beginnt
– daher liefert der erste Ausdruck abc (die ersten 3 Zeichen ab dem 0ten
Zeichen) und der zweite Ausdruck tatsächlich das z, das in unserem 26 Zeichen langen Beispiel-String ja die Nummer 25 trägt. Der dritte Ausdruck
liefert ebenfalls das letzte Zeichen zurück, da die length-Funktion die Anzahl der Zeichen im $alphabet korrekt zu 26 berechnet. Der letzte Ausdruck schließlich demonstriert, dass bei Weglassen des 3. Argumentes der
substr-Funktion einfach der ganze Rest-String ab dem spezifizierten Zeichen zurückgegeben wird.
Tipp 1.2: Eine Funktion, die auch links kann
Sie können substr auch verwenden, um Teile einer Zeichenkette zu
verändern – verwenden Sie substr einfach links des ZuweisungsOperators, um anzugeben, welchen Teilstring Sie verändern möchten,
und geben Sie rechts des Zuweisungsoperators an, wodurch Sie den
Teilstring ersetzen wollen:
$text = "Hämoglobin";
substr($text, 1, 1) = "ae";
würde den Inhalt von $text zu Haemoglobin ändern.
1.7
Eingabe von der Konsole
Meist wird man ein Programm selten nur zu dem Zweck schreiben, eine
ganz bestimmte Berechnung mit festen Werten vorzunehmen, sondern dem
Benutzer/der Benutzerin die Wahl lassen wollen, welche Daten verarbeitet
werden sollen. Die einfachste Möglichkeit, ein Perl-Programm dazu zu veranlassen, nach der Eingabe von Daten zu verlangen, ist die Verwendung des
<>-Operators:
Listing 1.14: Benutzereingabe
print ( " Bitte eine Zeichenkette eingeben : " ) ;
$string1 = < STDIN >;
print ( " Und eine zweite Zeichenkette : " ) ;
$string2 = < STDIN >;
print ( " Ihre Eingaben :\ n$string1 \ n$string2 \ n " )
<>
30
Filehandle,
Dateihandle
STDIN
STDOUT
STDERR
Standard-Eingabe
Standard-Ausgabe
StandardFehlerkonsole
KAPITEL 1. EINFÜHRUNG
Als Argument erwartet der <>-Operator ein sog. File- oder Dateihandle (von engl. handle, Griff“). Ein Filehandle – STDIN in obigem Beispiel –
”
stellt einen Bezeichner für eine (geöffnete) Datei dar, mit dessen Hilfe man
auf die Inhalte der Datei zugreifen kann – dazu später mehr. Hier sei nur
so viel verraten, dass in Perl gemäß der UNIX-Philosophie Alles ist eine
Datei auch die Standard-Ein- und -Ausgabegeräte (i. d. R. die Konsole) wie
eine Datei behandelt werden – oder vielmehr sogar wie 3 verschiedene Dateien. Die entsprechenden Handles tragen die fest vergebenen Namen STDIN,
STDOUT und STDERR, was für Standard-Eingabe, Standard-Ausgabe und
die Standard-Fehlerkonsole steht. Tatsächlich haben wir STDOUT bisher
schon vielfach implizit benutzt: Der print-Anweisung kann man nämlich
optional als erstes Argument ein Handle derjenigen Datei, in die ausgegeben
werden soll, mit auf den Weg geben – und wenn nichts angegeben wird, wird
einfach die Standardausgabe verwendet. Die beiden Zeilen
print("Hello world!\n")
und
print STDOUT ("Hello world!\n")
sind also von ihrer Bedeutung her identisch. Das Handle STDERR sollte
man verwenden, um Fehlermeldungen auszugeben
print STDERR ("Ooops - da ist was schief gelaufen!\n",
und in STDIN sollte man überhaupt nicht zu schreiben versuchen – es
kann nur, wie in Listing 1.14 auf S. 29, zum Einlesen verwendet werden.
chomp
Beim Einlesen mittels <> gibt es noch einen kleinen Stolperstein zu
berücksichtigen. Der <>-Operator liest Daten nämlich zeilenorientiert ein,
d. h. er liest so lange vom jeweiligen Filehandle (bzw. im Falle von STDIN der
Konsole) ein, bis er auf er ein Zeilenumbruch-Zeichen \n trifft. Sodann gibt
er alle eingelesenen Zeichen einschließlich dem terminalen \n als Zeichenekette zurück. Am abschließenden Zeilenumbruch-Code ist man jedoch
häufig (aber keinesfalls immer!) nicht weiter interessiert. Glücklicherweise
bietet Perl eine einfach Möglichkeit, störende Zeilenumbruch-Zeichen zu entfernen – die chomp-Anweisung:
chomp($eingabe)
Falls die der chomp-Funktion übergebene Zeichenketten-Variable $eingabe
als letztes Zeichen eine Zeilenenumbruch-Sequenz enthält, wird diese entfernt, und $eingabe enthält danach den entsprechend verkürzten String13 .
13
Beachten Sie: chomp ist keine Funktion wie z. B. sin oder length –
chomp($eingabe) liefert den um ein mögliches \n verkürzten String nicht zurück, sondern verändert die String-Variable ggfs. direkt!
1.7. EINGABE VON DER KONSOLE
31
Übungen
1.5 Fügen Sie in Listing 1.14 auf S. 29 chomp-Anweisungen ein, die die
Zeilenumbruch-Codes der Eingaben entfernt und vergleichen Sie
die Ausgabe mit der nicht modifizierten Version des Programms.
Die Eingabe von der Konsole ist übrigens einer der wenigen Fälle, wo es
sinnvoll sein kann, eine in der Behandlung eines skalaren Wertes zwischen
numerisch“ und Zeichenkette“ zu wechseln – wenn nämlich die Eingabe
”
”
eines Zahlenwertes erwartet wird:
Listing 1.15: Zeichenkette als Zahl verwenden
print ( " Bitte eine Zahl eingeben : " ) ;
$num = < STDIN >;
# Anwendung einer Z e i c h e n k e t t e n f u n k t i o n
chomp ( $num ) ;
# Verwendung in numerischem Ausdruck
print ( " 17 + $num = " , 17 + $num , " \ n " ) ;
Leider ist es nicht ganz trivial eine eingegebene Zeichenkette daraufhin
zu überprüfen, ob sie tatsächlich einen gültigen Zahlenwert darstellt; in Kaptitel Kapitel 5 ab S. 79 werden wir jedoch Mittel und Wege kennen lernen,
Eingaben entsprechend zu prüfen.
32
KAPITEL 1. EINFÜHRUNG
Aufgaben
1.1 Schreiben Sie ein Programm, das zwei DNA-Sequenzen von der
Konsole einliest, beide in beiden möglichen Reihenfolgen verknüpft und die Ergebnisse ausgibt! Führen Sie den Benutzer/die
Benutzerin durch die Ausgabe geeigneter Bildschirmmeldungen
durch Ihr Programm (z. B. durch Ausgabe einer kurzen Beschreibung, welche Funktionalität Ihr Programm eigenlich anbietet).
1.2 Schreiben Sie ein Programm, das es dem Benutzer/der Benutzerin erlaubt, aus einer einzugebenden Protein-Sequenz eine Teilsequenz zu extrahieren. Der Benutzer/die Benutzerin soll dabei
die Nummer des ersten und letzten Aminosäurerestes angeben
können (Beachten Sie: die Zählung der Reste beginnt bei eins!).
Erklären Sie auch hier durch Bildschirmausgaben, was Ihr Programm kann bzw. gerade tut/erwartet.
1.3 Im Labor steht man häufig vor der (lästigen...) Aufgabe, bestimmte Volumina von Lösungen definierter Konzentration herzustellen (etwa: 250 mL einer 3-molaren AmmoniumchloridLösung). Schreiben Sie ein Programm, dass bei Angabe des
gewünschten Volumens, der gewünschten Konzentration und der
molaren Masse der gewünschten Substanz die einzuwiegende
Masse ausrechnet. Achten Sie wiederum auf eine einfache Benutzerführung.
Kapitel 2
Verzweigungen und Schleifen
Nach Studium dieses Kapitels können Sie
• den Ablauf eines Programms situationsgerecht automatisch anpassen
• beim Programmieren einige grundlegende Regeln, die die
Übersichtlichkeit und Wartbarkeit von Programmen fördern, beachten
• gleichartige Berechnungen beliebig oft wiederholen lassen, ohne sie entsprechend oft explizit ausschreiben zu müssen
2.1
Kontrollfluss – Die bedingte Verzweigung
Häufig kommt es vor, dass in einem Programm verschiedene Dinge getan
werden müssen, je nachdem, welche Eingabedaten vorliegen. Auch können
verschiedene Zwischenergebnisse der Datenverarbeitung nach verschiedenartiger Weiterverarbeitung verlangen.
Beispiel:
Ein Programm soll zwei Zahlen dividieren. Falls der Benutzer/die Benutzerin jedoch als Divisor 0 eingibt, sollte das Programm eine Warnmeldung
ausgeben, statt die (nicht definierte) Division durch 0 durchzuführen.
33
34
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
Je nachdem, ob bestimmte Bedingungen (Benutzereingabe zulässig, Berechnung ergibt bestimmtes Zwischenergebnis etc.) zutreffen oder nicht, sollen also verschiedene Programmanweisungen befolgt werden. Hierbei übernehmen dann in Abhängigkeit von diesen Bedingungen verschiedene Abschnitte des Programms die Kontrolle über das, was weiter geschieht. Bei
einer solchen bedingungsabhängigen Auswahl von Programmblöcken spricht
man von bedingten Verzweigungen im Kontrollfluss. Anweisungen, die
den Kontrollfluss eines Programms beeinflussen, nennt man dementsprechend Kontrollanweisungen.
bedingte Verzweigung
Kontrollfluss
Kontrollanweisungen
Nun stellt sich die Frage, wie man in einem Programm herausbekommt,
ob eine Bedingung zutriff oder nicht. In den meisten Programmiersprachen
werden Bedingungen als eine besondere Form von Ausdrücken formuliert –
nämlich als sogenannte boolsche Ausdrücke. Im Angedenken an George
Boole 1 , den englischen Mathematiker und Begründer der mathematischen
Logik, werden damit Ausdrücke bezeichnet, deren Auswertungsergebnis entweder als Bestätigung (Ergebnis ist wahr bzw. TRUE) oder Verneinung (Ergebnis ist falsch bzw. FALSE) der durch den Ausdruck formulierten logischen
Aussage interpretiert werden – kurz: ob eine Behauptung wahr oder falsch
ist. Dies lässt sich vielleicht am besten durch ein Beispiel verdeutlichen:
boolscher Ausdruck
Boole, George
Listing 2.1: Division
1
2
3
4
5
6
7
8
9
10
11
12
13
if
Vergleichsoperator
==
print ( " Bitte Dividend eingeben : " ) ;
$dividend = < STDIN >;
chomp ( $dividend ) ;
print ( " Bitte Divisor eingeben : " ) ;
$divisor = < STDIN >;
chomp ( $divisor ) ;
if ( $divisor == 0) {
print ( " Divisor darf nicht 0 sein !\ n " ) ;
}
else {
print ( " $dividend / $divisor = " , $dividend / $divisor , ➘
"\n");
}
print ( " Programm beendet .\ n " ) ;
Die Zeilen 1 – 6 enthalten nichts prinzipiell Neues (Einlesen von Zahlen
von der Konsole). In Zeile 7 jedoch wird die bedingte Verzweigung eingeleitet: if ($divisor == 0) bedeutet tatsächlich nichts anderes als Falls
”
(if) der Divisor gleich (==) 0 ist...“. Der in den (obligatorischen!) Klammern eingeschlossene boolsche Ausdruck $divisor == 0 benutzt hier einen
Vergleichsoperator (nämlich ==) um zu testen, ob die Teilausdrücke an
seiner linken ($divisor) und rechten (0) Seite den gleichen Zahlenwert ergeben. Falls dem so ist – der Divisor also tatsächlich gleich 0 ist –, ergibt der
1
1815 – 1864
2.1. KONTROLLFLUSS – DIE BEDINGTE VERZWEIGUNG
35
Vergleich TRUE, falls nicht FALSE. Falls (if) nun der Vergleich TRUE ergibt,
werden die Anweisungen zwischen dem ersten {...}-Block (Ende Zeile 7 –
Zeile 9), andernfalls (else) die zwischen dem zweiten {...}-Block (Zeile 10 –
12) ausgeführt. Unabhängig davon wird das Programm in jedem Falle nach
Durchlaufen des einen oder des anderen Zweiges in Zeile 13 fortgesetzt.
Generell nennen wir allen Programmcode, der zwischen einem Paar geschweifter Klammern {...} steht, einen Block . In Abhängigkeit von bestimmten Bedingungen (im obigen Beispiel je nachdem, ob der $divisor
null ist oder nicht) werden entweder alle oder keine Anweisungen in einem solchen Block ausgeführt. Diese Zusammengehörigkeit demonstrieren
wir durch eine gemeinsame Einrückung aller Anweisungen, die zu einem
Block gehören.
Außer == (ist gleich) bietet Perl noch eine ganze Reihe weiterer Vergleichsoperatoren an, deren genaue Beschreibung ebenfalls in der bereits
erwähnten perlop-Hilfeseite zu finden ist. Für numerische Vergleiche gibt
es z. B. < (kleiner als), > (größer als), <= (kleiner oder gleich), >= (größer
oder gleich) und != (ungleich). Auch Zeichenketten können mit entsprechenden Operatoren z. B. auf Gleichheit ($string1 eq $string2, von equal ) oder
Ungleichheit ($string1 ne $string2, von not equal ) überprüft werden. (Zeichenketten können auch per gt (greater than), ge (greater or equal ), lt (less
than) und le (less or equal )in Bezug auf ihre relative Größe“ zueinander
”
verglichen werden – wobei hier mit Größe“ ihre lexikalische Ordnung
”
gemeint ist. "zwei" gt "drei" wäre demnach also TRUE.
Der else-Zweig ist übrigens optional; falls er weggelassen wird, fährt die
Programmausführung bei Nichtzutreffen der Bedingung einfach hinter dem
if-Block fort. (All das, und noch viel mehr, können Sie auf der perldocSeite perlsyn nachlesen – dort ist die generelle Syntax von Perl erklärt,
einschließlich aller Flusskontroll-Anweisungen.)
Bedingte Verzweigungen dürfen auch beliebig verschachtelt werden:
Listing 2.2: pq-Formel
1
2
3
4
5
6
7
8
9
10
11
12
print ( " Lösung einer quadratische Gleichung nach der pq -➘
Formel \ n " ) ;
print ( " Bitte p eingeben : " ) ;
$p = < STDIN >;
chomp ( $p ) ;
print ( " Bitte q eingeben : " ) ;
$q = < STDIN >;
chomp ( $q ) ;
$wurzelausdruck = $p **2/4 - $q ;
if ( $wurzelausdruck < 0) {
print ( " Keine Lösung !\ n " ) ;
}
else {
else
Block
Einrückung
<
>
<=
>=
!=
eq
ne
lexikalische Ordnung
36
$wurzel = sqrt ( $wurzelausdruck ) ;
if ( $wurzel == 0) {
print ( " Einzige Lösung : " , - $p /2 , " \ n " ) ;
}
else {
print ( " 1. Lösung : " , - $p /2 + $wurzel , " \ n " ) ;
print ( " 2. Lösung : " , - $p /2 - $wurzel , " \ n " ) ;
}
13
14
15
16
17
18
19
20
21
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
}
Zu Erinnerung:
Definition pq-Formel :
Die Lösung(en) einer quadratischen Gleichung der Form
x2 + px + q = 0
berechnen sich gem.
x1/2
p
=− ±
2
r
p2
−q
4
Das pq-Formel-Programm berechnet – nach Eingabe von p und q –
zunächst den Ausdruck unter der Wurzel (Zeile 8). Falls dieser negativ ist
(Abfrage in Zeile 9) wird das Programm mit der Meldung, dass es keine
Lösung gibt, abgebrochen. Andernfalls (else-Zweig ab Zeile 12) wird die
Wurzel gezogen. Nun kommt in Zeile 14 eine weitere Abfrage: Ist die Wurzel null (Abfrage Zeile 14), verschmelzen beide Lösungen zu einer einzigen
Lösung, die in Zeile 15 berechnet und ausgegeben wird. Falls der Wurzelwert
ungleich null ist (else-Zweig ab Zeile 17), werden schließlich beide Lösungen
angezeigt (Zeilen 18 und 19).
Gelegentlich kommt es vor, dass man eine ganze Reihe von Alternativen
durchtesten möchte. Viele Programmiersprachen erlauben dies über Befehle
wie switch oder case; da diese in Perl fehlen, müsste man sich mit verschachtelten Konstruktionen behelfen, bei denen innerhalb eines else-Zweiges sofort ein neuer if-Zweig begonnen wird, böte Perl nicht die elsif-Anweisung:
elsif
Listing 2.3: Begruessung
1
2
3
4
5
6
7
8
print ( " Begrüßung in 3 Sprachen \ n " ) ;
print ( " Geben Sie ein : d für Deutsch \ n " ) ;
print ( "
f für Französisch \ n " ) ;
print ( "
e für Englisch \ n " ) ;
$sprache = < STDIN >;
chomp ( $sprache ) ;
if ( $sprache eq " d " ) {
print ( " Guten Tag !\ n " ) ;
2.1. KONTROLLFLUSS – DIE BEDINGTE VERZWEIGUNG
9
10
11
12
13
14
15
16
17
18
37
}
elsif ( $sprache eq " f " ) {
print ( " Bon jour !\ n " ) ;
}
elsif ( $sprache eq " e " ) {
print ( " Hi buddy , what ’s up ?\ n " ) ;
}
else {
print ( " Diese Sprache spreche ich nicht !\ n " ) ;
}
Hier werden nacheinander die in den Zeilen 7, 10 und 13 formulierten Bedingungen geprüft, und der Block, dessen Bedingung als erstes für
TRUE befunden wird, wird ausgeführt. Trifft keine der Bedingungen zu, wird
schließlich der (optionale) else-Zweig in Zeile 16 ausgeführt.
Bedingungen können übrigens auch mehrere boolsche Teilausdrücke enthalten, die über logische Operatoren verknüpft werden können2 . Perl
stellt die auch aus der menschlichen Sprache bekannten Verknüpfungen and
(und, kann auch als && geschrieben werden) und or (oder, auch ||) zur
Verfügung, wobei letzterer einem einschließenden Oder entspricht – nicht
dem ausschließendem Entweder-Oder , das in Perl xor heißt (von exclusive or ).
Die möglichen Ergbenisse logischer Verknüpfungen lassen sich recht übersichtlich in sog. Wahrheitstabellen darstellen, wobei A und B für die
beiden zu verknüpfenden Teilaussagen stehen:
Tabelle 2.1: Logisches UND
A
B
A and B
FALSE
TRUE
FALSE
TRUE
FALSE
FALSE
TRUE
TRUE
FALSE
FALSE
FALSE
TRUE
2
Falls Sie sich über die Prioritäten der Operatoren im unklaren sind, zögern Sie nicht,
Klammern zu verwenden!
logische Operatoren
and
&&
or
||
einschließendes Oder
ausschließendes Oder
xor
Wahrheitstabellen
38
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
Tabelle 2.2: Logisches einschließendes ODER
A
B
A or B
FALSE
TRUE
FALSE
TRUE
FALSE
FALSE
TRUE
TRUE
FALSE
TRUE
TRUE
TRUE
Tabelle 2.3: Logisches ausschließendes ODER
not
!
A
B
A xor B
FALSE
TRUE
FALSE
TRUE
FALSE
FALSE
TRUE
TRUE
FALSE
TRUE
TRUE
FALSE
Zusätzlich gibt es noch den Verneinungs-Operator not (oder !), der den
Wahrheitswert des Ausdrucks, auf den er sich bezieht, umkehrt:
$zahl1 > $zahl2
ist demnach gleichbedeutend mit
not ($zahl1 <= $zahl2)
bzw.
!($zahl1 <= $zahl2)
In Form einer Wahrheitstabelle sähe das wie folgt aus:
Tabelle 2.4: Logisches NICHT
unless
A
not A
FALSE
TRUE
TRUE
FALSE
In diesem Zusammenhang sei noch die unless-Anweisung ( nur wenn
”
nicht“, es sei denn“) erwähnt, die quasi eine verneinte if-Abfrage darstellt:
”
statt if (not <Bedingung>) kann man auch unless (<Bedingung>) schreiben
– was letztenendes, wie vieles in Perl, eine Geschmacksfrage ist.
Ein Beispiel für die Verwendung der logischen Operatoren mag der der
Test darstellen, ob eine Zahl $n innerhalb oder außerhalb eines durch die
2.1. KONTROLLFLUSS – DIE BEDINGTE VERZWEIGUNG
39
untere Grenze $lower und die obere Grenze $upper definierten Intervalls
liegt. So würden die (äquivalenten) Perl-Ausdrücke
($n >= $lower) and ($n <= $upper)
($n >= $lower) && ($n <= $upper)
nur dann TRUE ergeben, wenn $n sowohl größer/gleich $lower als auch
kleiner/gleich $upper ist – $n also tatsächlich im Intervall (einschließlich
Intervallgrenzen) liegt. Die (wiederum äquivalenten) Ausdrücke
($n < $lower) or ($n > $upper)
($n < $lower) || ($n > $upper)
hingegen ergeben sofort TRUE, sobald $n kleiner als $lower ist (d. h. $n
kleiner als die untere Intervallgrenze ist) oder $upper übersteigt (also größer
ist als die obere Intervallgrenze).3
Es sei noch vermerkt, dass sich boolsche Ausdrücke in Perl nicht prinzipiell von anderen skalaren (arithmetischen und Zeichenketten-) Ausdrücken
unterscheiden – denn die möglichen Ergebnisse TRUE und FALSE boolscher
Ausdrücke werden tatsächlich durch einen skalaren Wert repräsentiert. Dabei gibt es genau vier verschiedene Werte, die Perl als FALSE interpretiert:
den numerischen Wert 0, den Leerstring "", den Null“string "0" sowie einen
”
speziellen Wert namens undef, der uns hier nicht weiter interessiern soll4 .
Alle anderen Werte – nicht nur der Zahlenwert 1 – werden als TRUE interpretiert, wenn sie in einem boolschen Kontext (z. B. als Bedingung in einer
if-, elsif- oder unless-Anweisung) vorkommen.
Da boolsche Ausdrücke also tatsächlich eben genau das sind – nämlich
Ausdrücke –, kann man das Ergebnis ihrer Auswertung auch in einer skalaren
Variablen speichern:
$divisorOK = ( $divisor != 0) ;
z. B. würde die Variable $divisorOK mit einem TRUE-Wert belegen, falls
die in $divisor gespeicherte Zahl tatsächlich ungleich null ist, andernfalls
mit FALSE. Danach kann $divisorOK an jeder beliebigen Stelle im Programm,
an der ein boolscher Ausdruck erwartet wird, verwendet werden, um zu
testen, ob $divisor ungleich null ist (oder zumindest zum Zeitpunkt der
Zuweisung war):
...
if ( $divisorOK ) {
$quotient = $dividend / $divisor ;
}
3
So ganz äquivalent sind die ausgeschriebenen und die symbolischen Operatoren, genau
genommen, doch nicht, denn letztere haben eine viel höhere Priorität. Im Zweifel schauen
Sie unter perlop nach – oder setzen Sie Klammern!
4
Tatsächlich ist undef derjenige Wert, der von Perl bei boolschen Ausdrücken standardmäßig im FALSE-Fall zurückgegeben wird. In Kapitel 6 wird er uns zudem noch als
Vorgabewert nicht initialisierter Variablen begegnen.
40
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
...
Möchten Sie selbst eine solche boolsch“ Variable mit TRUE oder FALSE
”
belegen, sollten Sie dafür die Zahlenwerte 1 bzw. 0 verwenden.
Übungen
2.1 Schreiben Sie ein Programm, das nach Eingabe des Alters eine Glückwunsch-Meldung ausgibt. Für nullende“ sowie durch
”
25 teilbare (außer dem 25.) Geburtstage soll ein besonderer
Glückwunsch ausgegeben werden, nicht aber für den 100. Geburtstag – dieser soll mit einer noch schwülstigeren Gratulation
bedacht werden. (Hinweis: eine natürliche Zahl n ist genau dann
ohne Rest durch eine andere natürliche Zahl m teilbar, wenn der
Perl-Ausdruck $n % $m null ergibt!)
?
Gelegentlich möchte man in einem Ausdruck entweder einen oder einen
anderen Wert verwenden – je nachdem, ob eine bestimmte Bedingung zutrifft
oder nicht. Eine if-Anweisung erweist sich hier manchmal als Overkill –
einfacher mag gelegentlich die Verwendung des ?-Operators sein:
$zahlEigenschaft = (($zahl % 2) == 0 ? "gerade" : "ungerade")
Je nachdem, ob die in $zahl gespeicherte Zahl ohne Rest durch 2 teilbar ist – das ist in dem boolschen Ausdruck vor dem ? formuliert –, wird
entweder der erste (im Falle von TRUE) oder der (mit einem Doppelpunkt
: abgetrennte) zweite Wert (bei FALSE) übergeben. Dieses Konstrukt sollte
man aber nur bei nicht zu komplexen Ausdrücken einsetzen, da sonst die
Übersichtlichkeit leidet.
2.2
Zuse, Konrad
EVA-Prinzip
Programmierpraxis
Konrad Zuse 5 – der deutsche Computer-Pionier, der 1941 die weltweit
erste funktionsfähige programmgesteuerte Rechenmaschine fertigstellte –
scheute sich in seinen ersten Computer-Entwürfen davor, Verzweigungen
des Kontrollflusses zuzulassen, da er fürchtete, der Programmablauf würde
dadurch zu kompliziert und unvorhersehbar werden. Jetzt, da auch wir prinzipiell beliebig verschachtelte Programme schreiben können, ist auch für uns
der Zeitpunkt gekommen, ein paar Gedanken zum Thema Gute Program”
mierpraxis“ zu verlieren.
In diesem Praktikum werden wir vor allem Programme entwickeln, die
nach dem EVA-Prinzip arbeiten – das heißt, man kann im Ablauf (mehr
5
1910 – 1995
2.2. PROGRAMMIERPRAXIS
41
oder weniger eindeutig) die aufeinanderfolgenden Schritte Eingabe, Verarbeitung und Ausgabe unterscheiden. Dies gilt bei weitem nicht immer –
denken Sie an menügesteuerte Systeme, bei denen der Nutzer/die Nutzerin
immer wieder die Möglichkeit hat, den Programmablauf zu beeinflussen,
oder gar graphische Benutzeroberflächen, wo es völlig unvorhersehbar ist,
wo der nächste Mausklick erfolgen wird. Aber generell ist es eine gute Idee,
die folgenden Ratschläge zu beherzigen, bevor man sich an die Tastatur
setzt.
Tipp 2.1: Erst denken, dann programmieren!
Bevor Sie die erste Zeile Code schreiben, nehmen Sie Papier und Bleistift
und notieren Sie
• welcher Art die Eingabedaten sind, die Ihr Programm verarbeiten
soll (Datentypen, Wertebereiche, Bezeichnungen etc.)
• wie die Daten verarbeitet werden sollen (Berechnungsformeln, Sonderfälle und deren Behandlung ... - kurz: den Algorithmus!)
• wie die Berechnungsergebnisse dargestellt werden sollen (mit Kommentaren versehen, in Tabellenform, vielleicht sogar eine einfache
Graphik?)
Wenn Sie dann zu programmieren beginnen, gibt es einige weitere Dinge
zu beachten, um den Code lesbar zu halten – für andere, aber auch für Sie
selbst. Denn ein Programm ist selten wirklich fertig und wird oft – mitunter
nach Monaten oder Jahren – erweitert, umgebaut und von neu entdeckten
Fehlern bereinigt. Tatsächlich entfallen bei größeren Softwareprojekten auf
die Wartung und Instandhaltung des Systems i. d. R. mehr Kosten als auf
deren Entwicklung! Daher beachten Sie bitte Tipp 2.2 auf S. 42 zu Ihrem
eigenen Wohl und dem künftiger KollegInnen und NachfolgerInnen.
Ein weiterer wichtiger Aspekt ist die Formatierung von Quellcode.
Dem Perl-Interpreter ist es im Prinzip egal, ob und wieviele Leerzeichen und
Zeilenumbrüche Sie in den Quelltext einfügen – solange Perl-Schlüsselwörter
(Anweisungen) nicht unterbrochen und von eigenen Bezeichnern abgesetzt
werden. Die beiden Programmfragmente
if ( $divisor == 0) {
print ( " Divisor darf nicht 0 sein !\ n " ) ;
}
else {
print ( " $dividend / $divisor = " ) ;
print ( $dividend / $divisor , " \ n " ) ;
}
print ( " Programm beendet .\ n " ) ;
QuellcodeFormatierung
42
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
Tipp 2.2: Code sprechen lassen
Sorgen Sie dafür, dass dem Programmcode zu entnehmen ist, was Ihr
Programm eigentlich tut – und wie es das tut!
• Machen Sie Gebrauch von Kommentaren! Leiten Sie logisch zusammenhängende Teile ihres Programms mit einem Kommentar ein,
und kommentieren Sie Schlüsselzeilen, in denen entscheidende Verarbeitungsschritte stattfinden oder Sonderfälle behandelt werden!
• Verwenden Sie sprechende“ Bezeichner! Geben Sie Variablen Na”
men, die etwas bedeuten – also $divisor statt $zahl2 oder $b!
• Gleiches gilt auch für die Namen Ihrer Programme – diese sollten bereits einen Hinweis enthalten, was die Programme leisten!
getsubseq.pl ist sicherlich aussagekräftiger als program17.pl oder
test2.pl.
• Werden zusammengesetzte Wörter als Bezeichner verwendet, sollten
die Wortbestandteile optisch unterscheidbar sein. Üblich ist die Verwendunge des Unterstrichs ($molare_masse) und die Großschreibung
interner Wortanfänge (CamelCase- oder Kamelhöcker-Schreibweise,
$molareMasse).
• Es ist prinzipiell egal, welcher Sprache Sie die Bezeichner entnehmen (Deutsch, Englisch, Kisuaheli...) – aber entscheiden Sie sich für
eine und bleiben Sie dabei! (In diesem Kurs werden wir zunehmend
dazu übergehen, englische Bezeichner zu verwenden – aber niemals
innerhalb eines Programmes deutsche und englische Bezeichner mischen!)
und
if ( $divisor ==0) { print ( " Divisor darf nicht 0 sein !\ n " ) ;}➘
else { print ( " $dividend / $divisor = " ) ; print (➘
$dividend / $divisor , " \ n " ) ;} print ( " Programm beendet .\➘
n");
sind aus der Sicht von Perl also identisch – gewiß aber nicht aus der Sicht
eines/einer menschlichen Betrachters/Betrachterin. Perl erfordert dank seiner zahlreichen syntaktischen Varianten und der Vielzahl von Bedeutungen, die die verschiedensten Sonderzeichen je nach Kontext haben können6 ,
6
Böse Zungen behaupten, bei Perl handele es sich um executable character noise –
also ausführbares Zeichen-Rauschen... Tatsächlich werden in diesem Skript einige in der
Perl-Gemeinde üblichen Konstrukte lediglich kurz vorgestellt, aber nicht weiter verwen-
2.2. PROGRAMMIERPRAXIS
43
besondere Sorgfalt bei der Formatierung des Quellcodes, doch gilt das im
Folgenden gesagt so oder ähnlich für pratisch alle Programmiersprachen.
So hat es sich als sehr hilfreich erwiesen, Anweisungsblöcke – also durch
geschweifte Klammern {...} eingefasste Programmteile – optisch um einige
Spalten einzurücken (wie wir es ja auch bereits getan haben), um ihre
Zusammengehörigkeit zu visualisieren. Solche Blöcke treten ja meistens in
Verbindung mit alternativen Kontrollflüssen auf und werden i. d. R. entweder als Ganzes oder gar nicht ausgeführt. Dementsprechend kann man sie
beim Lesen eines Quellcode-Listings mit Einrückung einfach überspringen,
falls der entsprechende Programmzweig gerade nicht von Interesse ist.
Es haben sich verschiedene Stile für die Einrückung (und andere Aspekte der Quellcode-Formatierung) entwickelt – verbreitet sind z. B. folgende
Einrückungsarten:
if ( $divisor == 0) {
print ( " Divisor darf nicht 0 sein !\ n " ) ;
}
else {
print ( " $dividend / $divisor = " , $dividend / $divisor , ➘
"\n");
}
print ( " Programm beendet .\ n " ) ;
oder
if ( $divisor == 0)
{
print ( " Divisor darf nicht 0 sein !\ n " ) ;
}
else
{
print ( " $dividend / $divisor = " , $dividend / $divisor , ➘
"\n");
}
print ( " Programm beendet .\ n " ) ;
oder auch
det; vielmehr halten wir uns tendenziell eher an Gepflogenheiten, wie sie in vielen anderen
prozeduralen und objektorientierten Programmiersprachen üblich sind, was Ihnen das Erlernen solcher Sprachen vereinfachen wird.
Einrückung
44
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
if ( $divisor == 0)
{
print ( " Divisor darf nicht 0 sein !\ n " ) ;
}
else
{
print ( " $dividend / $divisor = " , $dividend / $divisor ➘
, "\n");
}
print ( " Programm beendet .\ n " ) ;
Die Einrückung selbst kann entweder mit Leerzeichen oder mit Tabulatorzeichen erfolgen – eine Mischung der beiden ist allerdings strikt (wenn
auch nicht ohne weiteres) zu vermeiden, da die Einrückung per Tabulator
– im Gegensatz zur Leerzeichen-Einrückung – von den Einstellungen des
jeweils verwendeten Editors abhängt. Generell sind Leerzeichen daher sicherer bezüglich der systemübergreifenden Lesbarkeit von Quellcode. Deswegen
muss man jedoch nicht unbedingt auf den Gebrauch der Tabulator-Taste verzichten – die meisten Editoren bieten die Option, Tabulatoren automatisch
durch eine entsprechende Anzahl Leerzeichen zu ersetzen (die Präferenzen
reichen hier übrigens von 2 bis ca. 8 Spalten!).
Tipp 2.3: Hohe Quellcodelesbarkeit sicherstellen
• Rücken Sie Anweisungsblöcke ein – möglichst mit Leerzeichen und
immer um eine konstante Anzahl von Spalten!
• Stellen Sie Ihren Editor so ein, dass eine Schriftart mit fester Zeichenbreite verwendet wird (Monospace, Courier o.ä. – so wie hier
Programmcode gesetzt ist), keine Proportionalschrift!
• Verwenden Sie Leerzeilen, um logisch zusammenhängende Programmteile optisch voneinander zu trennen!
• Erhöhen Sie die Lesbarkeit komplizierter Ausdrücke durch die Verwendung von Leerzeichen und zusätzlicher Klammerung!
• Entwickeln Sie ruhig ihren eigenen Stil – aber bleiben Sie sich treu!
debugging
Trotz Einhaltung der guten Ratschläge aus Tipp 2.3 wird selten ein Programm auf Anhieb fehlerfrei funktionieren. Ein Großteil der Programmierarbeit entfällt auf das Suchen und Bereinigen von Programmfehlern – auch
debugging genannt (engl. für entwanzen“). Glücklicherweise merkt der
”
Compiler-Anteil von Perl Fehler in der Perl- Rechtschreibung“ (der Syn”
2.2. PROGRAMMIERPRAXIS
45
tax – daher Syntaxfehler ) schon vor der Programmausführung und gibt
Hinweise bezüglich der Art des Fehlers und die Stelle seines Auftretens. Gleiches gilt auch für Fehler, die erst zur Laufzeit des Programms auftreten (z.
B. versuchte Division durch Null) – hier ist es der Interpreter, der Informationen über Art und Ort des Laufzeitfehlers (runtime error ) ausgibt.
Um Fehler nicht nur zu suchen, sondern auch zu finden, sollten Sie die in
Tipp 2.4 auf S. 45 aufgeführten Hinweise befolgen.
Syntax
Syntaxfehler
Laufzeitfehler
runtime error
Tipp 2.4: Fehler suchen und finden
• Um lediglich die Syntax eines Programms zu überprüfen ohne es
auszuführen, können Sie den Perl-Compiler
mit der Option -c star
ten – also ~$ perl -c mein programm.pl Enter .
• Um es mit Douglas Adams zu sagen: Don’t panic! Schauen Sie
sich Fehlermeldungen genau an und entnehmen Sie ihnen Art und
Quellcode-Zeile des Fehlers!
• Falls Sie beim Compilieren mehrere Fehlermeldungen erhalten, konzentrieren Sie sich auf die erste (und vielleicht noch die zweite)! Ein
Syntaxfehler zieht häufig einen ganzen Schwanz weiterer Fehler nach
sich, die quasi automatisch behoben werden, wenn der auslösende
Fehler korrigiert wird.
• Zum Aufspüren der Gründe für evtl. auftretende Laufzeitfehler
kann es sich als nützlich erweisen, vorübergehend zusätzliche printAnweisungen in das Programm einzubauen, um sich Zwischenergebnisse anzeigen zu lassen.
”
Wer suchet, der findet“ – Matthäus 7, Vers 8
Es mag oft recht praktisch sein, dass Perl bei skalaren Variablen nicht
zwischen numerischen Werten und Zeichenketten unterscheidet – man sagt
auch, Perl sei schwach typisiert. Andere, stark typisierte Sprachen (wie
C, Java, Pascal etc.) unterscheiden meist nicht nur zwischen Zeichenketten
und Zahlenwerten, sondern auch zwischen einzelnen Zeichen (char), Zeichenketten (string), ganzen Zahlen verschiedener Größenordnung (byte,
integer, long...) und realen Zahlen (Fließkommazahlen) verschiedener Genauigkeit (float, double...). Bei einer starken Typisierung kann daher schon
zur Übersetzungszeit festgestellt werden, ob z. B. versehentlich versucht werden soll, mit einer Variablen, die nur für Zeichenketten vorgesehen ist, zu
rechnen. Die schwache Typisierung von Sprachen wie Perl stellt daher eine
häufige Fehlerquelle dar – zumal Perl ja durch seine automatische Konver-
schwache Typisierung
starke Typisierung
46
semantische Fehler
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
tierung zwischen Zahlenwerten und Zeichenketten eine Aufdeckung solcher
Fehler auch zur Laufzeit vereitelt. Solche Fehler, bei denen ein Programm
scheinbar problemlos durchläuft und keine Syntax- oder Laufzeitfehler meldet, aber nicht das tut, was man von ihm erwartet, werden semantische
Fehler genannt – und sind notorisch schwierig zu diagnostizieren. Daher in
Tipp 2.5 auf S. 46 noch ein Appell an Ihre Programmierdisziplin:
Tipp 2.5: Freiwillig typisieren
Verwenden Sie jede Variable nur für jeweils einen Datentyp! Unterscheiden
Sie strikt zwischen Variablen, die für
• numerische Werte
• Zeichenketten
• boolsche Werte, repräsentiert durch die Zahlenwerte 0 (für FALSE)
und 1 (für TRUE)
vorgesehen sind! Diese – im Rahmen von Perl: freiwillige – Typisierung
wird Ihnen viel Ärger ersparen!
Programmtestung
Um sowohl Laufzeit- als auch semantische Fehler möglichst auszuschließen, ist es unabdingbar, ein Programm – wie in Tipp 2.6 beschrieben – zu
testen. Dazu überlegt man sich eine Reihe von sinnvollen Testfällen, die
nicht nur typische Eingabedaten, sondern möglichst auch alle Sonderfälle
abdecken. Das erfolgreiche Absolvieren der Tests ist zwar noch lange keine
Garantie für Fehlerfreiheit, erhöht aber die Wahrscheinlichkeit einer korrekten Implementierung.
Tipp 2.6: Programme testen
Testen Sie Ihr Programm mit sinnvollen Testdaten! Diese sollten
• typische Eingabedaten (möglicherweise zufällig erzeugt)
• Daten, die im Programmablauf eine Sonderbehandlung erfordern
• Extremdaten (kleinste und größte zulässige Eingabe)
• 0 und 1 (bei numerischen Daten) bzw. den Leerstring "" (bei Zeichenketten)
umfassen.
2.3. MEHR KONTROLLFLUSS – SCHLEIFEN
47
Tatsächlich gibt es auch Verfahren, die Korrektheit (im Sinne einer Spezifikation) eines Programms zu beweisen – das dazu nötige Verfahren (Programmverifikation) ist jedoch sehr aufwändig und dementsprechend teuer, so dass es nur in sicherheitskritischen Bereichen angewendet wird, etwa
in der Flugzeug- und Kernreaktortechnik.
2.3
Programmverifikation
Mehr Kontrollfluss – Schleifen
Computer sind nicht nur aufgrund ihrer hohen Rechengeschwindigkeit und
-genauigkeit nützliche Werkzeuge – auch die stoische Gelassenheit, mit der
sie auch die langweiligste Aufgabe immer und immer wieder durchführen,
erweist sich als sehr vorteilhaft. Tatsächlich gibt es zahlreiche Problemstellungen, die durch einen iterativen Algorithmus gelöst werden können – also
einem Algorithmus, der sich der Lösung schrittweise durch Wiederholung der
immer gleichen Berechnung nähert, wobei bei jeder neuen Berechnung das
Ergebnis der vorherigen Berechnung Eingang findet. Aber falls es sich als
nötig erweisen sollte, eine bestimmte Berechnung Tausende von Malen immer wieder durchzuführen, wollen wir sie ja nicht eben so oft explizit im
Quelltext niederlegen! Zumal oft zum Zeitpunkt der Programmerstellung
nicht feststeht, wie oft eine solche Berechnung konkret durchzuführen ist.
Daher erweisen sich Programmschleifen – wie Programmverzweigungen
– häufig als unabdingbare Kontrollstrukturen in Computerprogrammen.
Doch wie bringt man einem Computer bei, eine Berechnung zu wiederholen? Wie im Falle der bedingten Verzweigung sei dies auch hier anhand
eines Beispiels erläutert:
Definition Fakultät:
Die Fakultät n! einer natürlichen Zahl ist definiert als

1
für n = 0
n! = Q
 n i = 1 × 2 × 3... × n für n > 0
i=1
Zur Berechnung der Fakultät brauchen wir also eigentlich nur die Zahlen
1 ... n zu multiplizieren. Praktisch erledigt dies folgendes Programm für uns:
Iteration
Schleifen
48
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
Listing 2.4: Fakultaet, iterativ
1
2
3
4
5
6
7
8
9
10
Zählvariable
while
Schleifenrumpf
print ( " Fakultätsberechnung - bitte eine Zahl n eingeben➘
: ");
$n = < STDIN >;
chomp ( $n ) ;
$fac = 1;
$i = 1;
while ( $i <= $n ) {
$fac = $fac * $i ;
$i = $i + 1;
}
print ( " Die Fakultät von $n ist $fac .\ n " )
In den Zeilen 1 – 3 wird die Zahl n, deren Fakultät berechnet werden
soll, von der Konsole abgefragt und in der Variablen $n gespeichert. In Zeile 4 definieren wir eine Hilfsvariable $fac, die die Zwischenergebnisse der
Berechnung vorhält und mit 1 initialisiert wird. Zusätzlich definieren wir
in Zeile 5 eine Zählvariable $i. Und nun (Zeile 6) beginnt die Schleife: Solange (while) der Wert von $i kleiner oder gleich $n ist, wird der
in den Zeilen 7 – 9 folgende Programmblock (auch Schleifenrumpf genannt) ausgeführt. Hier multiplizieren wir (Zeile 7) das Zwischenergebnis
$fac (das im ersten Durchlauf 1, in jedem weiteren Durchlauf das Produkt
der Zahlen von 1...i − 1 enthält) mit $i – und erhalten so das Produkt der
Zahlen von 1...i. Schließlich wird $i, das die laufende Nummer des aktuellen
Schleifendurchganges enthält, (in Zeile 8) um 1 erhöht, und die Programmausführung springt zurück zur while-Anweisung in Zeile 6, wo die Bedingung
für die Durchführung des Schleifenrumpfes erneut geprüft wird. Erst nach n
Durchgängen wird die Zählvariable $i einen Wert größer als n enthalten –
dann liefert der Bedingungsausdruck FALSE, der Schleifenrumpf-Block wird
übersprungen, und das Ergebnis der Berechnung (das ja in $fac protokolliert
wurde) wird ausgegeben.
Übungen
2.2 Machen Sie sich die Funktionsweise des iterativen Fakultätsberechnungs-Programms klar, indem Sie den Programmablauf anhand des Eingabewertes 3 genau durchspielen.
2.3 Was ist mit dem Sonderfall 0 – liefert das Programm auch hier
das richtige Ergebnis? Begründen Sie Ihr Votum!
Endlosschleife
Falls Sie bei einer while-Schleife eine Bedingung angeben, die immer
TRUE ist – etwa 1 == 1 oder einfach nur 1 –, wird die Schleife niemals beendet werden – wir haben es mit einer so genannten Endlosschleife zu
2.3. MEHR KONTROLLFLUSS – SCHLEIFEN
49
tun. Falls sich ihr Programm also einmal aufhängt“, könnte das daran lie”
gen, dass Sie für eine while-Schleife versehentlich eine sich immer erfüllende
Schleifenbedingung formuliert haben.
Analog zum Anweisungspaar if – unless gibt es in Perl auch ein Pendant
zu while – nämlich die until-Anweisung. Eine Schleife der Art
until
until ( Bedingung ) {
...
}
wird so lange (until eben!) ausgeführt, bis die Bedingung erfüllt und
ist somit äquivalent zu while (not Bedingung). Auch mit until können Sie
(willentlich oder versehentlich) Endlosschleifen produzieren; allerdings darf
hier – im Gegensatz zu while-Endlosschleifen – die Schleifenbedingung niemals erfüllt sein (etwa until (1 == 2) oder until (0)).
In beiden Fällen haben wir es jedoch mit Schleifen mit Eingangsbedingung zu tun – zuerst wird geprüft, ob die Bedingung zutrifft, und nur wenn
dem so ist, dann wird der Schleifenblock betreten. Solche Schleifen werden
u. U. kein einziges Mal ausgeführt, da es ja sein kann, dass die Bedingung
von Anfang an nicht zutrifft; es gibt jedoch auch Schleifenkonstrukte, die
wenigstens einmal durchlaufen werden, da die Bedingungsprüfung erst nach
Durchlaufen des Blocks erfolgt. Solche Schleifen mit Ausgangsbedingung
werden in Perl durch das Schlüsselwort do eingeleitet und kommen wieder
in zwei Varianten daher:
do {
...
} while ( Bedingung )
do {
...
} until ( Bedingung )
Ausgerüstet mit dieser Vielzahl an Kontrollstrukturen, sollte es Ihnen
keine größeren Probleme bereiten, folgende Übungen zu absolvieren:
Übungen
2.4 Schreiben Sie ein Programm, das eine DNA-Sequenz von der
Konsole einliest und dann ihre Basenzusammensetzung (Anzahl
Gs, As, Ts und Cs) bestimmt. (Hinweis: Erinnern Sie sich an die
Zeichenketten-Funktionen length, uc und substr?)
Eingangsbedingung
Ausgangsbedingung
50
KAPITEL 2. VERZWEIGUNGEN UND SCHLEIFEN
Aufgaben
2.1 Ergänzen Sie das Programm aus Aufgabe 1.3 auf S. 32 so, dass
der Benutzer/die Benutzerin wahlweise zunächst gefragt wird, ob
er/sie – wie gehabt – die einzuwiegende Stoffmenge oder (nach
Angabe des Lösemittelvolumens und der eingewogenen Masse)
die Enkonzentration der Lösung berechnen möchte. (Letztere Berechnung sollten Sie dann selbstverständlich auch implementieren...)
2.2 Ergänzen Sie das Programm aus Übung 2.4 so, dass es aus der
Basenzusammensetzung den GC-Gehalt berechnet und als Prozentwert ausgibt.
Kapitel 3
Listen
Nach Studium dieses Kapitels können Sie
• außer einzelnen Zahlen oder Zeichenketten auch ganze Listen von Daten verarbeiten
• weitere Typen von Schleifen verwenden, die u. a. den Umgang mit
Listen erleichtern
3.1
Mit Listen arbeiten
In Kapitel 1 haben wir bereits eine kurze Beschreibung eines Algorithmus
zur Mittelwertsberechnung gesehen. Zur Erinnerung:
Definition Mittelwert:
Der Mittelwert x einer Stichprobe x1 ...xn ist definiert als
Pn
xi
x = i=1
n
Hier haben wir es also mit einem ganzen Satz von gleichnamigen, aber
indizierten Variablen x1 ...xn zu tun, die alle über ihren Index i angesprochen werden können. Um dies in Perl nachzubilden, könnte man auf die Idee
kommen, Code wie den folgenden zu erstellen:
$mw = ( $x1 + $x2 + $x3 + $x4 ...) / $n
51
Indizierung
52
KAPITEL 3. LISTEN
Da aber selten beim Erstellen eines Mittelwert-Programms feststehen
wird, wie viele Werte zu mitteln sein werden, wäre dies eine etwas unflexible
Lösung – zumal es ja durchaus vorkommen kann, dass man den Mittelwert Tausender von Zahlen berechnen möchte, was ziemlich viel QuellcodeSchreibarbeit erfordern würde...
Glücklicherweise bietet Perl die Möglichkeit, eine ganze Liste duchnumerierter Werte in einer einzigen Datenstruktur – genannt Liste oder Feld ,
engl. list oder array – zu speichern und auf die einzelnen Elemente über
ihren Index zuzugreifen. Den grundlegenden Umgang mit Listen mag folgendes Progrämmchen verdeutlichen:
Liste
Feld
Array
Listing 3.1: Listen
1
2
3
4
5
6
7
@liste = (3 , 1 , 4 , 1 , 5) ;
print ( @liste , " \ n " ) ;
print ( " @liste \ n " ) ;
print ( " Die Liste enthält " , scalar ( @liste ) , " Elemente➘
.\ n " ) ;
print ( " Das 3. Element ist $liste [2].\ n " ) ;
$liste [2] = 42;
print ( " Das 3. Element ist jetzt $liste [2].\ n " ) ;
Bei Ausführung erhalten wir
31415
3 1 4 1 5
Die Liste enthält 5 Elemente
Das 3. Element ist 4.
Das 3. Element ist jetzt 42.
Listenvariablen
@
Listen-Konstruktor
()
scalar
Index
[]
Im Gegensatz zu Skalaren, deren Namen mit einem $ beginnen, wird Listenbezeichnern (also den Namen von Listenvariablen) ein @ vorangestellt.
In Zeile 1 sehen wir, wie einer Liste eine Folge von Elementen zugewiesen wird: rechts des Zuweisungs-Operators = werden die einzelnen Elemente
einfach durch Kommata getrennt aufgezählt und das Ganze in runde Klammern – den Listen-Konstruktor () – eingeschlossen. Die Zeilen 2 und
3 demonstrieren, wie Perl Listenvariablen ausgibt: Entweder werden alle
Listenelemente direkt hintereinander gehängt (Zeile 2), oder die Elemente
werden – bei Einschluss in doppelte Anführungszeichen " – durch Leerzeichen getrennt ausgegeben (Zeile 3). Zeile 4 führt die neue Funktion scalar
ein, die (u. a. ) die Anzahl der Elemente einer Liste zurückgibt, und die
restlichen Zeilen schließlich demonstrieren, wie auf einzelne Listenelemente
zugegriffen werden kann – nämlich durch Angabe seiner laufenden Nummer,
auch Index genannt, in eckigen Klammern []. Hierbei beachten Sie bitte
zweierlei:
1. Die Zählung der Listenelemente beginnt bei 0. Das erste Element hat
also den Index 0, das letzte scalar(@liste) - 1
3.1. MIT LISTEN ARBEITEN
53
2. Da Listenelemente Skalare sind, beginnen sie mit einem $ – das 3. Element wird also mit $liste[2] angesprochen, nicht etwa mit @liste[2].
Trotzdem ist $liste[irgendwas] nicht zu verwechseln mit $liste, was
eine eigenständige skalare Variable darstellen würde!
Eine Alternative zur expliziten Angabe aller Elemente einer (literalen)
Liste stellt die Verwendung des Bereichs-Operators .. dar, der beliebig
mit Einzelelementen gemischt und sowohl mit Zahlen als auch Buchstaben
verwendet werden kann:
Bereichs-Operator
..
Listing 3.2: Bereichs-Operator
@liste = (1..3 , " Konstanti " , " n " .. " p " , " el " ) ;
print ( " @liste \ n " ) ;
ergibt
1 2 3 Konstanti n o p el
als Ausgabe.
Wie Sie sehen, können listen beliebige Arten von Skalaren (sowohl Zahlen
als auch Zeichenketten) in beliebiger Mischung enthalten - i. d. R. wird
man es allerdings schwer haben, die gemeinsame Speicherung beider skalarer
Datentypen in einer einzigen Liste zu rechtfertigen ( freiwillige Typisierung“
”
wie auf in Tipp 2.5 auf S. 46 beschrieben).
Außer durch Angabe aller Elemente lassen sich Listen auch aus vorhandenen Teillisten zusammensetzen:
Listing 3.3: Listen aus Listen
@a = ( " Ich " , " bin " , " das " ) ;
@b = ( " Element " ) ;
@c = ( @a , " fünfte " , @b ) ;
print ( " @c \ n " ) ;
ergibt als Ausgabe
Ich bin das fünfte Element
Schließlich lassen sich auch ganze Teillisten, so genannte Slices (engl. für
Scheibe“) aus einer Liste extrahieren. Eine von zwei Möglichkeiten, die Perl
”
hierzu bereitstellt, besteht in der Verwendung des eckigen Klammerpaars
[], das wir schon als Operator für den Zugriff auf einzelne Listenelemente
(also quasi Teillisten der Länge 1) kennen gelernt haben, und das dementsprechend auch Slice-Operator genannt wird. Folgende Fortführung von
Listing 3.3 mag das demonstrieren:
slice
[]
Slice-Operator
54
KAPITEL 3. LISTEN
Listing 3.4: Slice-Operator
@d = @c [0..2 , 4];
print ( " @d \ n " ) ;
liefert
Ich bin das Element
als Ausgabe. Beachten Sie bitte, wie auch hier der Bereichs-Operator ..
zum Einsatz kommen kann.
splice
Die andere Möglichkeit besteht in Verwendung der splice-Funktion, die
es ebenfalls erlaubt, mitten in einer Liste eine beliebige Anzahl Elemente zu
entfernen und zusätzlich die Funktion bietet, bei Bedarf eine neue Teilliste
einzufügen. Dazu folgendes Beispiel:
Listing 3.5: Die splice-Funktion
@a = ( " Edel " , " sei " , " der " , " Mensch , " , " hilfreich " , "➘
und " , " gut ! " ) ;
print ( " \ @a ist : @a \ n " ) ;
@b = ( " Wein , " , " dann " , " schmeckt " , " er " , " auch " ) ;
@c = splice ( @a , 3 , 3 , @b ) ;
print ( " \ @a ist jetzt : @a \ n " ) ;
print ( " Herausgenommen wurden : @c \ n " ) ;
Als Ausgabe erhalten wir:
@a ist : Edel sei der Mensch , hilfreich und gut !
@a ist jetzt : Edel sei der Wein , dann schmeckt er auch ➘
gut !
Herausgenommen wurden : Mensch , hilfreich und
Die splice-Funktion erwartet (bis zu) vier Argumente: Den Namen der
Liste, die verändert werden soll (hier: @a); den Index des ersten Argumentes,
das von der Änderung betroffen sein soll (hier: 3); die Anzahl der aus der
Liste zu entfernenden Elemente (hier ebenfalls 3); und schließlich eine Liste
von Elementen, die anstelle der entfernten in die Liste eingefügt werden
sollen (hier: @b). Wenn das letzte Argument weggelassen wird, werden keine
neuen Elemente eingefügt; wird das dritte Argument gleich 0 gesetzt, werden
keine Elemente entfernt. Als Funktionsergebnis erhält man eine (u. U. leere)
Liste der herausgenommenen Elemente1 .
unshift
push
shift
pop
Es gibt eine Reihe von praktischen Perl-Anweisungen, die es erlauben,
jeweils ein einzelnes Element in eine Liste einzufügen oder herauszuholen.
So lässt sich mit den Anweisungen unshift und push jeweils ein Element
an den Anfang bzw. das Ende der Liste anhängen; die Funktionen shift
und pop hingegen entfernen das erste bzw. letzte Element und erlauben,
1
In Bezug auf die Elemente einer Liste leistet splice also Ähnliches wie substr in
Bezug auf die einzelnen Zeichen einer Zeichenkette, also einem Skalar.
55
3.1. MIT LISTEN ARBEITEN
es in Ausdrücken weiter zu verwenden. Das folgende Diagramm mag das
Zusammenspiel dieser vier Befehle verdeutlichen:
X
unshif t
−−−−−→
←−−−
shif t
( A, B, C ... W )
push
←−−−
Y
−
→
pop
Mit push und pop kommen z. B. in folgendem kleinen Programm zum
Einsatz:
Listing 3.6: Listeninvertierung 1
@liste = ( " eins " , " zwei " , " drei " , " vier " ) ;
@inv = () ;
while ( scalar ( @liste ) > 0) {
$element = pop ( @liste ) ;
push ( @inv , $element ) ;
}
print ( " @inv \ n " ) ;
Übungen
3.1 Überlegen Sie sich, wie das in Listing 3.6 gezeigte Programm
funktioniert. (Wenn Sie möchten, können Sie die Veränderungen,
die die beiden Listen @liste und @inv während des Programmablaufs erfahren, durch Einfügen zusätzlicher print-Anweisungen
verfolgen.) Können Sie den gleichen Effekt durch Verwendung
von shift und unshift erreichen?
Hier sei noch verraten, dass Perl selbst eine Funktion zur Invertierung
der Reihenfolge der Elemente einer Liste bereitstellt: die reverse-Funktion.
Obige Schleife könnte also durch ein einfaches
reverse
@inv = reverse(@liste)
ersetzt werden.
Oft steht man vor dem Problem, irgend welche Daten sortieren zu
müssen – Zahlen nach ihrer Größe oder Zeichenketten nach lexikalischer
Ordnung. Glücklicherweise stellt Perl eine Möglichkeit zur Verfügung, in
Listen gespeicherte Werte mit einem einzigen Befehl zu sortieren: nämlich
mit der sort-Funktion, die die sortierte Liste als Ergebnis zurückgibt:
Sortierung
sort
@sortiert = sort(@unsortiert)
Normalerweise sortiert sort die Listenelemente aufsteigend nach ihrer
lexikalischen Reihenfolge, wonach z. B. 10 vor 2 käme – soll eine andere
Sortierung – etwa eine aufsteigende numerische Sortierung – vorgenom-
lexikalische
Sortierung
numerische
Sortierung
56
KAPITEL 3. LISTEN
men werden, muss dies der sort-Funktion explizit mitgeteilt werden:
@sortierteZahlen = sort {$a <=> $b} (@unsortierteZahlen)
Ordinal-Operator,
numerisch
<=>
Ordinal-Operator,
lexikalisch
cmp
@ARGV
Spezialvariablen
Die beiden (fest vergebenen) Variablen-Bezeichner $a und $b stehen
hierbei für zwei numerische Listenelemente, die mittels des numerischen
Ordinal-Operators <=> auf ihre relative Größe überprüft werden. Genaueres – auch über den standardmäßig von sort verwendeten lexikalischen
Ordinal-Operator cmp – lesen Sie bitte auf der perlfunc- bzw. perlopHilfeseite nach.
Eine ganz besondere Liste stellt @ARGV dar – eine von Perls zahlreichen
Spezialvariablen, die von Perl beim Programmstart automatisch initialisiert werden und Informationen über den Kontext (Betriebssystem, Umgebungsvariablen etc.), unter dem das Programm ausgeführt wird, bereitstellen. @ARGV steht dabei für argument vector – das ist die Liste2 der dem Programm an der Kommandozeile mitgegebenen Argumente (oder Kommandozeilen-Parameter). Nehmen wir als Beispiel ein Progrämmchen namens perlecho.pl,
das (neben der Shebang-Zeile) lediglich aus der einzigen Code-Zeile besteht:
Listing 3.7: Echo in Perl
print ( " @ARGV \ n " )
Bei Aufruf des Programms gemäß
~$ ./perlecho.pl Sprich mir nach! Enter landen die angegebenen Kommandozeilen-Argumente Sprich, mir und
nach! als einzelne (Zeichenketten-)Elemente in @ARGV, d. h. $ARGV[0] wäre
Sprich, $ARGV[1] entspräche mir und $ARGV[2] enthielte nach!. Ähnlich wie
der UNIX-Befehl echo würde perlecho.pl vermittels des print-Befehls demnach die Argumente einfach auf der Konsole ausgeben:
Sprich mir nach !
Natürlich kann man mit den in @ARGV bereitgestellten KommandozeilenArgumenten noch andere Dinge anstellen, als den echo-Befehl zu imitieren
– man könnte z. B. ein umgedrehtes“ Echo erzeugen:
”
Listing 3.8: Reverses Echo in Perl
@rev = reverse ( @ARGV ) ;
print ( " @rev \ n " )
Weitere Informationen über Perl-Spezialvariablen erhalten Sie übrigens
über perldoc perlvar.
2
Ein Vektor ist schließlich auch eine Art Liste von Elementen
3.2. NOCH MEHR KONTROLLFLUSS – NOCH MEHR SCHLEIFEN 57
Übungen
3.2 Schreiben Sie ein Programm zur Mittelwertsberechnung. Dabei
sollen alle zu mittelnden Zahlen als Kommandozeilenparameter
angegeben – also nicht per <STDIN> eingelesen – werden. Geben
Sie eine Warnmeldung aus, falls die Anzahl der übergebenen Zahlen gleich Null ist – schließlich lässt sich aus 0 Zahlen auch kein
Mittelwert berechnen...
Abschließend sei hier noch erwähnt, dass sich durch Voranstellen von $#
an den Namen einer Liste der Index ihres letzten Elementes ermitteln lässt;
$#
@liste = (1..5) ;
print ( " $# liste \ n " ) ;
würde zur Ausgabe von 4 führen (das fünfte Element hat den Index
4). Da die Numerierung von Listenelementen in Perl allerdings willkürlich
geändet werden kann (und zwar durch Setzen der Spezialvariablen ), werden
wir auf die Verwendung von $# verzichten und generell mit der durch scalar
ermittelten Listenlänge arbeiten.
3.2
$[
Noch mehr Kontrollfluss – Noch mehr Schleifen
Bei den in Abschnitt 2.3 ab S. 47 vorgestellten Schleifentypen, die durch
while oder until eingeleitet werden, hatten wir sog. Zählvariablen eingeführt
– wobei wir im Schleifenrumpf selbst dafür Sorge zu tragen hatten, dass die
Zählvariable ihrem Namen auch gerecht wird: wir mussten sie nämlich selbst
bei jedem Durchlauf erhöhen! Nun gibt es zwar viele Fälle, in denen wir gar
keine Zählvariable benötigen; für den Fall, dass aber doch, bietet Perl eine
Art der Schleifenkonstruktion, die es uns abnimmt, uns Gedanken über die
Unterbringung der Zählanweisung zu machen: die for-Anweisung, die dem
mathematischen Für alle i von i gleich 0 bis n...“ nachempfunden ist.
”
Listing 3.9: For-Schleife
for ( $i = 0; $i <= 10; $i = $i + 2) {
print ( " $i \ n " ) ;
}
ergibt
for
58
KAPITEL 3. LISTEN
0
2
4
6
8
10
Schauen wir uns den Klammerausdruck hinter der for-Anweisung etwas
genauer an. Zunächst wird eine Zählvariable (hier $i3 ) vereinbart und gleich
mit einem Startwert versehen. Mit einem Semikolon abgetrennt, folgt die
Schleifenbedingung, die hier als Eingangsbedingung fungiert – solange diese
erfüllt ist (hier also $i kleiner oder gleich 10 ist), wird der Schleifenrumpf
ausgeführt. Schließlich folgt, mit einem weiteren Semikolon abgetrennt, die
Anweisung, die bestimmt, wie $i nach jedem Schleifendurchlauf verändert
werden soll – in diesem Beispiel wird $i um jeweils 2 erhöht. Als Ergebnis
– wie auch in der Ausgabe sichtbar – durchläuft $i alle geraden Zahlen von
0 bis 10.
Zusammengefasst lässt sich die for-Schleife also wie folgt darstellen:
for ( Initialisierung ; Schleifenbedingung ; Zählanweisung➘
) {
...
Schleifenrumpf
...
}
Tipp 3.1: Keine Experimente mit der Schleifenbedingung!
Theoretisch darf sich die Schleifenbedingung einer for-Schleife mit jedem
Schleifendurchlauf ändern (etwa indem der Bedingungsausdruck weitere
Variablen enthält, die im Schleifenrumpf verändert werden) – dies wird
jedoch rasch unübersichtlich und sollte möglichst vermieden werden. Generell werden for-Schleifen vor allem dort eingesetzt, wo die Anzahl der
nötigen Wiederholungen bereits beim Schleifeneintritt feststeht.
Inkrementoperator
++
Dekrementoperator
Perl bietet zwei Operatoren an, die gerade im Zusammenhang mit Schleifen und Zählvariablen, die in Einer schritten verändert werden sollen, recht
praktisch sind: den Inkrementoperator ++ (gleichbedeutend mit $i = $i + 1)
und den Dekrementoperator -- (äquivalent mit $i = $i - 1). Diese können
z.B. gut in for-Schleifen eingesetzt werden:
for ($i = 1; $i < $max; $i++)
-3
Schleifenvariablen werden häufig wie mathematische Indices benannt – also i, j, k, l
etc. Das ist eine unter Programmiererinnen und Programmierern so allgemein verbreitete
Sitte, dass diese doch recht kurzen Namen durchaus als sprechend“ durchgehen können.
”
3.2. NOCH MEHR KONTROLLFLUSS – NOCH MEHR SCHLEIFEN 59
Zudem ist es möglich, eine Operation mit einer Zuweisung zu verbinden, wenn das Ergebnis der Operation sofort wieder derjenigen Variablen
zugewiesen werden soll, auf die der Operator angewendet wird. Statt
$i = $i + 3
kann man nämlich auch
$i += 3
schreiben. Außer mit += funktioniert das auch mit allen anderen zweistelligen Operatoren wie -=, *=, .= etc.
Häufig steht man vor der Aufgabe, alle Elemente einer Liste den gleichen
Verarbeitungsschritten zu unterziehen. Dies lässt sich in Perl recht elegant
mit der foreach-Anweisung (für jedes ...) erledigen:
+=
-=
*=
.=
foreach
Listing 3.10: Listeninvertierung 2
@liste = ( " eins " , " zwei " , " drei " , " vier " ) ;
@inv = () ;
foreach $element ( @liste ) {
unshift ( @inv , $element ) ;
}
print ( " @inv \ n " ) ;
liefert das gleiche Ergebnis wie Listing 3.6 auf S. 55, wurde jedoch mittels
foreach implementiert (und lässt die Ausgangsliste @liste unverändert!).
$element ist dabei der (frei wählbare) Name, unter dem bei den einzelnen
Schleifendurchläufen das jeweilige Listenelement verfügbar gemacht wird
und – wie hier in der unshift-Anweisung – weiterverarbeitet werden kann.
Anstatt sich selbst einen Namen auszudenken, hätte man auch auf eine weitere von Perls Spezialvariablen zugreifen können: $_. In dieser Variablen
wird bei zahlreichen Anweisungen, die einen Skalar zurückgeben, eben jener Rückgabewert gespeichert. Ebenso kann bei vielen Befehlen, die einen
Skalar als Argument erwarten, das Argument weggelassen werden, wobei es
automatisch durch $_ ersetzt wird. Eine weitere Ausprägung der Do what I
mean-Philosophie von Perl – die aber nicht unbedingt zur Übersichtlichkeit
beiträgt. Oder können Sie auf den ersten Blick vorhersagen, was folgendes
Programm macht:
Listing 3.11: Schleife mit Spezialvariablen
@liste = ( " Ene " , " mene " , " muh " , " und " , " raus " , " bist " , ➘
" Du " ) ;
@len = () ;
foreach ( @liste ) {
push ( @len , length ) ;
}
print ( " @len \ n " ) ;
$_
60
KAPITEL 3. LISTEN
Übungen
3.3 Erkunden Sie die Funktionsweise des Programms in Listing 3.11
und verändern Sie es so, dass es wieder gut lesbar ist! (Hinweis:
vielleicht zuerst die Spezialvariable $_ explizit ausschreiben und
dann durch einen sinnvollen Namen ersetzen.)
Es gibt noch ein paar weitere Anweisungen, die den Kontrollfluss insbesondere in Schleifen beeinflussen – hier seien die last- und next-Anweisung
genannt, die eine Schleife komplett (last) bzw. nur den aktuellen Schleifendurchlauf (next) beenden:
last
next
Listing 3.12: Start- und Stoppcodons
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sequenz = " g a t a c g a t g c g t g c a t c a g a t g t c a t g c a g t c a t a a t a c g c t "➘
;
for ( $pos = 0; $pos < ( length ( $sequenz ) - 2) ; $pos = ➘
$pos + 3) {
$triplett = substr ( $sequenz , $pos , 3) ;
if ( $triplett eq " atg " ) {
print ( " *** " ) ;
next ;
}
if (( $triplett eq " tag " ) or
( $triplett eq " taa " ) or
( $triplett eq " tga " ) ) {
print ( " - - -\ n " ) ;
last ;
}
print ( " $triplett " ) ;
}
Dieses Progrämmchen durchsucht alle Tripletts des ersten Leserasters einer DNA-Sequenz (gespeichert in der Zeichenketten-Variablen $sequenz, Zeile 1) nach Start- (atg) und Stopp-Codons (tag, taa und tga). Dazu wird die
Zählvariable $pos der for-Schleife in Dreierschritten erhöht ($pos = $pos + 3),
bis sie die Indexnummer des vorvorletzten Nucleotids überschreitet (dieses
hat den Index length($sequenz) - 3 und steht am Anfang des letzten Tripletts). Das jeweilige Triplett wird in Zeile 3 mittels substring aus der Sequenz ausgelesen und in Zeile 4 daraufhin geprüft, ob es ein Startcodon ist.
Falls ja, wird *** ausgegeben (Zeile 5) und mittels der next-Anweisung in
Zeile 6 mit dem nächsten Schleifendurchlauf weitergemacht. Wird hingegen
ein Stopp-Codon gefunden (Test in Zeilen 8 – 10; beachten Sie, wie hier
der Übersichtlichkeit halber zusätzliche Zeilenumbrüche in den Quellcode
eingebaut wurden!), werden --- sowie ein Zeilenumbruch ausgegeben (Zeile
3.2. NOCH MEHR KONTROLLFLUSS – NOCH MEHR SCHLEIFEN 61
11) und die Schleife wird in Zeile 12 mittels last beendet. Ganz normale“
”
Tripletts werden am Ende des Schleifenrumpfes in Zeile 14 ausgegeben.
last und next können natürlich auch in while- und until-Schleifen ver-
wendet werden.
Schließlich noch ein Hinweis zu verschachtelten Schleifen – also Schleifen,
die innerhalb des Rumpfes einer anderen Schleife liegen: hier beziehen sich
last und next immer auf die direkt umschließende Schleife!
Es sei an dieser Stelle vermerkt, dass Sie mit Listen und Schleifen nun bereits alles Handwerkszeug beisammen haben, um jegliche Problemstellung, die sich überhaupt mit einem Computer bearbeiten lässt, angehen und lösen zu können! In den folgenden Kapiteln geht es nun letztlich nur noch darum, wie verschiedene Dinge (nicht
unerheblich) eleganter und bequemer erledigt werden können.
62
KAPITEL 3. LISTEN
Aufgaben
3.1 Schreiben Sie ein Programm, das eine DNA-Sequenz in eine entsprechende RNA-Sequenz umwandelt (also alle Ts in Us
ändert) und ausgibt. Die Sequenz soll entweder fortlaufend (also nicht durch Leerzeichen unterbrochen!) als (einzelner und
einziger!) Kommandozeilenparameter übergeben werden oder,
falls kein Kommandozeilenparameter angegeben wird, von der
Standard-Eingabe eingelesen werden. Verzichten Sie hier bitte auf jegliche Form der Kommunikation mit dem Benutzer/der Benutzerin (Ausgabe von Erklärungen etc.), da Sie dieses
Programm bei späteren Aufgaben als Baustein für kombinierte Programmaufrufe benötigen werden! Vorsicht: Verwechseln
Sie nicht die einzelnen Basensymbole (Zeichen), aus denen sich
eine Sequenz (eine Zeichenkette) zusammensetzt, mit den einzelnen Kommandozeilen-Parametern (Zeichenketten), die Sie dem
Argument-Vektor (einer Liste) entnehmen können! Schließlich
soll die Eingabe der Sequenz nicht in Form einzelner, durch Leerzeichen getrennter Basensymbole erfolgen...
3.2 Schreiben Sie ein Programm, das zu einer DNA-Sequenz die reverse Sequenz (d. h. die Nucleotide in umgekehrter Reihenfolge)
ermittelt und ausgibt. Für die Datenein- und Ausgabe gilt das
gleiche wie in der vorherigen Aufgabe.
3.3 Schreiben Sie ein Programm, das die zu einer DNA-Sequenz komplementäre Nucleotidfolge ausgibt. Die Ein- und Ausgabe erfolgt
wiederum wie in den beiden vorherigen Aufgaben.
3.4 Nun sollten Sie durch geschickte Anwendung von Pipelines in
der Lage sein, zu einer beliebigen DNA-Sequenz eine passende
Anti-Sense-RNA darstellen zu lassen...
Kapitel 4
Arbeiten mit Dateien
Nach Studium dieses Kapitels können Sie
• von Ihren Programmen aus den Inhalt von Textdateien der Reihe nach
auslesen
• neue Dateien anlegen und mit Daten füllen
• an bereits bestehende Dateien weitere Daten anhängen
• schnell an beliebige Stellen innerhalb von Dateien springen und dort
weiterarbeiten
• verschiedene Eigenschaften von Dateien und Verzeichnissen abfragen
• mit Uhrzeiten und Datumsangaben umgehen
• sich die Funktionalität bereits vorhandener Programme in Ihren eigenen
Programmen zunutze machen
4.1
Lesen aus Dateien
Sie wissen bereits (aus dem Linux-Teil des Kurses), wie Daten auf Massenspeichern in Form von Dateien und Verzeichnissen organisiert werden. Zudem haben Sie gelernt, wie Programme Daten, die Benutzer/Benutzerinnen
an der Kommandozeile oder über die Standardeingabe in den Arbeitsspeicher eingegeben haben, manipulieren können. Nun wird man die häufig recht
umfangreichen Daten, mit denen man es gerade in der Bioinformatik zu tun
hat, nicht unbedingt jedes Mal wieder neu an der Kommandozeile eingeben
63
64
KAPITEL 4. ARBEITEN MIT DATEIEN
wollen, wenn sie von einem Programm bearbeitet werden sollen. Vielmehr
wäre es wünschenswert, könnten sich unsere Programme die nötigen Daten
direkt aus den – vorhandenen oder einfach über das WWW zu beschaffenden
– Dateien holen.
Filehandle,
Dateihandle
open
Wie also kann man innerhalb eines Programms Daten aus einer Datei
einlesen? Nun, in Kapitel 1 hatten wir ja bereits den Begriff des File- oder
Dateihandles kennen gelernt – also eines Bezeichners, mit dem auf die
Inhalte geöffneter Dateien zugegriffen werden kann. Sie kennen bereits die
Bezeichner für die (implizit immer geöffnete) Standard-Eingabe, StandardAusgabe und die Standard-Fehlerkonsole. Ein Filehandle für eine echte“
”
Datei bekommt man mittels der open-Anweisung:
open (MEINE_DATEI, "pfad/zur/datei")
Gelingt das Öffnen der Datei, erhalten wir ein Filehandle (hier: MEINE_DATEI)
auf die Datei mit dem angegebenen Namen zurück. Für Filehandles können
wir jeden beliebigen Bezeichner verwenden (sofern er sich an die auch für
Variablen-Bezeichner gültigen Syntax-Regeln hält, s. Abschnitt 1.5 ab S. 23 –
es hat sich jedoch eingebürgert, für Filehandles Bezeichner in GROSSBUCHSTABEN
zu verwenden. Der Pfad zur Datei selbst kann – wie in der Shell – absolut
oder relativ angegeben werden; in letzterem Falle bezieht sich die Pfadangabe auf dasjenige Verzeichnis, in dem das Perl-Programm ausgeführt
wird. Ein Unterschied zur Pfadangabe an der Kommandozeile besteht darin, dass der open-Befehl (leider) mit dem Kürzel ~ für Ihr Home-Verzeichnis
nichts anfangen kann; wir werden jedoch im Verlauf des Kurses eine einfache
Möglichkeit kennen lernen, wie Sie dennoch in Perl auf Ihr Home-Verzeichnis
Bezug nehmen können.
eof
close
Haben wir eine Datei erfolgreich geöffnet und ein Handle darauf erhalten,
können wir – wie schon von STDIN bekannt – mittels des <>-Operators, den
wir auf das Handle anwenden, einzelne Zeilen daraus auslesen. Die erstmalige
Verwendung von <HANDLE> liefert die erste Zeile, die zweite Verwendung die
zweite Zeile usw. Das können wir so lange fortführen, bis die eof-Funktion
(von end of file, Dateiende), angewendet auf das Handle, TRUE zurückliefert
– dann haben wir das Ende der Datei erreicht, und jeder weitere Leseversuch
würde nur noch den Leerstring "" zurückliefern. Schließlich muss eine Datei
nach Beendigung der Arbeit mittels close auch wieder geschlossen werden1 :
1
Die Datei ecoli-k12.seq enthält die gesamte DNA-Sequenz des Bakteriums Escherichia coli, Stamm K12, und wird Ihnen vom Kursleiter/von der Kursleiterin zur Verfügung
gestellt.
4.1. LESEN AUS DATEIEN
65
Listing 4.1: Lesen aus einer Datei
open ( SEQ_FILE , " / var / tmp / ecoli_k12 . seq " ) ;
until ( eof ( SEQ_FILE ) ) {
$line = < SEQ_FILE >;
print ( $line ) ;
}
close ( SEQ_FILE ) ;
Bei Verwendung einer Listenvariable lassen sich alle Zeilen einer Datei
auf einen Schlag einlesen:
Listing 4.2: Einlesen einer Datei in eine Liste
1
2
3
4
open ( SEQ_FILE , " / var / tmp / ecoli_k12 . seq " ) ;
@lines = < SEQ_FILE >;
close ( SEQ_FILE ) ;
print ( @lines ) ;
Beachten Sie, dass die Datei direkt nach dem Einlesen (Zeile 2) geschlossen werden kann – ihr Inhalt steht zu diesem Zeitpunkt ja bereits in Form
der Liste @lines im Arbeitsspeicher! Dies ist auch der Grund, weshalb dieses Vorgehen nicht immer zur Anwendung kommen kann; wenn nämlich die
Datei zu groß ist, um komplett in den (dem Programm zur Verfügung stehenden) Arbeitsspeicher zu passen, kommt nur eine blockweise Verarbeitung
(wie etwa der einzelnen Zeilen) in Frage.
Interessanterweise scheint der <>-Operator zu merken“ ob er nur eine
”
Zeile aus der Datei oder gleich alle Zeilen zurückliefern soll – je nachdem,
ob sein Rückgabewert einem Skalar oder einer Liste zugeordnet werden soll.
Tatsächlich liefern viele Perl-Funktionen je nachdem, ob sie in einem skalaren oder einem Listen-Kontext stehen, in sinnvoller Weise verschiedene
Dinge zurück – hier schlägt mal wieder die Do what I mean-Philosophie zu.
Falls Sie einer Funktion, die normalerweise eine Liste zurückgeben würde,
einen skalaren Kontext aufzwingen wollen, könne Sie dies mittels scalar
tun – der Funktion, die wir bisher dazu verwendet haben, um die Anzahl
der Elemente einer Liste zu ermitteln. Tatsächlich liefert eine Liste bei der
Verwendung im skalaren Kontext genau diese Information zurück – eine
Konstruktion wie
$n = @liste
ist also äquivalent zu
$n = scalar(@liste)
Der Übersichtlichkeit halber werden wir zukünftig aber bei der Version
mit scalar bleiben um zu verdeutlichen, dass wir an der Anzahl der Elemente interessiert sind und nicht etwa versuchen, einem Skalar eine Liste
zuzordnen!
<>
Skalarer Kontext
Listen-Kontext
66
KAPITEL 4. ARBEITEN MIT DATEIEN
Ein Beispiel für eine Funktion, bei der die explizite Verwendung eines
skalaren Kontextes sinnvoll sein kann, stellt die bereits vorgestellte reverseFunktion dar. Im Listen-Kontext liefert reverse ja die Listenelemente in
umgekehrter Reihenfolge zurück; im skalaren Kontext hingegen verkettet sie
zunächst alle Listenelemente zu einer einzigen Zeichenkette und gibt diese
Zeichenkette dann rückwärts“ zurück. Während also
”
@rev = reverse (" gattaca " , " attac ") ;
print (" @rev \ n ")
zur Ausgabe von
attac gattaca
führt, erhalten wir bei
$rev = reverse (" gattaca " , " attac ") ;
print (" $rev \ n ")
die Ausgabe
cattaacattag
Durch Übergabe einer Zeichenkette – also quasi einer Liste mit nur einem Element – kann man so mittels reverse recht einfach die rückwärts
geschriebene Variante der Zeichenkette erhalten; entscheidend ist nur, dass
reverse im skalaren Kontext aufgerufen wird.
Auch in den Argumentklammern der verschiedenen Perl-Befehle können
verschiedene Kontexte herrschen. Tatsächlich haben wir bereits mehrere
Perl-Befehle kennengelernt, die ihre Argumente als Liste interpretieren. Ein
Paradebeispiel ist der print-Befehl, dem wir bei Auflistung mehrerer, durch
Kommata getrennter Ausgabe-Elemente nichts weiter übergeben haben als
– eine Liste! Daher würde
print(reverse("gattaca"), "\n")
nicht zur Ausgabe der reversen Sequenz acattag führen, da die reverseFunktion sich innerhalb der Argumentklammern von print in einem Listenkontext wiederfindet. Der skalare Kontext ließe sich jedoch z. B. über die
scalar-Funktion erzwingen:
print(scalar(reverse("gattaca")), "\n")
Alternativ würde auch die Verwendung eines Operators, der nur Skalare
verarbeitet, einen skalaren Kontext hervorrufen – wie etwa der Konkatenationsoperator:
print(reverse("gattaca") . "\n")
Ein anderes Beispiel für einen Befehl mit Listenkontext in seinen Argumentklammern ist chomp – auch dieser Befehl verarbeitet eigentlich Listen,
wobei sich seine Wirkung auf alle Listenelemente erstreckt. Haben Sie z.
B. alle Zeilen einer Datei in die Liste @lines eingelesen, können Sie mit
chomp(@lines) auf einen Schlag alle Zeilen von ihrem terminalen Zeilenum-
4.1. LESEN AUS DATEIEN
67
bruch befreien. Genaueres darüber, welche Perl-Anweisungen Listen verarbeiten können, entnehmen Sie bitte der Perl-Hilfe perlfunc. (Tatsächlich
sind Skalare aus Sicht dieser Befehle ein-elementige Listen.)
Wie gesagt, das Öffnen einer Datei gelingt nicht immer. Es gibt vielfältige
mögliche Gründe für das Versagen eines open-Befehls – vielleicht existiert eine Datei mit dem angegebenen Namen gar nicht, oder Sie haben keine Leserechte dafür. Daher sollte man immer überprüfen, ob die open-Anweisung
erfolgreich durchgeführt werden konnte. Hier kann man sich die Tatsache
zunutze machen, dass in Perl eigentlich alle Anweisungen immer auch Funktionen sind und einen Wert zurückliefern. Falls eine Anweisung nicht eine
Funktion im klassischen“ Sinne (wie etwa die mathematischen Funktionsn
”
sin, cos etc., String-Funktionen wie substr, aber auch die oben vorgestellte eof-Funktion) darstellt, liefert sie als Mindestinformation im Falle einer
erfolgreichen Ausführung TRUE bzw. bei Versagen FALSE als Rückgabewert.
Bei vielen Befehlen enthält der Rückgabewert zudem weitere, mehr oder weniger brauchbare Angaben; chomp z. B. gibt – wenn als Funktion verwendet
– die Anzahl der Zeichenketten zurück, die tatsächlich von ihrem terminalen
\n befreit wurden (d. h. 0, also FALSE, falls in der übergebenen Liste keine einzige Zeichenkette mit einem abschließenden Zeilenumbruch enthalten
war).2
Diese Tatsache nun, dass alle Perl-Befehle, als Funktionen verwendet,
zumindest TRUE oder FALSE zurückgeben, kann man sich bei open z. B. wie
folgt zunutze machen:
Listing 4.3: Erfolgscheck beim Oeffnen
if ( open ( SEQ_FILE , " / var / tmp / ecoli_k12 . seq " ) ) {
until ( eof ( SEQ_FILE ) ) {
$line = < SEQ_FILE >;
print ( $line ) ;
}
close ( SEQ_FILE ) ;
}
else {
print ( " Couldn ’t open file !\ n " ) ;
}
Falls eine sinnvolle Fortführung des Programms nach dem Fehlschlagen
einer open-Anweisung ganz und gar unmöglich erscheint, können Sie das
Programm auch mit einer Fehlermeldung abbrechen. Dazu dient die dieAnweisung, die die angegbene Zeichenkette (sowie – falls die Zeichenkette
nicht mit einem Zeilenumbruch endet – die Zeilennummer, in der der Fehler
2
Wie bereits erwähnt, können Sie sich mittels perldoc perlfunc über Syntax, Funktionsweise Rückgabewerte aller Perl-Anweisungen informieren. Dabei werden Sie feststellen, dass Perl nicht unbedingt auf die Klammerung von Funktionsargumenten besteht – in
diesem Kurs werden wir die Klammern behufs besserer Lesbarkeit jedoch immer setzten.
die
68
Exitcode
exit
KAPITEL 4. ARBEITEN MIT DATEIEN
auftrat) auf der Standard-Fehlerkonsole STDERR ausgibt und das Programm
mit einem Exitcode (exit code) beendet. Generell gibt jedes Programm
bei seiner Beendigung einen solchen Exitcode zurück, der dann z. B. von
einem das Programm aufrufenden Shell-Skript weiter ausgewertet werden
kann. Vereinbarungsgemäß bedeutet ein Exitcode von 0, dass das Programm
ordnungsgemäß beendet wurde, wohingegen die Bedeutungen von Exitcodes
> 0 nicht näher festgelegt sind, aber auf jeden Fall das Auftreten eines
Fehlers signalisieren. Generell können Sie Ihr Programm übrigens an jeder
beliebigen Stelle auch mittels der exit-Funktion beenden – dabei können Sie
den Exitcode, mit dem Ihr Programm beendet werden soll, explizit angeben
(exit($some_exit_code)).
Gelegentlich sieht man in Perl-Programmen Ausdrücke der Art
open(FILEHANDLE, "path/to/file") or die("Couldn’t open file")
void-Kontext
Es sei dahingestellt, ob dies eine schöne Konstruktion ist – es funktioniert jedenfalls, weil Perl beliebige Ausdrücke im Programm zulässt, auch
wenn deren Auswertungsergebnis nicht weiterverarbeitet (einer Variablen
zugewiesen, ausgegeben, als Bedingung in einer Schleife eingesetzt...) wird.
Da so ein Ausdruck dann weder in einem skalarem noch in einem ListenKontext steht, sagt man, der Ausdrück stünde in einem void-Kontext (von
engl. void, Leere“). Und da sowohl open als auch die – wie oben erläutert
”
– auch als Funktionen mit Rückgabewert angesehen werden können, dürfen
sie mittels or zu einem boolschen Ausdruck verknüpft werden (der in obigem Beispiel einen void -Kontext hat). Nun ist Perl schlau – und wertet
einen logischen Ausdruck nur so weit aus, bis sein Ergebnis feststeht. Falls
der open-Befehl erfolgreich ist, liefert er TRUE zurück, und damit ist auch
der gesamte Ausdruck schon TRUE – für das einschließende Oder genügt es
ja, dass mindestens einer der Teilausdrücke wahr ist. Schlägt open hingegen
fehl, muss auch der zweite Ausdruck – die die-Anweisung – berücksichtigt
werden, und zu ihrer Auswertung wird sie eben ausgeführt, gibt die Fehlermeldung aus und beendet das Programm.
Ähnlich abenteuerlich, aber durchaus des Öfteren zu beobachten, sind
auch Konstrukte wie das folgende:
die("Couldn’t open file") unless (open(FILEHANDLE, "path/to/file"))
Perl erlaubt es, für jede Anweisung eine – im Quellcode nachgestellte
– Vor bedingung (hier unless (...)) anzugeben, die erfüllt sein muss, bevor die eigentliche – im Quellcode vorangestellte – Anweisung (hier die)
ausgeführt wird. Außer unless können auch if, while und until dafür herangezogen werden – auch hier gilt es jedoch genau zu überlegen, ob die
Lesbarkeit eines Programms unter einer Vielzahl syntaktischer Varianten
nicht eher leiden würde.
4.2. SCHREIBEN IN DATEIEN
69
Tipp 4.1: Ein Blick sagt mehr als tausend Worte
Wann immer Sie – sei es in einer Übungsaufgabe, sei es draußen in der
Realität – vor der Aufgabe stehen, ein Programm zu schreiben, das Textdateien verarbeiten soll, ist es oft hilfreich, sich eine solche Textdatei auch
mal mittels eines Texteditors anzuschauen. Der so gewonnene visuelle Eindruck in den Aufbau der Datei vermag eine zwar genaue, aber meist doch
eher trockene textuelle Beschreibung der Dateistruktur in der Regel mit
etwas mehr Leben zu füllen.
Übungen
4.1 Schreiben Sie ein Programm, das eine DNA-Sequenz aus einer Datei einliest und auf ungültige Zeichen überprüft! (Hinweis: Nicht über das Zeilenumbruch-Zeichen stolpern!) Falls
ungültige Zeichen gefunden werden, soll das Programm mit
einer Fehlermeldung abbrechen und angeben, in welcher Zeile und Spalte der Datei das ungültige Zeichen steht. Zum
Testen Ihres Programms können Sie die beiden Dateien
C3aR-Homo_sapiens.dna.seq und C3aR-Homo_sapiens.dna+x.seq
in Ihrem perl4bio/data-Verzeichnis verwenden; letztere Datei
enthält einen fehlerhaften Basencode.
4.2 Eine Datei (z. B. C3aR.dna.seqs in perl4bio/data) enthalte eine oder mehrerer DNA-Sequenzen, wobei sich jede Sequenz über
mehrere aufeinander folgende Zeilen erstrecken darf und verschiedene Sequenzen durch eine oder mehrere Leerzeilen voneinander getrennt seien. Schreiben Sie ein Programm, das diese Datei
so einliest, dass am Ende alle Sequenzen als einzelne, zusammenhängende Zeichenketten-Elemente in einer (auszugebenden)
Liste stehen!
4.2
Schreiben in Dateien
Nach (von einem Programm) getaner Arbeit möchte man das Arbeitsergebnis häufig wieder in einer Datei ablegen – sei es, um die Ergebnisse einer
weiteren Analyse durch weitere Programme zuzuführen, sei es zum Zwecke
der Archivierung. Um eine Datei zum Schreiben zu öffnen, verwenden wir
wiederum den open-Befehl – der hier in zwei Varianten daherkommt:
open(FILEHANDLE, ">pfad/zur/datei")
und
70
KAPITEL 4. ARBEITEN MIT DATEIEN
open(FILEHANDLE, ">>pfad/zur/datei")
>
Datei, überschreiben
Datei, neu anlegen
>>
an Datei anhängen
Die Semantik dieser beiden Varianten entspricht ihren Pendants aus der
Shell-Welt. Die erste Variante (mit einem vorangestellten >) öffnet eine Datei
zum überschreiben – wobei der vorherige Dateiinhalt verloren geht. Falls
die angegebene Datei noch nicht existiert, wird sie angelegt – so können wir
also auch eine neue Datei anlegen.
Variante 2 hingegen, charakterisiert durch zwei dem Pfad vorangestellte
>> – öffnet eine Datei zum anhängen weiterer Inhalte. Auch hier gilt, dass
die Datei bei Bedarf neu angelegt wird.
Das Schreiben selbst geht wie beim Schreiben in die Standardausgabe
von statten:
print FILEHANDLE ("irgendwas\n")
Denken Sie auch hier daran, eine Zeilenumbruchs-Sequenz an die Stellen
in die Datei zu schreiben, an denen eine Zeile zuende sein soll. Nur dann
können Sie die einzelnen Zeilen später auch wieder zeilenweise mit <> einlesen!
Übungen
4.3 Erweitern Sie eines der Sequenz-Konvertierungs-Programme aus
dem Aufgabenblock des letzten Kapitels so, dass es die EingabeSequenz aus einer Datei (deren Name an der Kommandozeile angegeben wird) einliest und das Ergebnis in eine andere Datei schreibt. Zum Testen können Sie wieder die Datei
C3aR-Homo_sapiens.dna.seq aus perl4bio/data verwenden.
4.3
Navigieren in Dateien
Oft kommt es vor, dass man nur an bestimmten Daten innerhalb sehr großer
Dateien interessiert ist – etwa einem Contig innerhalb einer Datei, die alle
Contigs eines Chromosoms enthält. Selbst wenn man weiß, an welcher Stelle
die gewünschte Information steht, wäre so eine Datei wahrscheinlich zu groß,
um komplett in den Arbeitsspeicher zu passen – Einlesen der Datei in eine
Liste und Zugreifen auf das gewünschte Element fällt daher leider aus. Bleibt
also nur das (langsame) zeilenweise Einlesen, bis man an die gewünschte
Stelle gelangt – oder?
Direktzugriff
Glücklicherweise bietet Perl die Möglichkeit, direkt auf beliebige Stellen
innerhalb einer Datei zuzugreifen. Dabei werden die einzelnen Bytes einer
Datei von 0 an aufsteigend durchnummeriert – da wir uns bisher (und auch
im Folgenden) auf simple ASCII-Textdateien beschränken, bei denen 1 Zei-
4.3. NAVIGIEREN IN DATEIEN
71
chen = 1 Byte gilt, können wir also die einzelnen Zeichen unserer Datei
anhand ihrer laufenden Nummer identifizieren. Die seek-Anweisung erlaubt
es uns, direkt zu einem bestimmten Zeichen zu springen:
seek
seek(FILEHANDLE, 25, 0)
Diese Anweisung würde das 26. (Numerierung beginnt bei 0!) Zeichen
der mittels FILEHANDLE geöffneten Datei anfahren. Die 0 als drittes Argument bedeutet dabei, dass die Zählung vom Anfang der Datei aus beginnt
– eine 1 würde Perl veranlassen, relativ zur letzten angefahrenen Position
aus zu zählen, und 2 führt zu einer Zählung relativ zum Ende der Datei. In
letzterem Falle ergeben natürlich nur negative Positionsangaben einen Sinn
– die übrigens auch bei der Variante mit der 1 verwendet werden können.
Generell merkt sich Perl die aktuelle Position innerhalb einer Datei für jedes offene Filehandle separat – man kann also eine Datei, falls nötig, ruhig
gleichzeitig mehrfach geöffnet haben und mit jedem Handle an einer anderen Stelle arbeiten. Die aktuelle Position lässt sich dabei mit Hilfe der
tell-Funktion ermitteln (tell(FILEHANDLE)).
tell
Um nun eine bestimmte Anzahl Zeichen ab der aktuellen Position einzulesen, bemüht man die read-Anweisung:
read
read(FILEHANDLE, $data, 17)
würde ab der aktuellen Position von FILE_HANDLE genau 17 Zeichen in
die Variable $data einlesen – wobei sich der Positionszeiger des Filehandles
gleichzeitig um 17 Positionen vorwärts bewegen würde.
Um den Geschwindigkeitsunterschied zwischen dem Einlesen der gesamten Datei und dem gezielten Anspringen einzelner Positionen zu ermitteln,
benötigen wir noch eine Möglichkeit der Zeitmessung. Diese bietet die (argumentlose) time-Funktion, die die Systemzeit zum Zeitpunkt ihres Aufrufes
zurückgibt. Systemzeit bedeutet hier die Anzahl der seit dem 01.01.1970,
00:00 Uhr vergangenen Sekunden. Für die Messung einer Zeitdifferenz ist
das ausreichend; um daraus Informationen wie die aktuelle Uhr zeit oder das
Datum zu berechnen, kann man die Funktion localtime verwenden, die eine
Liste zurückgibt:
@timeDate = localtime(time)
bzw. ganz allgemein
@timeDate = localtime($someTime)
Die einzelnen Listenelemente haben dabei folgende Bedeutung:
time
Systemzeit
localtime
72
KAPITEL 4. ARBEITEN MIT DATEIEN
Tabelle 4.1: Rückgabewerte der localtime-Funktion
Element
Bedeutung
$timeDate[0]
Uhrzeit, Sekunden-Anteil
Uhrzeit, Minuten-Anteil
Uhrzeit, Stunden-Anteil
Datum, Tag des Monats
Datum, Monat (Achtung! Zählung von
0..11!)
Datum, Jahr (Achtung! 0 entspricht
1900!)
Datum, Wochentag (0 = Sonntag, 6 =
Samstag)
Tag des Jahres (Achtung! Zählung
beginnt bei 0!)
1 bei Sommerzeit, sonst 0
$timeDate[1]
$timeDate[2]
$timeDate[3]
$timeDate[4]
$timeDate[5]
$timeDate[6]
$timeDate[7]
$timeDate[8]
Statt in einer Liste kann man die Ergebnisse von localtime (und anderen
Funktionen, die eine Liste zurückliefern) auch in einer Liste von skalaren
Variablen auffangen – was vielleicht netter für die weitere Verarbeitung ist:
( $sec , $min , $hour , $dom , $mon , $year , $dow , $doy , $dst➘
) = localtime ( time ) ;
print ( " Es ist $hour Uhr $min Minuten und $sec Sekunden ➘
ME " ,
( $dst ? " S " : " " ) ,
" Z am $dom . " , $mon +1 , " . " , $year +1900 , " \ n " ) ;
Gibt man weniger – sagen wir: n – skalare Variablen an, als die Rückgabeliste von localtime (oder einer anderen Funktion, die eine Liste zurückgibt)
Elemente enthält, werden nur die ersten n dieser Elemente in den entsprechenden Variablen gespeichert und die übrigen verworfen. Um die aktuelle
Uhrzeit auszugeben, könnte man daher auch kurz und knapp folgenden Code
verwenden:
( $sec , $min , $hour ) = localtime ( time ) ;
print ( " Es ist $hour Uhr $min Minuten und $sec Sekunden \➘
n");
Schließlich lässt sich localtime auch im skalaren Kontext verwenden, was
zu einer bereits gemäß den Voreinstellungen des Betriebssystems formatierten Zeit- und Datumsangabe führt:
print ( scalar ( localtime ( time ) ) , " \ n " ) ;
4.4. NOCH MEHR ZU DATEIEN
73
z. B. liefert zum Zeitpunkt, da diese Sätze erstmals geschrieben werden,
Thu Sep 29 12:29:20 2005
als Ausgabe.
Übungen
4.4 Schreiben Sie ein Programm, das den Geschwindigkeitsunterschied beim Auslesen des letzten Zeichens einer mehrere hundert MB großen Datei (etwa hs_chr21.gbk oder gar hs_chr1.gbk)
misst, wenn dieses Zeichen
1. nach Einlesen der gesamten Datei in eine Listen-Variable
2. per direktem Dateizugriff
ermittelt wird. Die erwähnten Dateien enthalten die gesamte, annotierte DNA-Sequenz der menschlichen Chromosomen 21 bzw.
1 im Genbank-Format und werden Ihnen vom Kursleiter/von der
Kursleiterin zur Verfügung gestellt.
4.4
Noch mehr zu Dateien
Um zu überprüfen, ob eine Datei existiert – etwa bevor man ansetzt, sie
zu überschreiben – bietet Perl den -e-Test (von exists). Bei Anwendung
auf einen Skalar, der einen Dateinamen enthält, liefert er TRUE oder FALSE
zurück – je nachdem, ob die spezifizierte Datei existiert:
-e
if ( - e " pfad / zur / datei ") {
die (" Die Datei existiert bereits \ n ") ;
}
Sie kennen bereits sog. Wildcards oder Joker-Zeichen, die in UNIXBefehlen wie ls, cp oder rm zur Anwendung kommen, um ganze Gruppen von
Dateien ähnlichen Namens anzusprechen. Perl bietet die Möglichkeit, eine
Liste aller Dateinamen, die einem Suchmuster mit Wildcards entsprechen,
zu erhalten – die glob-Funktion:
Wildcards
Joker
glob
@matchingFiles = glob("perl4bio/*.pl")
würde z. B. die Namen aller Perl-Dateien im Verzeichnis perl4bio in der
Liste @matchingFiles speichern. Diese können wir nun weiterverwenden, um
z. B. jede dieser Dateien einer bestimmten Prozedur zu unterziehen.
Doch Vorsicht! glob liefert nicht nur die Namen von Dateien im engeren
Sinne, sondern auch die von Verzeichnissen zurück! Um zu überprüfen, ob
sich einer der Listeneinträge auf ein Verzeichnis bezieht, kann man den -d-
-d
74
KAPITEL 4. ARBEITEN MIT DATEIEN
Test (directory) verwenden, der analog dem -e-Test funktioniert:
if ( - d $pfad ) {
print ( " $pfad ist ein Verzeichnis !\ n " ) ;
}
Überhaupt bietet Perl eine ganze Reihe solcher Tests an, die in perlfunc
in der Rubrik -X gleich am Anfang der alphabetischen Aufstellung erläutert
werden.
stat
Um noch mehr über eine Datei zu erfahren, bietet sich die stat-Funktion
– wie time eine Funktion, die eine Liste (diesmal mit 13 Elementen)
zurückgibt:
an3
@fileInfo = stat($pfad)
unlink
Das 8. Elemente dieser Liste (also das mit dem Index 7!) enthält z. B.
die Größe der Datei in Bytes, und das 10. Element gibt den Zeitpunkt der
letzten Änderung der Datei zurück (als Systemzeit – muss also ggfs. mit
localtime noch menschenlesbar“ gemacht werden). Die Bedeutung der an”
deren Elemente können Sie – wie immer – Ihrem treuen Begleiter perlfunc
entnehmen. Dort finden Sie auch Informationen über die zahlreichen weiteren Funktionen, die Perl zur Manipulation von Dateien und Verzeichnissen
bereitstellt, etwa die unlink-Anweisung, die Sie zum Löschen von Dateien
verwenden können:
unlink ( glob ( " perl4bio /*. pl " ) )
z. B. löscht alle Dateien mit der Endung .pl im perl4bio-Unterverzeichnis
des aktuellen Arbeitsverzeichnisses.
$0
A propos Informationen über eine Datei: Gelegentlich möchte man vielleicht von innerhalb eines laufenden Programms erfragen, wie die Programmdatei selbst heißt – dies läßt sich dann mit Hilfe der Perl-Spezialvariablen
$0 erledigen, die die gewünschte Information bereitstellt.
Übungen
4.5 Schreiben Sie ein Programm, das die Gesamtgröße (in kB) aller
in einem (an der Kommandozeile angegebenen) Verzeichnis enthaltenen Dateien ermittelt. Denken Sie daran, Unterverzeichnisse
von der Berechnung auszuschließen!
3
wobei sich einiger der von stat zurückgelieferten Informationen auch über entsprechende -X-Test beschafft werden können – s. perlfunc!
4.5. RECYCLING 1 – AUSFÜHREN EXTERNER PROGRAMME
4.5
75
Recycling 1 – Ausführen externer Programme
Es ist generell eine gute Idee, Vorhandenes, das sich bewährt hat, nicht neu
zu erfinden, sondern einfach zu nutzen. Das gilt auch und im Besonderen
für die Kunst des Programmierens. Wenn es ein Programm gibt, das einen
Teilaspekt Ihres Problems gut und richtig lösen kann, benutzen Sie es! Zumal
eine Verbesserung dieses Programms durch denjenigen/diejenige, der/die
seine Pflege betreibt, dann auch automatisch Ihnen zugute kommt – was
für einer Neuimplementierung durch Sie nicht der Fall wäre. Daher ist es
gut zu wissen, wie man vorhandene Programme von einem eigenen (Perl)Programm aus aufruft. Prinzipiell stehen dazu drei verschiedene Wege offen:
1. system – Diese Anweisung ruft ein externes Programm auf und wartet
auf dessen Beendigung. Zusätzlich speichert system einige Informationen über den Erfolg oder Misserfolg der Programmausführung in
Perls Spezialvariable $?. Ist man z. B. am Exitcode des externen Programms interessiet, muss man die Operation >> 84 auf $? anwenden.
Den Exitcode erhält man also durch $exitCode = $? >> 8 – ein Code von 0 bedeutet auch hier wieder, dass das Programm erfolgreich
beendet wurde.
system
$?
>> 8
Den Namen des aufzurufenden Programms, samt aller nötigen Kommandozeilenparameter, kann man system auf zweierlei Arten mit auf
den Weg geben. Entweder als eine einzige lange Zeichenkette – so, wie
man das Programm auch von der Kommandozeile aus aufrufen würde:
system("pfad/zum/programm arg1 arg2 arg3 ...")
– oder als Liste, wobei das erste Element (Index 0!) den Namen des
Programms enthält und alle weiteren Elemente als Kommandozeilenparameter interpretiert werden:
system("pfad/zum/programm", "arg1", "arg2", "...")
Letztere Variante bewährt sich vor allem dann, wenn der Pfad zum
Programm oder eines der Argumente Leerzeichen enthält – bei Variante 1 wird der übergebene String nämlich intern auch wieder in eine
Liste verwandelt, und dabei wird das Leerzeichen als Trennzeichen
zwischen den Elementen interpretiert!
2. exec – Der Befehl ohne Wiederkehr. Funktioniert im Prinzip genau so
wie system, nur, dass nach Ausführung des externen Programms nicht
wieder zum aufrufenden Perl-Programm zurückgekehrt wird.
exec
3. qx ( quote and execute“) – Diese Funktion führt ein als Zeichenkette
”
qx
4
Dabei handelt es sich um eine bitweise Verschiebung um 8 Stellen nach rechts - falls
Sie an Details über bitweise Operationen interessiet sind, sehen Sie bitte in perlop nach!
76
KAPITEL 4. ARBEITEN MIT DATEIEN
übergebenes Kommando wie system aus und liefert alles zurück, was
das externe Programm an die Standard-Ausgabe liefert. In einem skalaren Kontext erhält man die gesamte Ausgabe als einen langen String,
wobei die einzelnen Ausgabezeilen durch Zeilenumbruch-Zeichen getrennt sind:
$allInOne = qx(pfad/zum/programm arg1 arg2 ...)
Im Listen-Kontext hingegen werden die einzelnen Zeilen als Listenelemente gespeichert, was den Vorteil hat, dass man direkt auf die
einzelnen Zeilen zugreifen kann und sie nicht erst aus einem langen
String extrahieren muss:
@lines = qx(pfad/zum/programm arg1 arg2 ...)
Auch bei qx ist es übrigens möglich, per $? auf den Exitcode des aufgerufenen Programms zuzugreifen.
Übungen
4.6 In den Aufgaben zum letzten Kapitel haben Sie je ein Programm
zur Bestimmung der reversen bzw. komplementären Sequenz einer DNA-Sequenz geschrieben. Schreiben Sie eine Art Menü”
Programm“, das den Benutzer/die Benutzerin zunächst fragt,
welche der durch die beiden Programme bereitgestellten Funktionen er/sie nutzen möchte, erfragen Sie dann die Eingabe-Sequenz
und rufen Sie schließlich das gewünschte Programm auf!
4.5. RECYCLING 1 – AUSFÜHREN EXTERNER PROGRAMME
77
Aufgaben
4.1 Erweitern Sie das Programm aus Übung 4.6 auf S. 76 dergestalt,
dass auch die Möglichkeiten revers-komplementäre Sequenz“
”
und anti-sense-RNA“ angeboten und implementiert sind. Grei”
fen Sie dazu wiederum auf die bereits vorhandenen Programme
zur Sequenzmanipulation zurück!
4.2 Das FASTA-Format ist ein verbreitetes Datenformat zur Speicherung von Nukleinsäure- und Aminosäuresequenzen. Eine
.fasta-Datei (z. B. C3aR.dna.fasta und C3aR.prot.fasta in
perl4bio/data) darf mehrere Sequenzen enthalten, wobei die
einzelnen Einträge wie folgt aufgebaut sind: Jeder Seqenzeintrag
beginnt mit einem >, gefolgt von einem beliebigen Kommentar
(etwa dem Sequenznamen). Die eigentliche Sequenz befindet
sich in der (oder den) folgenden Zeile(n). Ein Sequenzeintrag
endet mit der nächsten Kommentarzeile, einer oder mehreren
Leerzeilen oder dem Dateiende. Das Ganze sieht dann in etwa
so aus:
>C3aR-Macaca_fascicularis
atggcgcctttctctgctgagaccaattcaactgacctac...
tccccagtaattctctccatggtcattctcagccttactt...
...
>C3aR-Cavia_porcellus
atggactcttcctctgctgaaaccaactcaactggcctac...
cccgaaacaattctggccatggccatcctaggcctcactt...
...
>C3aR-Mus_musculus
atggagtctttcgatgctgacaccaattcaactgacctac...
ccccaagacattgcctccatggtcattcttggtctcactt...
...
Schreiben Sie ein Programm, das es Ihnen ermöglicht, eine
Art Inhaltsverzeichnis“ einer FASTA-Datei – eine Liste aller
”
Kommentarzeilen – anzuzeigen.
78
KAPITEL 4. ARBEITEN MIT DATEIEN
Aufgaben
4.3 Nachdem Sie nun ermitteln können, welche Sequenzen in einem
FASTA-Archiv liegen, möchten Sie vielleicht auch eine Sequenz,
die Sie anhand ihres Kommentars identifizieren, aus dem Archiv
extrahieren (auf der Konsole ausgeben) können? Schreiben Sie
dazu ein Programm, dem man per Kommandozeilen-Parameter
mitteilen kann, 1) aus welcher FASTA-Datei Sie 2) welchen Sequenzeintrag extrahieren möchten.
4.4 Schreiben Sie nun ein Programm, das es Ihnen erlaubt, Ihre
eigenen Sequenz-Archive anzulegen. Das Programm soll 3
Kommandozeilenparameter erwarten: 1) eine Zeichenkette, die
entweder die zu archivierende Sequenz repräsentiert oder den
Pfad zu einer Datei darstellt, die die Sequenz enthält; 2) den
Namen der Datei, die das FASTA-Archiv enthält (oder neu
angelegt wird, falls sie noch nicht existiert); 3) einen kurzen
Namen für die Sequenz. Bei Aufruf des Programms nach dem
Muster
~$ seqarch.pl gattaca meine sequenzen.fasta Filmsequenz Enter soll dann entweder der Inhalt der Datei gattaca (sofern es
eine solche Datei gibt) oder eben die Zeichenkette gattaca der
Archivdatei meine_sequenz.fasta als neuer Eintrag mit dem
Kommentar >Filmsequenz hinzugefügt werden. (Falls Sie besonders ehrgeizig sind, können Sie das Programm noch so erweitern,
dass es eine Warnung ausgibt, falls bereits eine Sequenz mit
dem gleichen Namen/Kommentar existiert! Dazu können Sie z.
B. das Programm aus Aufgabe 4.2 (wieder-)verwenden)
Kapitel 5
Reguläre Ausdrücke
Nach Studium dieses Kapitels können Sie
• Klassen von Zeichenketten (Muster) regelbasiert definieren
• innerhalb von Zeichenketten nach diesen Mustern suchen
• mit den diesen Mustern entsprechenden Treffern weiterarbeiten
• anhand von Mustern identifizierte Substrings durch andere Zeichenketten ersetzen
• Zahlen und Zeichenketten vor dem Ausdrucken formatieren
5.1
Teilstring-Suche
Die Bioinformatik beschäftigt sich u. a. intensiv mit Sequenzdaten – mit den
Eigenschaften einzelner Sequenzen, den Gemeinsamkeiten von und Unterschieden zwischen mehreren Sequenzen, der Definition und Identifikation von
Sequenzmotiven et cetera, et cetera. Sequenzen – seien es die Nucleotidsequenzen von DNA- oder RNA-Molekülen, seien es die Aminosäuresequenzen
von Proteinen – werden dabei meist als Zeichenketten behandelt. Hier kommt
es uns sehr zupass, dass Perl von Natur aus“ mächtige Textanalyse- und
”
-manipulationsfunktionen zur Verfügung stellt – während die meisten anderen Sprachen solche Funktionen nur in Form zusätzlicher Sprachmodule
bereitstellen.
79
80
KAPITEL 5. REGULÄRE AUSDRÜCKE
5.1.1
reguläre Ausdrücke
Muster
pattern
m/.../
=~
Gruppierungen und Zeichenklassen
Ein zentrales Element von Perls Textanalyse-Funktionen sind sogenannte
reguläre Ausdrücke. Damit sind Ausdrücke gemeint, mit deren Hilfe man
fast beliebige Zeichenketten-Muster (patterns) definieren kann. Durch Anwendung eines regulären Ausdrucks auf eine Zeichenkette lässt sich feststellen, ob das durch den Ausdruck beschriebene Muster in der Zeichenkette
vorkommt1 . So ein Test wird in Perl durch m/.../ (von match, Treffer,
Übereinstimmung, Entsprechung) eingeleitet und mittels des =~-Operators
auf die zu testende Zeichenkette angewendet. Der Ausdruck
$string =~ m/aus/
i
Modifikatoren
würde z. B. mit Maus, Haus, hausieren, ausgeben (als mögliche Werte von
$string) den Wert TRUE ergeben, da all diese Wörter den Substring aus enthalten. Mit Ausgaben – hingegen bekäme man ein FALSE – m arbeitet nämlich
normalerweise unter Berücksichtigung der Groß- und Kleinschreibung. Dies
ließe sich durch Angabe des i-Modifikators (von case-insensitive) hinter
dem match-Operator beheben:
$string =~ m/aus/i
würde auch auf Ausgaben mit einem TRUE antworten. (Leider greift der
i-Modifikator nicht für Umlaute; auch in einem als groß/kleinschreibungsinsensitiv deklarierten match würden z. B. ä und Ä als ungleich gewertet
werden.)
Oftmals wird man nicht nur eine bestimmte Zeichenfolge, sondern variable Zeichenketten zur Definition von String-Klassen heranziehen wollen.
So wird die Aminosäure Isoleucin z. B. durch die drei möglichen Codons
ATA, ATC und ATT codiert2 . Und um eine DNA-Sequenz auf das Vorkommen
von mindestens einem möglichen Isoleucin-Codon zu testen, könnte man
natürlich alle drei Tests separat dürchführen:3
1
In gewisser Weise verwandt sind die bekannten, in Dateibefehlen verwendeten Joker
oder Wildcards – erlauben doch auch sie, mittels einer kurzer Zeichenfolgen Muster zu
definieren, die jeweils eine ganze Klassen von Dateinamen beschreiben.
2
Bei Nucleotid- oder Aminosäuresequenzen ist es häufig angebracht den i-Modifikator
einzusetzen, da sie meist mehr oder weniger willkürlich in Groß- oder Kleinbuchstaben notiert werden. Es gibt jedoch Ausnahmen – manchmal werden in der Wahl des Schriftsatzes
zusätzliche Informationen codiert, etwa Base gehört / gehört nicht zum Motvi“!
”
3
Hiermit lässt sich freilich nicht so ohne weiteres feststellen, in welchem Leseraster das
potenzielle Isoleucin-Codon gefunden wurde!
5.1. TEILSTRING-SUCHE
81
$containsIle = ( $sequence =~ m / ATA / i ) or
( $sequence =~ m / ATC / i ) or
( $sequence =~ m / ATT / i )
Die (hier als boolsch interpretierte) Variable $containsIle würde TRUE
zugewiesen bekommen, wenn mindestens einer der match-Ausdrücke fündig
wird (or-Verknüpfung!). Kürzer ginge es, indem wir die Alternativen direkt
im regulären Ausdruck angeben und – durch | voneinander getrennt – mit
runden Klammern (...) als eine Gruppe von Alternativen kennzeichnen:
$containsIle = ($sequence =~ m/(ATA|ATC|ATT)/i)
|
(...)
Gruppierung
Hier hat man auch die Möglichkeit, verschieden lange Teilmuster zuzulassen: Der Ausdruck
m/Bio(logie|informatik|gemüse)/
mag sowohl Biologie als auch Bioinformatik als auch Biogemüse.
Noch kürzer ließe sich das obige Isoleucin-Beispiel durch Verwendung
einer Zeichenklassen-Definition lösen:
Zeichenklassen
$containsIle = ($sequence =~ m/AT[ACT]/i)
Alle Zeichen zwischen den eckigen Klammern [...] gelten als gleichwertige Alternativen – wenn also $sequence die Zeichenfolge AT, gefolgt von
entweder A, C oder T, enthält, gilt dies als Treffer.
[...]
Für den Fall, dass man zahlreiche Zeichen als mögliche Treffer zulassen
möchte, erlaubt Perl, diese durch einen Zeichenbereich zu definieren – was
natürlich nur dann Sinn macht, wenn diese Zeichen im ASCII-Alphabet auch
hintereinander liegen:
$name =~ m/[A-H]/
würde auf Crass und Haubrock ansprechen, nicht aber auf Tech. Generell
wird man die Verwendung des Bindestrichs - tatsächlich auf die Bereiche der
Kleinbuchstaben (a-z), Großbuchstaben A-Z und Ziffern 0-9 beschränken –
den übrigen Zeichen liegt keine unmittelbar einsichtige Ordnung zugrunde.
Manchmal ist es einfacher, zu definieren, welche Zeichen man nicht als
Treffer gewertet haben möchte. Dies geschieht mittels des ^-Operators:
-
^
$sequence =~ m/AT[^G]/i
findet z. B. ebenfalls alle Isoleucin-Codons, da ATG ausgeschlossen wird.
Was aber tun, wenn man mittels eines regulären Ausdrucks nach einer
eckigen Klammer ([ oder ]) suchen will? Nach einem ^? Oder nach einem
Schrägstrich /? Diese – wie auch die Zeichen {, }, (, ), $, ., |, *, +, ? und \
– haben in regulären Ausdrücken eine besondere Bedeutung und bedürfen
als Metazeichen einer Sonderbehandlung, wenn sie als einfache Zeichen
verwendet werden sollen: Ihnen muss ein Backslash \ als Aufhebungszeichen
vorangestellt werden. Demnach würde der Ausdruck
m/\/\|\\/
Metazeichen
82
KAPITEL 5. REGULÄRE AUSDRÜCKE
auf die Zeichenfolge /|\ ansprechen.
.
Perl bietet noch einige weitere vordefinierte Zeichen und Zeichenklassen
als Escape-Sequenzen an – so kennen wir ja z. B. bereits das ZeilenumbruchZeichen \n. (Eine Liste der wichtigsten vordefinierten Zeichen und Zeichenklassen, auf die Sie jetzt mal einen Blick werfen sollten, finden Sie im Anhang!). Besonders hervorzuheben ist das Metazeichen ., welches für jedes
beliebige Zeichen (außer dem Zeilenumbruch \n) stehen kann. Sucht man
tatsächlich nach einem Punkt, muss dieser daher zusammen mit einem Aufhebungszeichen verwendet werden (\.) – außer innerhalb von ZeichenklassenDefinitionen, wo er auch ohne Aufhebung als Punkt gewertet wird.
m/G.T/i
findet also außer den Codons GAT (Asparaginsäure), GCT (Alanin), GGT
(Glycin) und GTG (Valin) auch noch Wörter wie gut oder Agathe.
Übrigens dürfen auch (skalare) Variablen innerhalb der Musterdefinition
eines m/.../-Ausdrucks verwendet werden. Der reguläre Ausdruck
m/acetyl-CoA:$acceptor O-acetyltransferase/
würde also je nach aktueller Belegung von $acceptor z. B. die acetyl-CoA:
choline O- acetyltransferase oder die acetyl-CoA:carnitine O-acetyltransferase finden.
Eine kurze Einführung in reguläre Ausdrücke erhalten Sie übrigens auch
per perldoc perlrequick; die perlretut-Seite hingegen ist etwas ausführlicher und perlre etwas formaler. perlreref schließlich stellt eine praktische
Kurzreferenz dar.
Exkurs: Restriktions-Endonucleasen
Restriktions-Endonucleasen sind Enzyme, die DNA-Doppelstränge an bestimmten Positionen durchtrennen. Die Schnittstellen sind durch enzymspezifische Basensqeuenzen definiert; so schneidet das Restriktionsenzym
EcoRI hinter dem G in GAATTC, während z. B. EcoRV genau in der Mitte
von GATATC schneidet. Andere Enzyme sind nicht so genau auf eine bestimmte Erkennungssequenz festgelegt – Bgl I z. B. setzt den Schnitt an
der mit | markierten Stelle in GCCNNNN|NGGC, wobei N für jede beliebige
Base stehen kann. Es gibt noch eine Reihe weiterer mehrdeutiger (ambiguitiver) Nucleotid-Codes, die Sie in Abschnitt B.1 ab S. 197 nachschlagen
können.
5.1. TEILSTRING-SUCHE
83
Übungen
5.1 In der täglichen molekularbiologischen Laborarbeit spielen
Restriktions-Endonucleasen eine wichtige Rolle, z. B. um genomische DNA und Klonierungsvektoren zur Klonierung vorzubereiten. Dementsprechend oft steht man vor der Aufgabe herauszufinden, ob eine längere DNA-Sequenz (z. B. ein zu klonierendes
Gen oder ein Vektor) von einem bestimmten Restriktionsenzym
geschnitten werden wird. Schreiben Sie ein Programm, das es
erlaubt, eine Nucleotid-Sequenz nach einer solchen Subsequenz
zu durchsuchen. Dabei sollen auch mehrdeutige Erkennungsmotive erlaubt sein – also solche, bei denen an manchen Positionen
mehr als ein Basentyp erlaubt ist. Die Angabe des mehrdeutigen
Motivs soll gemäß dem IUPAC-Standard erfolgen (s. Abschnitt
B.1 ab S. 197); zu Übungszwecken reicht es, wenn Sie lediglich
den ambiguitiven Code N berücksichtigen. (Hinweis: Gehen Sie
zunächst das vom Benutzer/der Benutzerin eingegebene Motiv
Zeichen für Zeichen durch und ersetzen Sie ambiguitive Nukleotidcodes durch Zeichenklassendefinitionen. Wenden Sie die dabei
erhaltene Musterdefinition dann auf die Sequenz an.)
5.1.2
Positions- und Wiederholungsangaben
Was tun, wenn man testen möchte, ob eine RNA-Sequenz einen poly-ASchwanz enthält? Zum einen muss man sicherstellen, dass die Suche tatsächlich am Ende der Sequenz (und nicht an beliebiger Stelle) stattfindet.
Das erledigen bestimmte Lokatoren – $
Lokatoren
$sequence =~ m/AAAAA$/i
$
sucht die Zeichenkette AAAAA am Ende von $sequence, während^
^
$sequence =~ m/^AAAAA/i
nur dann einen Treffer meldet, wenn $sequence mit AAAAA beginnt. Achtung – diese Verwendung des ^-Zeichens nicht mit seiner Verwendung in
eckigen Klammern [^Zeichenklasse] verwechseln, wo es die Negation der
Zeichenklasse (Es gelten alle Zeichen, die nicht zur Zeichenklasse gehören!)
bedeutet!
Zum anderen müssen wir angeben, wie viele Wiederholungen eines Musters wir verlangen oder zulassen wollen, damit ein Treffer erfolgt. Dazu stehen verschiedene Quantoren zur Verfügung, die sich jeweils auf das ihnen
vorangehenden Zeichen, Gruppierung oder Zeichenklasse beziehen. Ein ?
Quantoren
?
84
KAPITEL 5. REGULÄRE AUSDRÜCKE
gibt an, dass das vorhergehende Muster optional ist – es darf also fehlen
oder genau einmal vorkommen:
m/Soh?le/
*
würde sowohl Sole als auch Sohle erkennen. Völlige Freiheit bezüglich
der Anzahl der Wiederholungen bietet *:
m/Soh*le/
+
erkennt außer Sole und Sohle auch noch unendlich viele sinnlose Wörter
à la Sohhhhhle. Ein + hingegen fordert, dass das Muster mindestens einmal
vorkommt:
m/Hurra+!/
{n,m}
würde auf Hurra!, aber auch auf Hurraaaaa! ansprechen. Schließlich haben wir noch die Möglichkeit, eine größere Zahl von Wiederholungen genau
zu spezifizieren:
m/[^A]A{5,100}$/i
fordert 5 bis 100 As am Ende der Sequenz.4 Wollen wir uns nicht auf eine
obere Schranke festlegen, können wir diese auch weglassen:
m/A{5,}$/i
verlangt nach mindestens 5 (terminalen) As, während
m/[^A]A{5}$/i
auf genau 5 solcher As bestehen würde.
Auch komplexere Muster als nur einzelne Zeichen oder Zeichenklassen
können wir so auf Wiederholung prüfen – so könnten wir z. B. eine genomische DNA-Sequenz mittels
m/(AGCT){10,}/i
auf zehn- oder mehrfach hintereinander vorkommende Erkennungsstellen
für die Restriktionsendonuclease AluI testen und so das Vorhandensein von
Alu-Inseln feststellen.
Eine andere interessante Anwendung wäre das Erkennen offener Leserahmen in einer DNA Sequenz.
4
Die vorangestellte Zeichenklassen-Definition [^A] – also nicht-A – stellt hier sicher,
dass vor dem poly-A-Schwanz tatsächlich noch eine Nucleotid-Folge mit mindestens einem
anderen Basensymbol steht – sonst würde auch eine Folge von 101 As erkannt werden....
5.1. TEILSTRING-SUCHE
85
Übungen
5.2 Entwickeln Sie einen regulären Ausdruck, der offene Leserahmen
(ORF, von open reading frame) erkennt. Gehen Sie davon aus,
dass ein solcher ORF stets mit einem ATG beginnt, mit einem
Stopp-Codon (TAG, TAA oder TGA) endet und Start- und StoppCodon von mindestens einer Dreiergruppe beliebiger Zeichen getrennt ist.
Tipp 5.1: Zahl oder nicht Zahl?
Mit regulären Ausdrücken können Sie (z. B. bei BenutzerInnen-Eingaben)
überprüfen, ob eine Zeichenkette auch als Zahl interpretiert werden kann:
• Natürliche Zahl oder Null (ohne Vorzeichen/Nachkommastellen):
if ($eingabe =~ m/^\d+$/) {...}
• Ganze Zahl Null (mit optionalem Vorzeichen, aber ohne Nachkommastellen):
if ($eingabe =~ m/^[+-]?\d+$/) {...}
• Reelle Zahl (mit optionalem Vorzeichen/Nachkommastellen, aber
ohne Zehnerexponent); auch Kurzschreibweise ohne Vorkommastellen ist möglich (-.1 statt -0.1):
if ($eingabe =~ m/^[+-]?(\d+(\.\d*)?|\.d+)$/) {...}
• Natürliche Zahl oder Null (mit optionalem Vorzeichen/Nachkommastellen/Zehnerexponent):
if ($eingabe =~ m/^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/)
{...}
Bei Bedarf könnten Sie (wie?) zusätzlich noch beliebig viele Leerzeichen
vor und/oder nach dem eigentlichen Zahlen-String zulassen.
86
KAPITEL 5. REGULÄRE AUSDRÜCKE
5.2
Das Suchergebnis weiterverwenden
Ihr Suchausdruck für offene Leserahmen müsste ungefähr wie folgt aussehen:
m/ATG(...)+(TAG|TAA|TGA)/i
Erklärung: Mit diesem Ausdruck suchen wir (groß- und kleinschreibungsunabhängig, da i) nach der Zeichenfolge ATG, gefolgt von einem oder
mehreren (+) Dreiergruppen beliebiger Zeichen, ((...)), die mit einer der
drei Zeichengruppen TAG, TAA und TGA abgeschlossen wird.
(...)
Schön, damit können wir nun feststellen, ob unsere Sequenz einen ORF
enthält. Falls dem so ist, interessiert uns aber auch die Sequenz des Treffers!
Glücklicherweise kann man auf den Teilstring, der beim matching gefunden wurde, zugreifen – und zwar durch Einklammerung des interessierenden
Bereichs des regulären Ausdrucks:
$sequence =~ m/(ATG(...)+)(TAG|TAA|TGA)/i
z. B. würde das ATG sowie alle weiteren Nucleotide bis zum Beginn
des Stopp-Codons zurückgeben. (Haben Sie die zusätzlichen Klammern bemerkt?) Zurück wohin, ist die Frage – und die Antwort lautet: in eine weitere
von Perls Spezial-Variablen!
$sequence =~ m /( ATG (...) +) ( TAG | TAA | TGA ) / i ;
print ( " ORF : $1 \ n " ) ;
$1
Der match wird also in der Spezialvariablen $1 abgelegt. Der Name dieser Variablen legt nahe, dass es noch weitere ihrer Art geben könnte – und
dem ist tatsächlich so: Es lassen sich bis zu 9 Teiltreffer in $1 bis $9 ablegen. Wollten wir z. B. gleichzeitig zur Sequenz des ORFs noch erfragen,
von welchem der drei (durch (TAG|TAA|TGA) gruppierten) möglichen StoppCodons er beendet wird, könnten wir dies ebenfalls in einer Spezialvariable
nachschauen:
$sequence =~ m /( ATG (...) +) ( TAG | TAA | TGA ) / i ;
print ( " ORF : $1 \ n " ) ;
print ( " Stopp : $3 \ n " ) ;
Nanu – wieso steht das Stopp-Codon in $3 und nicht in $2? Weil auch
die Gruppierung (...)+ den von ihr gefundenen Teilstring zurückgibt5 und
– da sie das zweite geklammerte Teilmuster darstellt – ihn in $2 ablegt.
Wie gesagt, auch bei Klammerung erhalten wir im skalaren Kontext
lediglich eine boolsche Rückmeldung, darüber, ob ein Treffer erfolgt ist oder
nicht. Weisen wir das Ergebnis eines matches hingegen einer Listenvariable
zu, enthält diese der Reihe nach die Inhalte der Spezialvariablen $1 bis $9 –
und weitere Treffer, falls man sie angefordert hat:
5
Genau genommen gibt sie ihren letzten Treffer zurück – aber dieses Detail soll uns
hier nicht weiter interessieren.
5.2. DAS SUCHERGEBNIS WEITERVERWENDEN
87
@orfData = ( $sequence =~ m /( ATG (...) +) ( TAG | TAA | TGA ) / i ) ;
print ( " ORF : " , $orfData [0] , " \ n " ) ;
print ( " Stopp : " , $orfData [2] , " \ n " ) ;
Übungen
5.3 Schreiben Sie ein Programm, das eine DNA-Sequenz nach einer
vom Benutzer/der Benutzerin wählbaren Nucleotidfolge durchsucht und im Falle eines Treffers die den Treffer links und rechts
flankierenden 5 Basen zurückgibt.
Interessant ist noch die Frage, was wir erhalten, falls sie Sequenz mehrere
mögliche Treffer enthält, etwa wie im folgenden schematisch dargestellten
Falle:
...ATG ←300 Basen→ TAG ←90 Basen→ ATG ←240 Basen→ TAA...
Nun, Perl benimmt sich hier ausgesprochen gierig (greedy) und gibt die
längste Teilzeichenkette zurück, die sich, beginnend beim ersten ATG, mit
dem variablen Teil der Musterdefinition vereinbaren lässt – und die reicht in
unserem Falle bis zum letzten Stoppcodon (hier TAA). Wollen wir hingegen
den kürzesten möglichen Treffer haben, müssen wir den Quantor mit einem
angehängten ? entsprechend einstellen:
$sequence =~ m/(ATG(...)+?)(TAG|TAA|TGA)/i
liefert den die kürzeste Version des ersten möglichen Treffers in der Sequenz zurück – und der endet bereits bei TAG. Man beachte hierbei, dass dies
nicht bedeutet, dass der kürzeste mögliche Treffer überhaupt in der Sequenz
gefunden wird – das wäre nämlich der ORF vom zweiten ATG bis zum TAA!
Um alle ORFs zu erhalten, müssen wir m mittels des g-Modifikators in
den sog. globalen Modus zwingen. Dies hat zur Folge, dass Perl sich bei
jeder Anwendung des Match-Operators merkt“, wo in der Zeichenkette der
”
Treffer jeweils war und bei erneuter Anwendung des selben Ausdrucks hinter
dem zuletzt gefundenen Treffer weitersucht. Falls kein weiterer Treffer mehr
gefunden werden, gibt m FALSE zurück, sonst TRUE.
g
Wo der jeweilige Treffer endete, können wir mittels der pos-Funktion
erfragen. Doch Vorsicht: pos bezieht sich auch immer auf den letzten Treffer – bei mehreren Gruppierungs-Klammern (...) auf die jeweils letzte im
Muster!
pos
88
KAPITEL 5. REGULÄRE AUSDRÜCKE
Listing 5.1: Alle ORFs finden
print ( " Suche nach ORFs .\ n " ) ;
print ( " Bitte Sequenz eingeben : " ) ;
$seq = < STDIN >;
chomp ( $seq ) ;
while ( $seq =~ m /( ATG (...) +?) ( TAG | TAA | TGA ) / ig ) {
print ( " ORF : $1 \ n " ) ;
print ( " bei " , pos ( $seq ) - length ( $1 . $3 ) + 1 , " \ n " ) ;
}
Zur Berechnung der Startposition des ORF erfragen wir bei jedem Schleifendurchlauf mit pos($sequence), wo der aktuelle match zuende war (also
nach dem Stopp-Codon) und subtrahieren davon die gemeinsame Länge des
ORFs und des Stopp-Codons (length($1.$3) – zur Erinnerung: . verkettet zwei Zeichenketten!). Schließlich addieren wir noch 1, um der unterschiedlichen Zählweise von Perl (erstes Zeichen hat Nummer 0) und Molekularbiologen/Molekularbiologinnen (erste Base hat Nummer 1) gerecht
zu werden. Beachten Sie auch, dass wir hier weiterhin (durch Verwendung
des ?-Quantors) nach kürzesten ORFs suchen! Sonst würde Perl (wenn
überhaupt) nur einen einzigen match finden, nämlich den längsten Substring
der Sequenz, der mit einem Startcodon beginnt und mit einem Stoppcodon
im gleichen Leserahmen endet – ungeachtet möglicher weiterer Start- und
Stoppcodons, die dazwischen liegen könnten!
Übungen
5.4 Erweitern Sie Listing 5.1 so, dass die auf ORFs zu untersuchende Sequenz aus einer wählbaren Datei eingelesen
wird und der/die BenutzerIn eine Mindestlänge (in Codons)
für die zu suchenden ORFs festlegen kann. Geben Sie zudem für jeden gefundenen ORF aus, in welchem der drei
möglichen Leserahmen (1, 2 oder 3) relativ zum Beginn der Gesamtsequenz er gefunden wurde. (Als Testsequenz können Sie
~/perl4bio/data/C3aR-Homo_sapiens.cdna.seq verwenden; )
5.3. SUCHEN UND ERSETZEN
5.3
89
Suchen und ersetzen
Außer dem m/.../-Operator, der sozusagen das (extrem leistungsfähige)
Pendant zu der bekannten Suchen-Funktion in zahlreichen Editoren und
Textverarbeitungssystemen darstellt, bietet Perl mit dem s/.../.../-Operator (von substitute, ersetzen) auch das entsprechende Gegenstück zu Suchen und Ersetzen an. Mit siner Hilfe könnten wir beispielweise mit
s/.../.../
$tier = s/Wild/Haus/
ein Wildschwein zu einem Hausschwein und eine Wildgans zu einer Hausgans
domestizieren. Das, was zwischen den beiden ersten Schrägstrichen steht,
kennen wir bereits – hier kann alles auftauchen, was wir schon von m her
kennen. Zwischen dem zweiten und dritten / können wir nun angegben,
wodurch wir den gefundenen match ersetzen wollen.
Bei globalem Suchen und Ersetzen durch Verwendung des g-Modifikators
gibt s übrigens die Anzahl der vorgenommenen Ersetzungen zurück. Weiterhin steht dem dem s-Operator außer g z. B. auch noch der i-Modifikator
zur Verfügung, der die Suche – wie gehabt – groß- bzw. kleinschreibungsunabhängig macht.
Übungen
5.5 Übersetzen Sie mittels der s-Funktion eine DNA-Sequenz in die
entsprechende RNA-Sequenz.
Sollen nur einzelne Zeichen (statt komplexer Muster) durch andere einzelne Zeichen ersetzt werden, bietet sich der tr/.../.../-Operator (von
translate, übersetzen) an. Damit ließen sich z. B. gleichzeitig alle backslashes
\ in normale“ Schrägstriche / und alle Semikolons in Doppelpunkte verwan”
deln – was z. B. bei der Umwandlung von Pfadlisten aus der Windows-Welt
(\erster\pfad;\zweiter\pfad;\dritter\pfad) in UNIX-typische Pfadlisten
(/erster/pfad:/zweiter/pfad:/dritter/pfad) Verwendung finden könnte:
$path =~ tr/\\;/\/:/
Beachten Sie, dass tr keine der vordefinierten Zeichenklassen (wie \s, \d
etc.) versteht und wirklich nur eine Zeichen-zu-Zeichen-Übersetzung gestattet! Da tr allerdings die Anzahl der gefundenen Treffer zurückgibt, kann
es prima verwendet werden, um die Anzahl bestimmter Zeichen in einer
Zeichenkette zu bestimmen:
$anzahlC = ($sequence =~ tr/C/C/)
Beachten Sie weiterhin, dass tr keinen i-Modifikator kennt und daher
groß- bzw. kleinschreibungssensitiv ist! Um wirklich alle Cytosin-Symbole in
tr/.../.../
90
KAPITEL 5. REGULÄRE AUSDRÜCKE
einer DNA-Sequenz mittels tr sicher zu erfassen, müssten wir also sowohl
nach kleinen als auch nach großen Cs suchen:
$anzahlC = ($sequence =~ tr/cC/cC/)
Da auch s die Anzahl vorgenommener Ersetzungen zurückgibt, kann
auch dieser Operator zum Zählen eingesetzt werden – und zwar im Gegensatz zu tr auch von (nicht überlappenden) komplexeren Mustern statt nur
einzelner Zeichen. So ließe sich mittels
$anzahlATG = ($sequenz =~ s/ATG/ATG/gi)
die Anzahl der in einer DNA-Sequenz vorhandenen Start-Codons ermitteln – wobei diese nach der Zählung allerdings (ungeachtet ihrer ursprünglichen Schreibweise) allesamt in Großschreibung vorliegen würden.
Solche – meist unbeabsichtigten – Änderungen am Ausgangstext lassen sich
vermeiden, wenn man sich die Tatsache zunutze macht, dass im hinteren“
”
Teil von s – also dort, wo angegeben wird, wodurch ersetzt werden soll – die
Spezialvariablen $1 bis $9 sofort zur Verfügung stehen und bei der Ersetzung berücksichtigt werden können. So wird in folgendem Beispiel, in dem
die Anzahl in der Sequenz vorkommender Stopp-Codons ermittelt wird, jeder Treffer durch exakt sich selbst ersetzt – die Sequenz also nicht verändert:
$anzahlATG = ($sequenz =~ s/(TAG|TAA|TGA)/$1/gi)
e
Ein weiterer nützlicher Modifikator, der für s zur Verfügung steht, ist e
(von execute). Bei dessen Angabe wird das Substitut nicht als einzusetzende
Zeichenkette interpretiert, sondern als auszuwertender Perl-Ausdruck:
0,$seq =~ s/(A1)/"<-".length($1)."xA->"/gie
Hier wird jede (g) Folge von 10 oder mehr As (groß/kleinschreibungsunabhängig – i) durch eine Zeichenfolge ersetzt, die entsteht, wenn das Stück
Perl-Code zwischen dem zweiten und dritten / ausgeführt wird. Überlegen
Sie mal, was so aus einem Poly-A-Schwanz von, sagen wir: 150 Basen Länge
werden würde!
Übungen
5.6 Wieder einmal ist ein Programm gefragt, das eine DNA-Sequenz
in ihre komplementäre Sequenz verwandelt – und zwar unter Verwendung eines der in diesem Abschnitt vorgestellten Werkzeuge
zur Textmanipulation!
5.4
grep
Weitere Funktionen zur Zeichenketten-Manipulation
Mit grep sei hier noch eine gelegentlich ganz nützliche Funktion vorgestellt,
5.4. WEITERE FUNKTIONEN ZUR ZEICHENKETTEN-MANIPULATION91
die eine Liste daraufhin untersucht, welche Ihrer Elemente eine beliebige
Bedingung – etwa einem regulären Ausdruck zu entsprechen – erfüllen und
diese in einer neuen Liste zurückgibt:
@descr = grep(/^>.+/, @lines)
zum Beispiel würde alle jene in @lines gespeicherten Zeichenketten in
@descr kopieren, die am Anfang (^) ein > aufweisen, gefolgt von mindestens
einem (+) beliebigen Zeichen (.). Mit diesem Einzeiler könnte man z. B. aus
einer in @lines eingelesenen FASTA-Datei auf einen Schlag alle Kommentarzeilen extrahieren.
Folgendes Beispiel möge eine andere Aufgabenstellung illustrieren, der
man des öfteren begegnet. Eine Text-Date mit Informationen über Enzyme
könnte z. B. wie folgt aufgebaut sein:
ADH , Alcohol dehydrogenase , EC 1.1.1.1
PDC , Pyruvate decarboxylase , EC 1.2.4.1
PFK , Phosphofructokinase , EC 2.7.1.11
...
Dateien mit solchen Zeilen, die einzelne, durch Kommata voneinander
getrennte Datenwerte enthalten, heißen (comma separated value- oder
.csv-Dateien) und stellen ein verbreitetes Datenformat dar, um größere
Datenbestände in tabellarischer Form bereitzustellen. Die einzelnen Zeilen
entsprechen dabei den Datensätzen (engl. records) und die durch Kommata getrennten Einträge den Tabellenspalten (oder Datenfeldern/date
fields). Eine – gerade beim Extrahieren von Daten aus .csv-Dateien – oft
recht nützliche Funktion ist split. Diese spaltet eine Zeichenkette in eine
Liste von Substrings auf, wobei vermittels eines regulären Ausdrucks angegeben werden kann, wo innerhalb der Zeichenkette die Trennungen erfolgen
sollen. Enthielte $record z. B. den String
"ADH, Alcohol dehydrogenase, EC 1.1.1.1"
so würde
@fields = split(/,\s*/, $record)
die einzelnen, durch Kommata (und beliebig viele Leerzeichen, Tabulatoren u.ä. – whitespaces eben...) voneinander getrennte Werte fein säuberlich
in die @fields-Liste packen. Ein
foreach $value ( @fields ) {
print ( " $value \ n " ) ;
}
würde uns
ADH
Alcohol dehydrogenase
EC 1.1.1.1
comma separated
value
Datensatz
record
Datenfeld
data field
split
92
KAPITEL 5. REGULÄRE AUSDRÜCKE
liefern.
join
Quasi das Gegenteil zu split-Funktion stellt die join-Funktion dar –
und deshalb sei sie hier erwähnt, obwohl sie mit regulären Ausdrücken nichts
direkt etwas zu tun hat. Sie erlaubt es nämlich, die Elemente einer Liste zu
einer einzigen Zeichenkette zu verschmelzen – wobei wir angeben können,
was zwischen die einzelnen Teilstrings gepackt werden soll. So könnten wir
die oben erhaltene @fields-Liste mit
$newLine = join("\t", @fields);
zu einer Zeichenkette der Art
"ADH → Alcohol dehydrogenase → EC 1.1.1.1"
tab-separated value
vereinen, wobei → für das Tabulator-Zeichen \t steht. Die Trennung
einzelner Datenfelder durch Tabulator-Zeichen \t stellt übrigens – neben der
Komma-Separierung – eine weitere, verbreitete Methode dar, tabellarische
Daten bereitzustellen; man spricht von tab-separated value- oder .tsvDateien.
join kann auch prima verwendet werden, um die in eine Liste eingelesenen Zeilen einer Datei zu einer einzigen langen Zeichenkette zu verschmelzen:
if ( open ( FILE , $filename ) ) {
@lines = < FILE >
close ( FILE ) ;
$content = join ( " " , @lines ) ;
}
Und falls man z. B. eine Sequenz aus einer Datei einlesen möchte, in
der die Sequenzdaten über mehrere Zeilen verteilt sind, entfernt man die
Zeilenumbrüche einfach vor dem Verketten per chomp:
if ( open ( SEQ , $seqfile ) ) {
@lines = <SEQ >;
close ( SEQ ) ;
chomp ( @lines ) ;
$seq = join ( " " , @lines ) ;
}
printf
Gerade wenn man Tabellen, die Zahlen enthalten, berechnen und ausgeben möchte, steht man vor dem Problem, dass die Zahlenwerte u. U. aufgrund einer variierenden Anzahl von (Vor- und/oder Nachkomma-) Stellen
verschieden viel Platz beanspruchen. Das hat zur Folge, dass eine Ausrichtung der Zahlen in regelmäßigen Spalten vereitelt wird. Diese und ähnliche
Formatierungsprobleme lassen sich in Perl hervorragend mittels der printfAnweisung erledigen, die wie folgt zu benutzen ist. Beispiel:
printf (" Pi mit Vorzeichen und auf 4 Nachkommastellen ➘
genau : \%+8.4 f ." , 3.1415926)
ergibt als Ausgabe:
5.4. WEITERE FUNKTIONEN ZUR ZEICHENKETTEN-MANIPULATION93
Pi mit Vorzeichen und auf 4 Nachkommastellen genau: +3.1416.
Generell erwartet die printf-Anweisung beliebig viele, aber mindestens
zwei Argumente:
printf($format, $a, $b, $c ...)
Das erste Argument ist immer eine Zeichenkette, die beschreibt, wie die
nachfolgenden Argumente formatiert werden sollen. Diese Zeichenkette kann
beliebigen Text enthalten, wobei die Stellen, an denen die nachfolgenden
Argumente erscheinen werden sollen, durch besondere Platzhalter gekennzeichnet sind. Diese Platzhalter beginnen immer mit einem Prozent-Zeichen
% und enthalten weitere Informationen über die gewünschte Formatierung.
Über Details informieren Sie sich bitte bei Bedarf in der Hilfe-Seite perlfunc;
einige häufig gebrauchte Formatierungscodes seien hier jedoch kurz in Tabelle 5.1 erwähnt.
Falls Sie das Ergebnis einer Formatierung nicht ausgeben, sondern zur
anderweitigen Verwendung in einer Variablen speichern möchten, verwenden
Sie einfach die sprintf-Funktion. Sie versteht die gleichen Argumente wie
printf, liefert jedoch das Formatierungsergebnis als Zeichenkette zurück.
Tabelle 5.1: Formatierung
Beispiel
Bedeutung
Ergebnis
%8u
Vorzeichenlose ganze Zahl, 8
Spalten breit
Vorzeichenlose ganze Zahl mit
führenden Nullen, 8 Spalten breit
Ganze Zahl, ggfs. mit negativem
Vorzeichen, 8 Spalten breit
Reelle Zahl, 8 Spalten breit, wobei
2 Spalten für die
Nachkommastellen reserviert sind
Wie oben, aber mit
obligatorischem Vorzeichen
Rechtsbündige Zeichenkette, 8
Spalten breit
Linksbündige Zeichenkette, 8
Spalten breit
Prozentzeichen
"
%08u
%8d
%8.2f
%+8.2f
%8s
%-8s
%%
3"
"00000003"
"
3"
"
3.14"
"
+3.14"
" 3.1415"
"3.1415 "
"%"
Alle Beispiel-Formatierungscodes wurden auf den Wertes 3.1415 angewendet. Die Anführungszeichen " im Ergebnis gehören nicht zur Ausgabe,
sondern dienen nur der Verdeutlichung der Spaltenzahl des Ergebnisses.
sprintf
94
KAPITEL 5. REGULÄRE AUSDRÜCKE
Aufgaben
5.1 Schreiben Sie ein Programm, das die Anzahl aromatischer (hydrophober, geladener... was immer Sie wollen!) Aminosäuren in
einer Aminosäuresequenz bestimmt. (Hinweis: Als aromatisch
gelten die Aminosäuren Phenylalanin, Tyrosin und Tryptophan.)
5.2 Bei globaler Suche fährt Perl nach jedem match hinter dem letzten Treffer fort. Dabei kann es geschehen, dass überlappende
Treffer nicht gefunden werden – so wird m/GCGGCCGC/g (sucht
nach Restriktionsstellen des Enzyms NotI) in GCGGCCGCGGCCGC nur
einen Treffer finden, weil die zweite NotI-Site beginnt, bevor die
ersten fertig“ ist.
”
Schreiben Sie ein Programm, das dieses Manko behebt und
auch überlappende Vorkommen beliebiger Muster findet (und
deren Positionen in einer Liste speichert). (Hinweis: Außer die
aktuelle Suchposition zurückzuliefern, kann pos($string) auch
gem. pos($string) = $newPos dazu verwendet werden, die aktuelle Suchposition zu verändern! Und denken Sie daran, dass die
Länge des Treffers nicht unbedingt mit der Länge des Suchmusters übereinstimmen muss!)
5.3 Außer im FASTA-Format werden Sequenzdaten häufig auch in
Form von Dateien folgenden Formats bereitgestellt:
atggcgtctt tctctgctga gaccaattca actgacctac 40
tctcacagcc atggaatgag cccccagtaa ttctctccat 80
cccccagtaa ttctctccat ggtcattctc agccttactt 120
...
(Wahlweise können die Positionsangaben auch am Zeilenanfang stehen.) Schreiben Sie ein Programm, das eine
solchermaßen formatierte oder eine FASTA-Sequenz einlesen
(Beispieldateien
C3aR-Homo_sapiens.embl.seq
bzw.
C3aR-Homo_sapiens.dna.fasta) und als reine“ DNA-Sequenz (d.
”
h. nur die Zeichen a, c, g, t bzw. deren Großbuchstaben-Pendants
enthaltend; keine Ziffern, Leerzeichen oder Zeilenumbrüche!)
ausgeben kann.
5.4. WEITERE FUNKTIONEN ZUR ZEICHENKETTEN-MANIPULATION95
Aufgaben
5.4 Die in eukaryontischen Genomen vorkommenden sog. CpG-Inseln
(CpG islands) sind definiert als Regionen von mindestens 200 bp
Länge, die einen GC-Gehalt > 50% aufweisen und bei denen
das Verhältnis zwischen dem beobachteten und dem (anhand der
Basenzusammensetzung der gesamten Sequenz) erwarteten Gehaltes an CG-Folgen größer als 0.6 ist.
Angelehnt an diese Definition, schreiben Sie ein Programm, das
ein Fenster wählbarer Länge nucleotidweise über eine DNASequenz schiebt und für jedes dieses Fenster (genauer: die im
Fenster enthaltene Subsequenz) den GC-Gehalt sowie den oben
definierten Quotienten berechnet. Geben Sie die Berechnungsergebnisse für jedes Fenster in einer vierspaltigen Tabelle aus,
wobei die 1. Spalte Startposition des jeweiligen Fensters, die 2.
Spalte den ermittelten Quotienten, die 3. Spalte einen Indikator
(etwa * und -) dafür, ob das Fenster eine CpG-Insel überdeckt,
und die 4. Spalte eine Art horizontales Balkendiagramm“ des
”
GC-Gehaltes des Fensters (d.h. eine entsprechende Anzahl # oder
anderer Zeichen) beinhalten soll.
96
KAPITEL 5. REGULÄRE AUSDRÜCKE
Kapitel 6
Strukturierte
Programmierung
Nach Studium dieses Kapitels können Sie
• Ihre Programm durch die Zusammenfassung von Anweisungen zu Subroutinen besser lesbar machen
• beliebige Funktionalitäten für den Aufruf von beliebigen Stellen des
Programms aus verfügbar machen
• den Geltungsbereich von Variablen einschränken und sich so vor Namenskonflikten schützen
• auf sich selbst verweisende (rekursive) Berechnungen implementieren
6.1
Recycling 2 - Subroutinen
In dem Programm zur Identifizierung von CpG-Inseln, das Sie in Aufgabe 5.4
auf S. 95 verfasst haben, mussten Sie zur Lösung der Aufgabe mehrfach den
CG-Gehalt sowie die erwartete und die beobachtete CpG-Häufigkeit einer
Nucleotidsequenz bestimmen: zunächst für die gesamte Sequenz und dann
für die jeweiligen Subsequenzen der einzelnen Fenster. Für beide Teilaufgaben haben Sie wahrscheinlich fast wortwörtlich die gleichen Programmzeilen
niedergeschrieben – lediglich der eine oder andere Variablenname dürfte ausgetauscht gewesen sein. In diesem Falle mag das ja noch praktikabel gewesen
sein – aber stellen Sie sich vor, Sie müssten ein Programm schreiben, bei der
97
98
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
eine prinzipiell immer gleichartige längere Berechnung an zahlreichen Stellen
immer wieder benötigt wird!
Subroutine
Prozedur
Funktion
Nach Kapitel Abschnitt 4.5 ab S. 75 könnten Sie diese Berechnung in ein
externes Programm auslagern und dieses – wann immer nötig – aufrufen.
Alternativ könnten Sie die Berechnungsanweisungen aber auch innerhalb ihres eigenen Programms auf wiederverwendbare Art und Weise unterbringen
– in Form einer Subroutine, manchmal auch Prozedur oder Funktion
genannt (wann man was verwendet, klären wir später).
Ein einfaches Beispiel für eine Subroutine wäre eine solche, die bei Aufruf
die aktuelle Uhrzeit ausgibt:
sub printTime {
@timeDate = localtime ( time ) ;
print ( " Es ist jetzt " , $timeDate [2] , " Uhr " , ➘
$timeDate [1] , " \ n " ) ;
}
sub
Eine Subroutinen-Definition beginnt immer mit dem Schlüsselwort sub,
gefolgt von einem Bezeichner Ihrer Wahl und einem in {...} eingeschlossenen Block von Anweisungen. Wann immer Sie nun von Ihrem Programm
aus die Uhrzeit ausgeben möchten, können Sie die Subroutine einfach mit
ihrem Bezeichner (hier: printTime), dem Sie ein & voranstellen, wie eine
ganz normale Perl-Anweisung aufrufen – wobei die in dem Block stehenden
Anweisungen ausgeführt werden:
...
& printTime ;
# tue dies
& printTime ;
# tue das
& printTime ;
...
Hauptprogramm
Eine Subroutinen-Definition kännen Sie übrigens an beliebiger Stelle in
Ihrem Quellcode unterbringen; generell sollten Sie jedoch alle in Ihrem Programm verwendeten Subroutinen entweder komplett vor oder komplett nach
dem eigentlichen Programm-Kern (auch Hauptprogramm genannt) definieren.
Subroutinen fassen also eine Folge von Anweisungen zu einer Art Meta”
Befehl“ zusammen – einem neuen Perl-Befehl, der eine von Ihnen bestimmte
Wirkung hat. Nun kennen Sie bereits zahlreiche Perl-Befehle, die ein oder
6.1. RECYCLING 2 - SUBROUTINEN
99
mehrere Argumente erwarten – etwa den print-Befehl, dem mitgeteilt werden muss, was er eigentlich ausgeben soll, oder chomp oder close oder... Ob
so etwas wohl auch bei Subroutinen möglich ist?
Die Antwort lautet: Ja – und hier kommt eine weitere von Perls Spezialvariablen ins Spiel: @_. Schauen wir uns hierzu als Beispiel eine Subroutine
an, die zu einer DNA-Sequenz den komplementären Strang ausgibt:
sub printComplement {
$seq = shift ( @_ ) ;
$seq =~ tr / gatc / ctag /;
print ( " $seq \ n " ) ;
}
Aufrufen würde man so eine Subroutine wie folgt:
&printComplement("gattaca")
Anderes Beispiel: eine Subroutine, die ausgibt, wie oft ein beliebiges
Zeichen innerhalb einer Zeichenkette vorkommt:
& printOccurrances ( " t " , " gattaca " ) ;
...
sub printOccurrances {
$char = shift ( @_ ) ;
$string = shift ( @_ ) ;
$n = ( $string =~ s / $char / $char / g ) ;
print ( " $n \ n " ) ;
}
Wie bei anderen Perl-Anweisungen auch, werden die Argumente hinter
dem Befehlsnamen in Klammern übergeben. In der Subroutine selbst kann
dann über @_ auf die Argumente zugegriffen werden; diese Liste stellt alle
Argumente in derjenigen Reihenfolge bereit, in der sie beim Aufruf angeführt
werden. In obigen Beispielen werden die Argumente per shift der Reihe
nach von vorne nach hinten aus der Liste geholt – also zunächst t und dann
gattaca.
Es ist auch möglich, einer Subroutine eine ganze Liste als Argument zu
übergeben. Diese steht dann komplett in der @_-Liste, und man kann einfach
mit ihr weiterarbeiten:
sub printList {
print ( join ( " , " , @_ ) , " \ n " ) ;
}
Etwas kniffliger hingegen ist es, wenn man außer einer Liste auch noch
ein oder mehrere skalare Argumente übergeben möchte. Man kann nämlich
nach der Übergabe an die Subroutine nicht mehr unterscheiden, welche der
Elemente von @_ nun den ursprünglichen Skalaren entsprechen und welche
@_
100
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
Elemente Bestandteile der übergebenen Liste waren. Allerdings läßt sich
die ursprüngliche Liste mittels shift und pop wieder freilegen“, wenn man
”
weiß, wie viele und in welcher Reihenfolge die Skalare übergeben wurden:
& vieleArgumente ( $a , $b , @c , $d )
...
sub vieleArgumente {
$arg1 = shift ( @_ ) ;
$arg2 = shift ( @_ ) ;
$arg4 = pop ( @_ ) ;
@arg3 = @_ ;
...
}
#
#
#
#
holt $a
holt $b
holt $d
@c bleibt äbrig
Bisher haben wir Subroutinen mit diversen Argumenten aufgerufen und
uns – aus Sicht des Aufrufs – nicht weiter darum gekümmert, was diese mit
den Argumenten anfangen. Allerdings kännen Subroutinen auch ein Berechnungsergebnis zurückliefern und somit wie Funktionen verwendet werden:
$comp = & getComplement ( " gattaca " ) ;
...
sub getComplement {
$seq = shift ( @_ ) ;
$seq =~ tr / gatc / ctag /;
return $seq ;
}
return
Ausgabe
Rückgabe
Die return-Anweisung am Ende der Subroutine beendet diese und gibt
den Wert von $seq zurück, sodass er nach dem Aufruf in $comp aufgefangen
werden kann – so wie auch die eingebauten“ Perl-Funktionen wie sin, cos,
”
substr etc. ihr Berechnungsergebnis zurückgeben. (An dieser Stelle sei noch
einmal explizit auf den bereits in Abschnitt 1.6 ab S. 27 erwähnten fundamentalen Unterschied zwischen Ausgabe – Darstellung von Ergebnissen
auf einem Ausgabemedium wie dem Bildschirm – und Rückgabe – Bereitstellung eines Ergebnisses durch eine Funktion zur Weiterverarbeitung oder
Ausgabe (!) – hingewiesen!)
Eine Subroutine darf auch mehrere return-Answeisungen enthalten – wie
etwa in folgender Subroutine, die den Absolutbetrag einer Zahl berechnet:
6.1. RECYCLING 2 - SUBROUTINEN
101
sub absVal {
$a = shift ( @_ ) ;
if ( $a >= 0) {
return $a ;
}
else {
return - $a ;
}
}
Schließlich sei noch vermerkt, dass Subroutinen auch ganze Listen als
Ergebnis zurückliefern können:
@ucWords = & ucList ( @words )
...
sub ucList {
@result = () ;
foreach $element ( @_ ) {
push ( @result , uc ( $element ) ) ;
}
return @result ;
}
Mit Subroutinen haben Sie nun eine extrem flexible Möglichkeit kennen gelernt, Ihre Programme durch Wiederverwendung von Code besser zu
strukturieren. Wie auch bei der Auslagerung von zu wiederholenden Aufgaben in externe Programme, profitieren Sie hier von der verringerten Schreibarbeit und vor allem von der Tatsache, dass sich Änderungen (i.d.R. hoffentlich Verbesserungen...) an nur einer Stelle (in der Subroutine eben) sofort
global (überall, wo die Subroutine aufgerufen wird) auswirken.
Gegenüber der Auslagerung in externe Programme haben Subroutinen
zwar den Nachteil, nur für das eine Programm verfügbar zu sein, in dessen Quelltext sie stehen; dafür erfolgt der Aufruf einer Subroutine wesentlich schneller als der eines Programms (das erst vom Betriebssystem in
den Speicher geladen werden muss). Zudem können Sie das Ergebnis eines
Subroutinen-Aufrufs sofort in Form von Perl-Variablen weiterverarbeiten
statt erst die Ausgabe eines Programms durchparsen zu müssen.
Schließlich sei erwähnt, dass sie auf der perldoc-Seite perlsub alles über
Subroutinen nachlesen kännen.
102
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
Tipp 6.1: Noch bessere Quellcodelesbarkeit durch Subroutinen
Die Verwendung von Subroutinen kann auch dann sinnvoll sein, wenn die
in ihnen enthaltenen Anweisungen nicht mehrfach benötigt werden. Allein
durch die Zusammenfassung von logisch zusammengehörenden Anweisungen und deren Repräsentation durch einen einzigen Bezeichner kann die
Lesbarkeit eines Quelltextes ungemein verbessert werden. Für das prinzipielle Verständnis des Kontrollflusses ist es nämlich oftmals nicht so
wichtig, wie im Detail eine Berechnung durchgeführt wird – es genügt
zu wissen, wo innerhalb des Programms sie durchgeführt wird. Und dies
kann durch den Aufruf einer Subroutine mit wohlgewähltem Namen gut
verdeutlicht werden.
Ein Problem, das Subroutinen in ihrer bisher vorgestellten Form bereiten können, haben wir bisher jedoch geflissentlich verschwiegen. Um dieses
Problem zu illustrieren, wollen wir überlegen, was geschieht, wenn wir das
folgendes Programm laufen lassen:
Listing 6.1: Namenskonflikt
1
2
3
$sequence = " gattaca " ;
print & complement ( $sequence ) ,
" ist die komplementäre Sequenz zu $sequence .\ n " ;
4
5
6
7
8
9
sub complement {
$sequence = shift ( @_ ) ;
$sequence =~ tr / gatc / ctag /;
return $sequence ;
}
Man würde sich so etwas wie
ctaatgt ist die komplementäre Sequenz zu gattaca.
als Ausgabe erhoffen – was man allerdings beobachtet, ist folgendes:
ctaatgt ist die komplementäre Sequenz zu ctaatgt.
Das ist natürlich völliger Blödsinn. Was nur ist schief gelaufen?
Erklärung: In Zeile 1 wird der Variablen $sequence der Wert gattaca
zugewiesen, und in Zeile 2 wird dann die Subroutine &complement mit eben
diesem Inhalt von $sequence aufgerufen. So weit ist noch alles in Ordnung
– aber innerhalb der Subroutine – in Zeile 6 – wird der übergebene Wert
in einer Variablen namens $sequence gespeichert – also in der selben Variablen, die bereits im Hauptprogramm verwendet wird. In Zeile 7 wird der
Variablenwert in sein Komplement verändert, in Zeile 8 zurückgegeben und
– nach Rücksprung ins Hauptprogramm – in Zeile 2 ausgeben. Wenn der
Kontrollfluss nun in Zeile 3 ankommt und den Wert von $sequence ausgibt,
6.2. LOKALE VARIABLEN UND SICHTBARKEIT
103
enthält diese Variable nicht mehr den ursprünglichen Wert gattaca, sondern
bereits das komplementäre ctaatgt!
Dies ließe sich natürlich dadurch umgehen, dass wir in der Subroutine für die Sequenz eine anders benannte Variable als im Hauptprogramm
verwenden, etwa wie in folgendem Listing:
Listing 6.2: Namenskonflikt vermeiden, provisorisch
$sequence = " gattaca " ;
print & complement ( $sequence ) ,
" ist die komplementäre Sequenz zu $sequence .\ n " ;
sub complement {
$seq = shift ( @_ ) ;
$seq =~ tr / gatc / ctag /;
return $seq ;
}
Hier wurde in der Subroutine statt $sequece eine neue Variable namens
$seq verwendet, wodurch die Gefahr der doppelten Variablenverwendung
umgangen wurde. Theoretisch mäglich, aber sehr fehleranfällig wäre es, in
jeder Subroutine – wie in obigem Beispiel – einmalige, nirgendwo anders
ebenfalls verwendete Variablennamen zu verwenden. Im nächsten Abschnitt
werden wir jedoch eine Möglichkeit kennen lernen, sich dieser lästigen Pflicht
auf höchst elegante Weise zu entziehen.
6.2
Lokale Variablen und Sichtbarkeit
Um Konflikte zwischen Variablen mit dem gleichen Namen, aber verschiedener Bedeutung an verschiedenen Orten im Programm zu vermeiden, bieten
fast alle Programmiersprachen Unterstützung für das Konzept der Sichtbarkeitsbereiche an. Ein Sichtbarkeitsbereich ist ein Bereich innerhalb eines Programms, in dem eine Variable anhand ihres Namens angesprochen
werden kann – dem Interpreter bekannt, für ihn sichtbar ist. Bisher konnten
wir jede Variable von überall im Programm aus ansprechen; solche Variablen
nennt man globale Variablen. Nun sollten sich aber z. B. Hauptprogramm
und Subroutinen möglichst nicht darum scheren müssen, welche Variablennamen vom jeweils anderen Teil verwendet werden – und daher verwendet
man in Subroutinen i. d. R. so genannte lokale Variablen, die nur innerhalb der Subroutine über ihren Namen angesprochen werden können:
Sichtbarkeitsbereiche
globale Variablen
lokale Variablen
104
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
Listing 6.3: Namenskonflikte vermeiden durch lokale Variablen
1
2
3
$sequence = " gattaca " ;
print & complement ( $sequence ) ,
" ist die komplementäre Sequenz zu $sequence .\ n " ;
4
5
6
7
8
9
my
Maskierung
sub complement {
my $sequence = shift ( @_ ) ;
$sequence =~ tr / gatc / ctag /;
return $sequence ;
}
Sehen Sie den Unterschied? Richtig, in Zeile 6 wird der Variablen $sequence
bei ihrer erstmaligen Verwendung innerhalb der Subroutine das Schlüsselwort
my vorangestellt. Dies bewirkt, dass innerhalb der Subroutine neuer Speicherplatz reserviert wird und dieser ab der my-Zeile bis zum Ende der Subroutine
über $sequence angesprochen werden kann. Wann immer nun also innerhalb
des Subroutinen-Blocks auf $sequence zugegriffen wird, ist damit automatisch die lokale Variable $sequence gemeint – nicht die ( zufälligerweise“
”
gleich bezeichnete) Variable $sequence aus dem Hauptprogramm. Man sagt
auch, die lokale Variable $sequence maskiere die gleichnamige globale Variable1 .
Im folgenden Listing ist der Sichtbarkeitsbereich der lokalen Variable
$sequence der Subroutine hervorgehoben:
$sequence = "gattaca";
print(&complement($sequence),
" ist die komplementäre Sequenz zu $sequence.\n")
sub complement {
my $sequence = shift(@_);
sequence =~ tr/gatc/ctag/;
return $sequence;
}
Nur innerhalb des markierten Bereichs ist die lokale $sequence-Variable
gemeint, wenn man eben diesen Bezeichner verwendet. Generell sind lokale
Variablen nur innerhalb desjenigen Blockes (also einer Gruppe von in geschweifte Klammern {...} eingeschlossenen Perl-Befehlen) sichtbar, in dem
sie deklariert wurden. Dies gilt nicht nur für Subroutinen, sondern beispielsweise auch für die verschiedenen zu einer bedingten Verzeigung gehörenden
Blöcke sowie Schleifenrümpfe.
1
Tatsächlich ist der Zugriff auf maskierte Hauptprogramm-Variablen über einen kleinen Trick noch immer möglich – und zwar durch Voranstellung von main:: vor den
Variablennamen (in unserem Beispiel also $main::sequence)
6.2. LOKALE VARIABLEN UND SICHTBARKEIT
105
Auch bei der ausschließlichen Verwendung von globalen Variablen in
einer Subroutine ist es ohne weiteres möglich, von dort aus auf anders benannte (also nicht maskierte) Variablen des Hauptprogramms zuzugreifen.
Wenn Subroutinen solche globalen Variabeln verändern, verändern sie Dinge außerhalb ihrer selbst, und man sagt dann, die Subroutine habe Seiteneffekte. Wir werden noch (einige wenige!) Fälle kennen lernen, in denen
solche Seiteneffekte erwünscht sind; als Grundregel gilt jedoch, dass Sie die
Übergabe von Argumenten über die @_-Liste und die Rückgabe von Funktionswerten via return als einzige Mechanismen verwenden sollten, um Daten
zwischen Hauptprogramm und Subroutine (oder zwischen sich gegenseitig
aufrufenden Subroutinen) auszutauschen. Also statt
$sequence = " gattaca " ;
& complement ;
...
sub complement {
$sequence =~ tr / gatc / ctag /;
}
lieber so etwas wie
$sequence = " gattaca " ;
$sequence = & complement ( $sequence ) ;
...
sub complement {
my $sequence = shift ( @_ ) ;
$sequence =~ tr / gatc / ctag /;
return $sequence ;
}
Tipp 6.2: Lokal denken, lokal handeln!
Da man bei größeren Programmierprojekten rasch den überblick verliert,
welche Variablennamen wo verwendet werden bzw. aussagekräftige Variablennamen rasch verbraucht“ sind, empfiehlt es sich, alle Variablen, die
”
man in Subroutinen verwendet, per my als lokal zu deklarieren. So kann
man in jeder Subroutine diejenigen Variablennamen verwenden, die einem
sinnvoll erscheinen, ohne sich um mögliche Namenskonflikte kümmern zu
müssen. (Die Kommunkation zwischen Subroutin und dem aufrufenden
Programmteil sollten ja ohnehin einzig über die Argumentliste @_ und
Ergebnisrückgabe via return laufen!)
Generell sollte es zur Benutzung einer Subroutine ausreichen zu wissen,
welche Argumente sie erwartet und was sie zurückgibt – eine genaue Kennt-
Seiteneffekte
106
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
nis der Implementierungsdetails (Variablennamen, Algorithmus etc.) sollte
nicht nötig sein. Dieses mächtige und auch für andere Ebenen der CodeWiederverwertung gültige Programmierparadigma heißt Kapselungsprinzip und fördert Effizienz und Teamarbeit in der Softwareentwicklung –
größere Softwareprojekte werden durch seine Beachtung erst ermöglicht!
Kapselungsprinzip
In Perl gibt es eine Möglichkeit, die Verwendung von lokalen Variablen einzufordern: mit Hilfe des Pragmas strict. Pragmata sind so etwas wie Anweisungen an den Perl-Compiler, zusätzliche Syntax-Regeln zu
berücksichtigen oder Sprachkonstrukte und -semantiken bereitszustellen. Eingebunden werden Pragmata über die use-Anweisung, am besten zu Programmbegin:
Pragma
strict
use
Listing 6.4:
1
use strict ;
2
3
4
5
my $sequence = " gattaca " ;
print & complement ( $sequence ) ,
" ist die komplementäre Sequenz zu $sequence .\ n " ;
6
7
8
9
10
11
sub complement {
my $sequence = shift ( @_ ) ;
$sequence =~ tr / gatc / ctag /;
return $sequence ;
}
Beachten Sie, dass nun auch die globale Variable $sequence aus dem
Hauptprogramm per my als lokal“ deklariert werden muss. Praktisch ist sie
”
damit jedoch noch immer global – lokale Variablen sind nämlich auch von
Unterblöcken innerhalb ihres Geltungsbereichs aus sichtbar (sofern in einem
Unterblock nicht erneut eine lokale Variabel gleichen Namens deklariert wird
– Stichwort Maskierung), und da Subroutinen aus Sicht des Haptprogramms
Unterblöcke darstellen, könnte in Subroutinen auf Hauptprogramm-lokale
Variablen zugegriffen werden (was wir jedoch vermeiden wollen, s.o.).
Im folgenden Listing ist der Gültigkeitsbereich der Hauptprogrammlokalen (also de facto globalen) $sequenz-Variablen hell schattiert, während
der Bereich, in dem die gleichnamige lokale Variable bekannt ist (und ihr
globales Pendant verdeckt), dunkel unterlegt ist:
6.2. LOKALE VARIABLEN UND SICHTBARKEIT
107
use strict;
my $sequence = "gattaca";
print(&complement($sequence),
" ist die komplementäre Sequenz zu $sequence.\n")
sub complement {
my $sequence = shift(@_);
sequence =~ tr/gatc/ctag/;
return $sequence;
}
Ein weiteres nützliches Pragma ist warnings. Nach Einbindung mittels
use warnings warnt es schon beim Compilieren, aber auch beim eigentlichen Programmlauf vor zahlreichen möglichen Fehlerquellen. So unterstütz
warnings z. B. die in Tipp 2.5 auf S. 46 propagierte freiwillige Typisierung,
indem es zu einer Warnmeldung führt, wenn während des Programmablaufs
Werte unpassenden Typs an Funktionen übergeben oder mit Operatoren
verknüpft werden, also etwa versucht wird, eine Zeichenkette mittels + zu
einer Zahl zu addieren. Weiterhin achtet warnings darauf, ob Variablen vor
ihrer Benutzung ordentlich initialisiert worden sind. Bei der Einführung von
Variablen in einem Programm unterscheidet man nämlich zwischen der Deklaration – man teilt dem Compiler mit, dass man eine Variable verwenden
möchte – und der Initialisierung – also der Zuweisung eines ersten Wertes
an die Variable.
warnings
Deklaration
Initialisierung
In Perl lassen sich beide Schritte innerhalb einer Anweisung erledigen
(was wir auch schon die ganze Zeit über getan haben):
my $a = 10
Es wäre aber auch möglich, die Deklaration
my $a
an einer anderen Stelle vorzunehmen als die Initialisierung
$a = 10
Je nach Programmiersprache hat eine deklarierte, aber nicht initialisierte
Variable entweder einen festen Vorgabewert, einen zufälligen Wert oder – wie
in Perl – einen ganz bestimmten Wert, der anzeigt, dass die Variable noch
nicht initialisiert worden ist. In Perl heißt dieser Wert undef2 , und mittels
der Funktion defined kann überprüft werden, ob eine Variable initialisiert
worden ist:
2
In anderen Sprachen wird dieser Nicht-Wert“ oft null oder nil genannt.
”
undef
defined
108
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
Listing 6.5: Variableninitialisierung
1
2
use strict ;
use warnings ;
3
4
5
6
7
8
9
my $a ;
print ( " Definiert : " , defined ( $a ) , " \ n " ) ;
$a = 10;
print ( " Definiert : " , defined ( $a ) , " \ n " ) ;
$a = undef ;
print ( " Definiert : " , defined ( $a ) , " \ n " ) ;
Hierbei erhalten wir nur bei der mittleren print-Anweisung (in Zeile 7)
eine 1 – also TRUE – als Ausgabe; in Zeile 5 ist $a zwar deklariert, aber noch
nicht initialisiert, und bis Zeile 9 haben wir $a bereits wieder (in Zeile 8) in
den undefinierten Zustand zurückversetzt.
Übungen
6.1 Schreiben Sie je eine Subroutine, die die komplementäre bzw. reverse Sequenz zu einer als Argument übergebenen DNA-Sequenz
zurückgibt. Schreiben Sie eine weitere Subroutine, die die reverskomplementäre Sequenz bestimmt und sich die beiden anderen
Subroutinen zunutze macht. Achten Sie darauf, die Kommunikation zwischen aufrufendem und aufgerufenem Code einzig über
Funktionsargumente (@_) und return durchzuführen!
6.2 Schreiben Sie eine Subroutine, die alle – auch überlappende! –
Substrings zurückliefert, die innerhalb einer beliebigen (als Argument übergebenen) Zeichenkette einem (ebenfalls als Argument
übergebenen) wählbaren Muster (regulärer Ausdruck) entsprechen. Denken Sie daran, dass die Länge eines Treffers nicht unbedingt der Länge des regulären Ausdrucks entsprechen muss!
6.3 Schreiben Sie unter Verwendung der in den beiden vorherigen
Übungen erstellten Subroutinen ein Programm, das nach Eingabe
einer DNA-Sequenz sowohl den eingegebenen als auch den reverskomplementären Strang nach einem wählbaren Muster durchsucht und alle – auch überlappenden! – Treffer ausgibt.
109
6.3. REKURSION
Tipp 6.3: Sich Vorschriften machen und warnen lassen
Da es sich bei Namenskonflikten und der Verwendung von uninitialisierten
Variablen um sehr häufige Fehlerquellen handelt, sollten Sie bei der Programmentwicklung immer die Pragmate strict und warnings einbinden.
Tatsächlich werden wir im weiteren Verlauf des Kurses davon ausgehen,
dass use strict und use warnings genau so selbstverständlich am Anfang
Ihres Quelltextes stehen wie die Shebang-Zeile.
6.3
Rekursion
Erinnern Sie sich noch an die Definition der Fakultät auf S. 47? Wenn wir
die dort angegebenen Gleichungen etwas umstellen, erhalten wir
n! =



1









n
Y


i = 1 × 2 × 3... × (n − 1) × n
i=1



n−1

Y



i×n
=
(1
×
2
×
3...
×
(n
−
1))
×
n
=




i=1



= (n − 1)! × n
für n = 0
für n > 0
Offenbar können wir die Fakultät einer natürlichen Zahl n > 1 aus der
Fakultät ihres Vorgängers berechnen: n! = (n − 1)! × n. Ein solches Berechnungsverfahren, das auf sich selbst zurückgreift, nennt man rekursiv . Dank
der Möglichkeit, Subroutinen wie Perl-Funktionen zu verwenden, haben wir
alle Mittel in der Hand, solche rekursiven Berechnungsverfahren selbst zu
implementieren:
Rekursion
110
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
Listing 6.6: Fakultaet, rekursiv
1
2
3
use strict ;
# Wie gesagt , in Zukunft * immer *
use warnings ; # einbinden - auch wenn sie im
# Listing nicht mehr stehen !
4
5
6
7
my $n = $ARGV [0];
chomp ( $n ) ;
print ( " Die Fakultät von $n ist " , & fac ( $n ) , " \ n " ) ;
8
9
10
11
12
13
14
15
16
17
sub fac {
my $n = shift ( @_ ) ;
if ( $n == 0) {
return 1;
}
else {
return & fac ( $n -1) * $n ;
}
}
Der springende Punkt ist dabei die Zeile 14: Hier wird das Ergebnis der
Fakultät von n − 1 durch erneuten Aufruf der fac-Subroutine berechnet.
Spätestens hier wird übrigens deutlich, wie essenziell lokale Variablen sind
– bei jedem Aufruf von fac wird eine neue lokale Variable $n angelegt, auf
die auch nur innerhalb des gerade durch den Kontrollfluss beseelten facAufrufs zugegriffen werden kann. Übrigens sind die lokalen Variablen der
aufrufenden Subroutine von der aufgerufenen Subroutine aus generell nicht
sichtbar – der Aufrufer stellt keinen umschließenden Block dar!
Im folgenden Schema ist am Beispiel der Berechnung von 3! explizit aufgeführt, welchen Zahlenwert die rekursiven Aufrufe von fac jeweils in ihren
lokalen Variablen $n speichern (kursiv und fett dargestellt) und welche
Berechnungen sie damit durchführen:
6.3. REKURSION
111
sub fac {
my 3 = shift(@_);
if (3 == 0) {
return 1;
}
else {
return &fac(3 -1 = 2 ...
sub fac {
my 2 = shift(@_);
if (2 == 0) {
return 1;
}
else {
return &fac(2 -1 = 1 ...
sub fac {
my 1 = shift(@_);
if (1 == 0) {
return 1;
}
else {
return &fac(1 -1 = 0 ...
sub fac {
my 0 = shift(@_);
if (0 == 0) {
return 1 ;
}
...
}
...)→ 1 * 1 = 1 ;
}
}
...)→ 1 * 2 = 2 ;
}
}
...)→ 2 * 3 = 6 ;
}
}
Zur Verdeutlichung des Ablaufs mag es beitragen, wenn wir das rekursive
Fakultäts-Progrämmchen um ein paar Ausgaben erweitern, die den Ablauf
protokollieren (wobei wir das Ergebnis des rekursiven Aufrufs in Zeile 14
zwecks Ausgabe in der Variablen $tmp zwischenspeichern):
112
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
Listing 6.7: Fakultaet, rekursiv, mit Protokollausgabe
1
2
3
my $n = $ARGV [0];
chomp ( $n ) ;
print ( " Die Fakultät von $n ist " , & fac ( $n ) , " \ n " ) ;
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sub fac {
my $n = shift ( @_ ) ;
print ( " fac ( $n ) : beginne mit der Arbeit \ n " ) ;
if ( $n == 0) {
print ( " fac ( $n ) : Sonderfall 0 erreicht , gebe 1 ➘
zurück \ n " ) ;
return 1;
}
else {
print ( " fac ( $n ) : rufe fac ( " , $n -1 , " ) auf \ n " ) ;
my $tmp = & fac ( $n -1) ;
print ( " fac ( $n ) : fac ( " , $n -1 , " ) liefert $tmp , gebe ➘
$tmp * $n = " , $tmp * $n , " zurück \ n " ) ;
return $tmp * $n ;
}
}
Aufruf dieses Programms mit z. B. 4 als Kommandozeilenargument liefert die Ausgabe
fac (4) : beginne mit der Arbeit
fac (4) : rufe fac (3) auf
fac (3) : beginne mit der Arbeit
fac (3) : rufe fac (2) auf
fac (2) : beginne mit der Arbeit
fac (2) : rufe fac (1) auf
fac (1) : beginne mit der Arbeit
fac (1) : rufe fac (0) auf
fac (0) : beginne mit der Arbeit
fac (0) : Sonderfall 0 erreicht ,
fac (1) : fac (0) liefert 1 , gebe
fac (2) : fac (1) liefert 1 , gebe
fac (3) : fac (2) liefert 2 , gebe
fac (4) : fac (3) liefert 6 , gebe
Die Fakultät von 4 ist 24
gebe 1 zurück
1 * 1 = 1 zurück
1 * 2 = 2 zurück
2 * 3 = 6 zurück
6 * 4 = 24 zurück
Hier sieht man sehr schön, wie tatsächlich mehrere geschachtelte In”
vokationen“ der selben Subroutine gleichzeitig existieren, wobei die inneren
Aufrufe ihre Arbeit vollkommen unabhängig von ihrem jeweiligen Aufrufer
erledigen – so ist es z. B. fac(3) völlig egal, ob sie von fac(4) aufgerufen
wird oder (bei Starten des Programms mit 3 als Kommandozeilenargument)
direkt vom Hauptprogramm.
6.3. REKURSION
113
So elegant sich manche Algorithmen auch rekursiv formulieren lassen
mögen – nicht immer ist die Rekursion das Mittel der Wahl. Ein Problem dabei ist nämlich der mit der Rekursionstiefe steigende Speicherverbrauch; bei
jedem Subroutinen-Aufruf muss sich der Interpreter ja merken, wo innerhalb
des Aufrufers der Kontrollfluss nach Beendigung der Subroutine weitergehen
soll. Ebenso benötigen die bei jedem Aufruf neu angelegten lokalen Variablen
Speicherplatz. Diese Information legt der Interpreter übrigens in einem ganz
bestimmten Speicherbereich ab – dem Stapelspeicher (Stack , auch Kellerspeicher genannt). Und wenn dieser Speicherbereich durch vielfachen
Subroutinen-Aufruf voll ist, kommt es zu einem Stapelüberlauf (Stack
overflow ).
Zu einem solchen Fehler kann es auch kommen, wenn die Abbruchbedingung, die bestimmt, wann mit den rekursiven Aufrufen aufzuhören ist,
unsauber formuliert oder falsch implementiert ist; gleiches gilt für die an den
rekursiven Aufruf zu übergebenden Argumente. Daher zum Abschluss dieses
Kapitels ein kleiner Sinnspruch, der weniger als Warnung vor der Verwendung von Rekursion, denn als Ermahnung zur Sorfgalt bei ihrer Verwendung
verstanden werden will: Rekursiv geht meistens schief !
Übungen
6.4 Achtung! Diese Übung führen Sie bitte nicht auf einem der
Kursrechner durch, sondern auf einem Rechner, auf dem keine weiteren Personen arbeiten! Beobachten Sie, was geschieht,
wenn Sie in dem rekursiven Fakultäts-Berechnungsprogramm in
Listing 6.6 auf S. 110 in Zeile 15 aus versehen“ statt n − 1 den
”
Wert von n selbst übergeben!
Stapelspeicher
Stack
Kellerspeicher
Stapelüberlauf
Stack overflow
114
KAPITEL 6. STRUKTURIERTE PROGRAMMIERUNG
Aufgaben
6.1 Für die Polymerase-Kettenreaktion (polymerase chain reaction,
PCR) benätigt man sog. Primer – Oligonucleotide, die bestimmen, wo auf dem Matritzenstrang die Polymerase mit ihrer Arbeit beginnen soll. Pro PCR-Reaktion benätigt man zwei Primer,
die möglichst die gleiche Denaturierungstemperatur (Schmelztemperatur) aufweisen.
Schreiben Sie ein Programm, das die ersten bzw. letzten n (wobei
n vom Benutzer/der Benutzerin frei wählbar sein soll) Nucleotide einer DNA-Sequenz als Primer-Bindungsregionen interpretiert und die Denaturierungstemperaturen der zugehörigen Oligonucleotide anhand der Überschlagsformel Tm = 64, 9 + (41 ×
(Anzahl Gs + Cs) − 600)/n abschätzt. Wenn Sie Lust haben,
können Sie noch weitere Parameter der Oligos berechnen – etwa ihre Molekularmasse (A = 313,2 g/mol; C = 289,2 g/mol;
G = 329,2 g/mol; T = 304,2 g/mol) oder den Extinktionskoeffizienten ǫ (für die photometrische Konzentrationsbestimmung;
A = 15,3 cm2 /mmol; C = 7,4 cm2 /mmol; G = 11,8 cm2 /mmol;
T = 9,3 cm2 /mmol). Denken Sie daran, dass der 3’-Primer die
revers-komplementäre Sequenz des eingegebenen DNA-Stranges
haben muss! Strukturieren Sie Ihr Programm durch die Verwendung von Subroutinen!
6.2 Ein Palindrom ist eine Zeichenkette, die vorwärts gelesen die gleiche Zeichenfolge aufweist wie rückwärts gelesen – etwa Ein Ne”
ger mit Gazelle zagt im Regen nie“ (Leerzeichen sind hier zu
ignorieren...). Auch eine Nukleotidsequenz kann palindromisch
sein – wobei man die Sequenz hier allerdings nicht einfach mit
der reversen, sonder mit der revers-komplementären Sequenz vergleicht.
Schreiben Sie eine Subroutine, die eine DNA-Sequenz daraufhin
untersucht, ob sie ein Palindrom darstellt oder nicht. Verwenden
Sie dann diese Subroutine zum Schreiben eines Programms, das
in einer DNA-Sequenz alle Palindrome aufspürt, die eine gewisse
(wählbare) Mindestlänge aufweisen.
6.3 Schreiben Sie ein Programm, das nach Angabe eines Startverzeichnisses dieses Verzeichnis sowie alle seine (direkten und indirekten) Unterverzeichnisse nach Dateien mit der Endung .pm
durchsucht, eine Liste dieser Dateien erstellt und diese Liste ausgibt.
Kapitel 7
Hashes und Zeiger
Nach Studium dieses Kapitels können Sie
• Listen von Schlüssel-Wert-Paaren (Hashes) anlegen und auf die einzelnen Paare zugreifen
• Ermitteln, welche Schlüssel und Werte in einem Hash verwendet werden
• Daten in Form von Mengen repräsentieren und verarbeiten
• Datenstrukturen indirekt über Zeiger ansprechen
7.1
Erstellen von Hashes
Ein Lexikon oder Wörterbuch enthält zahlreiche Artikel oder Einträge, wobei die Einträge alphabetisch nach ihren jeweiligen Schlüsselbegriff sortiert
sind. Einen bestimmten Artikel finden wir nicht anhand seiner Seitenzahl
– wir spüren ihn vielmehr anhand seines (eindeutigen) Schlüsselwortes auf.
Tatsächlich gibt es im (wissenschaftlichen) Alltag zahlreiche weitere Beispiele solcher Schlüssel-Wert-Listen: Warenkataloge mit Paaren von Arikelnummern (die ja selten wirklich Nummern sind, sondern oft auch Buchstaben enthalten) und Artikelbeschreibungen, Restriktionsenzyme und deren
Schnittstellen, Codons und zugehörige Aminosäuren usw. usw.1
Wenn wir solche Daten – z. B. die Artikel eines Lexikons – in einem PerlProgramm repräsentieren und weiterverarbeiten wollten, könnten wir auf die
Idee kommen, die einzelnen Einträge z. B. als Zeichenketten in einer Liste zu
1
Wir wollen die Tatsache, dass ein gutes Lexikon auch mehrere Einträge z. B. zum
Stichwort Bach“ haben wird, mal außer acht lassen.
”
115
Schlüssel-Wert-Listen
116
KAPITEL 7. HASHES UND ZEIGER
speichern. Auf die einzelnen Listenelemente könnten wir dann – wie gehabt –
über ihren Index zugreifen. Allein, es gibt keine Möglichkeit, den Index eines
Eintrages aus seinem Schlüsselwort zu bestimmen! All den oben genannten
Problemen ist nämlich gemein, dass keine (einfache) Zuordnung zwischen
ihren jeweiligen Schlüsseln und einer fortlaufenden Menge von natürlichen
Zahlen, die als Index fungieren könnten, möglich ist. So bliebe uns nichts
anderes übrig, als alle Listenelemente einzeln durchzugehen und jeweils zu
prüfen, ob es vielleicht das gewünschte ist – was bei langen Listen sehr
zeitaufwändig werden kann...
Glücklicherweise haben Informatikerinnen und Informatiker Möglichkeiten gefunden, solche Listen von Schlüssel-Wert-Paaren doch effizient verwalten zu können und damit schnellen Zugriff auf den zu einem Schlüssel
gehörenden Wert zu erlauben. Gemäß obigem Lexikon-Beispiel nennt man
solche Datenstrukturen tatsächlich gelegentlich Dictionaries; ebenfalls sehr
verbreitet ist auch der Ausdruck Map. Im Umfeld von Perl schließlich
wird die Bezeichnung Hash verwendet wird. hash bedeutet – neben Ha”
schisch“ – soviel wie Zerhacktes“ (siehe hash browns, kleine Kartoffelpuffer,
”
die aus dem English breakfast eben so wenig wegzudenken sind wie bacon
und baked beans, oder hashée, die französische Variante der italienischen
Hackfleischsauce bolognese) und bezieht sich darauf, dass in einem Hash
die zu aufeinander folgenden Schlüsselwörtern gehörenden Werte im Arbeitsspeicher keineswegs hintereinander liegen müssen. Aber das ist ein informatisches Detail, das einen Perl-Programmierer/eine Perl-Programmiererin
glücklicherweise nicht weiter interessieren muss.
Dictionary
Map
Hash
Tatsächlich bietet Perl alles, was zum Arbeiten mit Hashes nötig ist, als
festen Bestandteil der Sprache an. Sehen wir uns hierzu ein Beispiel an:
Listing 7.1: Hashes
1
2
3
4
5
6
7
my % latNamen = (
" Taufliege " = > " Drosophila melanogaster " ,
" Wanderratte " = > " Rattus norvegicus " ,
" Bierhefe " = > " Saccharomyces cerevisiae " ,
" Bäckerhefe " = > " Saccharomyces cerevisiae " ,
" Steinlaus " = > " unbekannt "
);
8
9
10
11
12
13
14
15
16
print ( " Der lat . Name der Taufliege lautet : " ) ;
print ( $latNamen { " Taufliege " } , " \ n " ) ;
print ( " Der lat . Name der Steinlaus hingegen ist " ) ;
print ( $latNamen { " Steinlaus " } , " \ n " ) ;
$latNamen { " Steinlaus " } = " Petrophagus loriotii " ;
print ( " Jetzt : $latNamen { Steinlaus }\ n " ) ;
$latNamen { " Michael Mustermann " } = " Homo sapiens " ;
print ( " Und der Mensch : " , $latNamen { " Michael Mustermann➘
"}, "\n");
7.1. ERSTELLEN VON HASHES
117
Erklärung: In Zeile 1 wird eine Variable des neuen Datentyps Hash“ –
”
kenntlich an dem vorangestellten %-Zeichen – deklariert und gleich mit einer
Liste von Schlüssel-Wert-Paaren initialisiert. Die Paare selbst werden in runden Klammern (...) durch Kommata getrennt aufgeführt, wobei zunächst
der Schlüssel, gefolgt von einem Zuordnungsoperator => und schließlich
dem jeweiligen Wert, angegeben wird.
%
Zuordnungsoperator
=>
Wie Sie sehen, können verschiedene Hash-Elemente durchaus den gleichen Wert – hier: Saccharomyces cerevisiae – besitzen; jeder Schlüssel
darf in einem Hash jedoch nur einmal vorkommen und ist daher
absolut eindeutig!
In Zeile 10 sehen wir, wie per Schlüssel auf einen Wert im Hash zugegriffen wird: durch Angabe des Hash-Bezeichners, gefolgt vom Schlüssel
innerhalb geschweifter Klammern {...}. Beachten Sie, dass einzelne HashElemente – ähnlich wie Listenelemente – Skalare darstellen und daher ein
$-Zeichen statt eines % vorangestellt bekommen!
Ab Zeile 13 wird demonstriert, wie Hash-Werte verändert werden können.
Dazu wird der neue Wert einfach dem links des Zuweisungs-Operators stehenden und durch seinen Schlüssel identifizierten Hash-Element zugewiesen.
Falls für den Schlüssel bereits ein gespeicherter Wert existiert, wird dieser
überschrieben (für Schlüssel gilt ja: Es kann nur einen geben!); falls nicht,
wird ein neues Hash-Element mit dem entsprechenden Schlüssel-Wert-Paar
angelegt.
Zeile 14 demonstriert weiterhin, dass Hash-Elemente in in doppelten
Anführungszeichen angegebenen Zeichenketten-Literalen erkannt und interpoliert werden; die Anführungszeichen um literal angegebene Schlüssel entfallen dabei. Leider gibt es hingegen keine einfach Methode, den Inhalt eines
gesamten Hashs gut lesbar auszugeben; print(%latNamen) gibt alle SchlüsselWert-Paare unmittelbar (d. h. ohne trennende Leerzeichen) hintereinander
aus, und Hashes in doppelten Anführungszeichen werden überhaupt nicht
interpoliert – print("%latNamen") gibt lediglich die Zeichenkette %latNamen
aus.
Um zu überprüfen, ob ein Schlüssel bereits vergeben wurde, kann man
die Perl-Funktion exists bemühen – diese liefert TRUE zurück, falls das angegebene Hash-Element bereits angelegt wurde, sonst FALSE.
exists
if ( exists ( $latNamen { $deutscherName }) ) {
print ( " Für $deutscherName wurde bereits ein \ n " ) ;
print ( " lat . Name gespeichert !\ n " ) ;
}
Perl bietet auch die Möglichkeit, ein bereits existierendes Schlüssel-WertPaar wieder aus einem Hash zu entfernen: die delete-Anweisung, der man
einfach das zu löschende Hash-Element mitteilt:
delete($latName{"Michael Mustermann"})
delete
118
KAPITEL 7. HASHES UND ZEIGER
Bei der Angabe von Hash-Schlüsseln erlaubt es Perl übrigens, die Anführungszeichen um Zeichenketten wegzulassen, solange die Zeichenkette den
Regeln zur Bildung von Bezeichnern gehorcht – statt
$hash{"key1"}
könnte man also auch
$hash{key1}
schreiben.
In Hashes können sowohl Zeichenketten als auch numerische Werte gespeichert werden; Hash-Schlüssel hingegen werden immer als Zeichenketten
interpretiert, wie das folgende Progrämmchen demonstriert:
Listing 7.2: Zahlen und Zeichenketten als Hash-Schluessel
1
2
3
4
5
6
7
8
9
my % species = () ;
$species {8472} = " Alien " ;
print ( " $species {8472}\ n " ) ;
$species {8472.0} = " Alien 2 " ;
print ( " $species {8472}\ n " ) ;
$species { " 8472 " } = " Alien 3 " ;
print ( " $species {8472}\ n " ) ;
$species { " 8472.0 " } = " Alien 4 " ;
print ( " $species {8472}\ n " ) ;
Die Programm-Ausgabe
Alien
Alien 2
Alien 3
Alien 3
zeigt, dass auch die beiden numerischen Literale 8472 und 8472.0 in Zeile
2 bzw. 4 offenbar zunächst in eine Zeichenkette umgewandelt werden, wie sie
Perl z. B. bei der Ausgabe dieser Werte auf der Konsole verwenden würde
– nämlich "8472" in beiden Fällen. Dies ist nun genau die Zeichenkette, die
auch in Zeile 6 als Schlüssel verwendet wird ("8472"). So erklärt sich, dass
jede der Zuordnungen in den Zeilen 4 und 6 den jeweils vorher gespeicherten Wert überschreibt. Beachten Sie, dass durch die Zuordnung des Wertes
Alien 4 zum Zeichenketten-Schlüssel "8472.0" in Zeile 7 trotz numerischer
Wertgleichheit mit 8472 offenbar ein anderes Hash-Element angesprochen
wird als in den vorherigen Zuordnungen – "8472.0" ist ja auch eine andere
Zeichenkette als "8472".
Fazit aus dieser Verwirrung: in einem Hash möglichst keine Zahlen und
Zeichenketten gemeinsam als Schlüssel verwenden; wenn Zahlen verwendet
werden, möglichst nur in Form von natürlichen Zahlen!
Um mit allen Elementen eines Hashes eine bestimmte Operation durchzuführen – und sei es nur, alle Schlüssel-Wert-Paare, die in einem Hash
7.1. ERSTELLEN VON HASHES
119
gespeichert sind, auszugeben – müssen wir an die Schlüssel herankommen
können. Perl bietet dazu die keys-Funktion an, die eine Liste aller Schlüssel
eines Hashes zurückliefert:
keys
@species = keys (% latNamen ) ;
foreach $species ( @species ) {
print ( " $species : $latName { $species }\ n " ) ;
}
Die Reihenfolge, in der keys die Schlüsselelemente zurückliefert, ist übrigens nicht definiert; man sollte sich also nicht darauf verlassen, dass die
Schlüssel hinterher z. B. in lexikalischer Ordnung in der Rückgabeliste stehen!2 Da jeder Schlüssel nur einmal vorkommen kann und die Reihenfolge
der Schlüssel nicht definiert ist, bilden alle Schlüssel eines Hashes übrigens
– mathematisch gesehen – eine Menge.
Ähnliches gilt übrigens auch für die values-Funktion, die alle in einem
Hash gespeicherten Werte zurückliefert:
Menge
values
@alleLatName = values(%latNamen)
Hier bekommt man übrigens wirklich alle gespeicherten Werte zurück
– wenn ein Hash z. B. drei verschiedene Schlüssel enthält, die alle auf den
gleichen Wert verweisen, wird die Rückgabeliste diesen Wert ebenfalls dreimal enthalten. Dabei ist wiederum nicht definiert, in welcher Reihenfolge die
Werte in der Liste auftauchen werden – die drei gleichen Werte werden also
nicht unbedingt z. B. direkt hintereinander in der Liste stehen! Diese beiden
Eigenschaften qualifizieren den Satz aller Werte eines Hashes übrigens als
Multimenge.
Diese Ähnlichkeit zwischen Mengen-Elementen und Hash-Schlüsseln können wir übrigens ausnutzen, um eben solche Daten zu repräsentieren, die
eine Menge bilden – bei denen also kein Wert öfter als einmal vorkommen
darf und es auf die Reihenfolge nicht ankommt. Dazu verwenden wir die
Daten einfach als Hash-Schlüssel, anstatt sie als Hash-Werte zu speichern –
ja, den zum jeweiligen Schlüssel gehörenden Hash-Wert ignorieren wir sogar
geflissentlich, d. h. wir weisen ihm irgend einen beliebigen Wert zu! Alles
was zählt ist der Schlüssel – gibt es einen Schlüssel, gehört der durch ihn
repräsentierte Wert zur Menge dazu, sonst nicht.
Als Beispiel sei folgendes kleine Programm gegeben, dass bestimmt, welche Aminosäuren in einer Peptidsequenz vorhanden sind – also die Menge
aller in der Sequenz vorkommenden Aminosäure-Typen:
2
Glücklicherweise gibt es ja zum Sortieren von Listen die sort-Funktion...
Multimenge
120
KAPITEL 7. HASHES UND ZEIGER
Listing 7.3: Aminosaeurezusammensetzung als Menge
1
2
3
print ( " Bitte Peptidsequenz eingeben : " ) ;
my $seq = < STDIN >;
chomp ( $seq ) ;
4
5
6
7
8
my % aaSet = () ;
for ( my $pos = 0; $pos < length ( $seq ) ; $pos ++) {
$aaSet { substr ( $seq , $pos , 1) } = 1;
}
9
10
11
my @aa = keys (% aaSet ) ;
print ( " Das Peptid enthält die folgenden Aminosäuren : ➘
@aa \ n " ) ;
Erklärung: Nach Eingabe der zu untersuchenden Peptidsequenz in Zeilen 1 – 3 wird in Zeile 5 ein leerer Hash deklariert und initialisiert. Ab
Zeile 6 wird die Sequenz zeichenweise durchlaufen, wobei der jeweilige Aminosäure-Code als Schlüssel benutzt wird, um dem durch ihn spezifizierten
Hash-Element einen Wert (hier: 1) zuzuweisen. Falls es das Hash-Element
bereits gibt, ändert sich nichts; falls nicht, wird es erzeugt. Am Ende der
Schleife sollte der Hash dann für jeden in der Peptidsequenz vorkommenden Aminosäure-Code ein Hash-Element besitzen. Dessen Schlüssel lassen
wir uns dann in Zeile 10 mittels der keys-Funktion geben und geben sie
schließlich in Zeile 11 auf der Konsole aus.
Für die Simulation des Datentyps Menge“ mit Hashes ist es, wie gesgt,
”
völlig belanglos, welchen Wert man den einzelnen Hash-Elementen zuweist
– sogar undef wäre eine Möglichkeit. Denn auch durch die Zuweisung
$menge{"Schlüssel"} = undef
wird ein Schlüssel erzeugt.
Hashes können übrigens auch aus Listen erzeugt werden:
%hash = ("a", 1, "b", 2, "c", 3)
ist äquivalent mit
%hash = ("a" => 1, "b" => 2, "c" => 3)
Dabei werden die Listenelemente mit geradem Index (beginnend bei 0!)
als Schlüssel, die unmittelbar auf die Schlüssel folgenden Listenelemente (mit
ungeradem Index) als zugehörige Werte interpretiert. Dabei obliegt es dem
Programmierer/der Programmiererin darauf zu achten, dass die als Schlüssel
verwendeten Werte wirklich nur einmal vorkommen – sonst können unvorhergesehene Dinge passieren. Ob der damit verbundenen Risiken werden wir
diese Art der Hash-Konstruktion in diesem Kurs nicht weiter verwenden.
Die umgekehrte Umwandlung eines Hashes in eine Liste ist hingegen
völlig problemlos und wird auch implizit angewendet, um einen Hash einer
Subroutine als Argument zu übergeben:
7.1. ERSTELLEN VON HASHES
121
& hashRoutine (% someHash ) ;
...
sub hashRoutine {
my % hash = @_ ;
...
}
Hier darf man übrigens beruhigt sein – da mit dem Hash vor der Übergabe
alles in Ordnung war, wird auch der nach der Übergabe innerhalb der Subroutine aus der Argumentliste erzeugte lokale Hash keine doppelten Schlüssel
enthalten.
Auch die Rückgabe von Hashes als Ergebnis eines Subroutinen-Aufrufs
ist unproblematisch:
% someHash = & hashFunction ()
...
sub hashFunction {
my % hash = ()
...
return % hash
}
Perl wäre nicht Perl, wenn es nicht auch Spezialvariablen in Form von
Hashes anbieten würde. Sehr praktisch ist z. B. die Spezialvariable %ENV,
in die Perl die Werte aller Shell-Umgebungsvariablen (engl. environment
variables) ablegt – all die Schlüssel-Wert-Paare, die Sie sich z. B. per
~$ env | sort Enter alphabetisch sortiert anzeigen lassen können. So können Sie z. B. mit
$ENV{PATH} Ihren Suchpfad für ausführbare Dateien und mit $ENV{HOME} den
absoluten Pfad zu Ihrem Home-Verzeichnis ermitteln. So können Sie auch
das in Abschnitt 4.1 ab S. 63 angesprochene Problem umgehen, dass der
open-Befehl mit der Tilde ~ als Kurzbezeichner für das Home-Verzeichnis
nichts anfangen kann – führende Tilden einfach in jeden Pfad vor Verwendung per
$path =~ s/^~(\/.*)?$/$ENV{HOME}$1/
in den absoluten Home-Pfad umwandeln. (Überlegen Sie mal, wie obiger
Ausdruck mit Pfadangaben wie ~, ~/, ~file oder ~/foo/bar/file umgehen
würde!)
%ENV
122
KAPITEL 7. HASHES UND ZEIGER
Übungen
7.1 Schreiben Sie ein Programm, das die Molekularmasse eines
Polypeptids berechnet! Implementieren Sie zu diesem Zweck
eine Subroutine, die die benötigten Daten aus der Datei
~/perl4bio/data/Mw_AA.csv einliest, die Einträge der Art
A, 89
C, 121
D, 132
...
enthält (also IUPAC-Code , Molekularmasse). Verwenden
Sie %ENV, um vor dem Öffnen den absoluten Pfad zu Mw_AA.csv
zu ermitteln. Stellen Sie dann die in der Datei enthaltenen Daten
dem Hauptprogramm in Form eines Hashes zur Verfügung. Und
denken Sie daran, dass bei Ausbildung einer Peptidbindung ein
Molekül Wasser abgespalten wird.
7.2 Schreiben Sie ein Programm, das zählt, wie oft die verschiedenen Aminosäuren in einer Polypeptid-Sequenz vorkommen und
die Zählergebnisse ausgibt! Verwenden Sie einen Hash, um die
Zählerstände für die Aminosäure-Typen zu speichern. (Denken
Sie daran, dass wir mit dem Pragma warnings arbeiten, wodurch
eine Initialisierung von Variablen vor ihrer ersten Verwendung erzwungen wird. Dementsprechend müssen Sie auch die als Zähler
verwendeten Hash-Elemente bei ihrer ersten Verwendung initialisieren.)
7.3 Entwerfen Sie eine Subroutine, die eine Liste aller Schlüssel eines übergebenen Hashes zurückgibt, die auf einen (ebenfalls als
Argument übergebenen) Wert verweisen.
7.2
Zeiger
Mit Skalaren, Listen und Hashes kennen wir nun all die praktischen Datentypen, die Perl bereitstellt, um dem Programmierer/der Programmiererin
das Leben zu erleichtern. Gerade Hashes stellen eine sehr mächtige Datenstruktur dar – und doch gibt es noch einige (eigentlich sogar ziemlich viele)
Problemstellungen, die sich mit den drei Standard-Datentypen nicht wirklich elegant lösen lassen. Hier ein Beispiel:
7.2. ZEIGER
123
Wir wollen eine Subroutine schreiben, die eine Liste von Sequenzen
nach bestimmten Motiven durchsucht, die wir ebenfalls in Form einer Liste
übergeben wollen. Als Rückgabewert wollen wir für jede Sequenz wissen,
wie oft jedes der Muster in der Sequenz gefunden wurde. Schreiben wir eine
grobe Implementierungs-Idee in einer Art Mischung zwischen Programmierund Umgangssprache ( Pseudocode“) auf:
”
sub
>
>
>
multipleSeqPatternScan {
Liste der übergebenen Sequenzen @seqs
und Liste der Muster @patterns aus
Argumentliste @_ extrahieren
# alle Sequenzen durchgehen
foreach my $seq ( @seqs ) {
# alle Muster durchgehen
foreach my $pattern ( @patterns ) {
# Anzahl Treffer des Musters in Sequenz finden
my $nMatches = ( $seq =~ s /( $pattern ) / $1 / gi ) ;
> Speichern , wie oft ( $nMatches ) $pattern
> in $seq gefunden wurde
}
}
> Für jede Sequenz alle gefundenen
> Muster zurückgeben
}
So weit, so gut. An ein paar Stellen geraten wir jedoch mit der Implementierung ins Stocken. Das beginnt bereits bei der Extraktion der Sequenzen
und Muster aus der Argumentliste @_. Denn wenn wir die Subroutine etwa
wie folgt
&multipleSeqPatternScan(@sequenzen, @muster)
aufrufen, wird es uns schwer fallen, innerhalb der Subroutine zu entscheiden, welche der Elemente von @_ ursprünglich aus der @sequenzen-Liste und
welche von @pattern stammen. Diesem Problem sieht man sich übrigens immer gegenüber, wenn man mehr als eine der komplexeren Datenstrukturen
Liste oder Hash als Argumente für eine Subroutine verwenden will.
Was also ist zu tun? Eine mögliche Antwort könnte lauten: Als zusätzliches Argument die Länge einer der beiden Listen übergeben – dann lassen
sich die beiden übergebenen Listen innerhalb der Subroutine aus @_ z. B.
unter Verwendung der splice-Funktion rekonstruieren:
124
KAPITEL 7. HASHES UND ZEIGER
& m u l t i p l e S e q P a t t e r n S c a n ( scalar ( @sequenzen ) , @sequenzen ,➘
@muster )
...
sub m u l t i p l e S e q P a t t e r n S c a n {
my $n = shift ( @_ ) ;
my @seqs = splice ( @_ , 0 , $n ) ;
my @patterns = @_ ;
...
}
Zeiger
Pointer
Referenz
Adress-Operator
\
Eleganter (und, wie wir noch sehen werden, auch effizienter) geht es
allerdings, wenn wir nicht die Listen selbst übergeben, sondern die Speicheradressen, an denen der Listeninhalt im Arbeitsspeicher zu finden ist!
Speicheradressen sind nichts weiter als Zahlen und finden somit bequem
in skalaren Variablen Platz; eine skalare Variable, die eine Speicheradresse
enthält, wird auch Zeiger , Pointer oder Referenz genannt.
Wie ermitteln wir nun, ab welcher Adresse die in einer Liste gespeicherten Daten im Arbeitsspeicher abgelegt sind? Unter Zuhilfenahme des des
Adress-Operators \:
$seqRef = \ @sequenzen ;
$musRef = \ @muster ;
Die Skalare $seqRef bzw. $musRef zeigen dann auf die eigentlichen Daten,
die in @sequenzen bzw. @muster stehen – was hier anhand ersteren Beispiels
noch einmal visualisiert sei:
@sequenzen
$seqRef
=
("gattaca", "gatatc", ...)
ր
Dass die so erzeugten Zeiger tatsächlich auf Speicherzellen verweisen, in
denen Listendaten stehen, können Sie sehen, wenn Sie einen solchen Zeiger
einfach mal ausdrucken:
print("$seqRef\n")
erzeugt so etwas wie
ARRAY(0x8171298)
als Ausgabe – wobei der Wert in der Klammer (hier: 0x8171298) die
Adresse der Speicherzelle darstellt, ab der die einzelnen, in @sequenzen gespeicherten Zeichenketten im Speicher stehen. (Der genaue Zahlenwert ist
natürlich vom momentanen Zustand Ihres Systems abhängig.)
Da Zeiger Skalare darstellen, können wir sie nach Übergabe an unsere
Subroutine problemlos aus der Argumentliste @_ extrahieren. Mit ihrer Hilfe
können wir dann innerhalb der Subroutine auf die Elemente der Ausgangslisten zugreifen. Im Folgenden verzichten wir zudem noch auf das Zwischenspeichern der Zeiger in skalaren Variablen, sondern ermitteln sie beim Aufruf
125
7.2. ZEIGER
der Subroutine direkt mittels des Adressoperators aus den zu übergebenden
Listen:
1
& m u t l i p l e S e q P a t t e r n S c a n (\ @sequenze , \ @muster )
2
3
...
4
5
6
7
sub m u l t i p l e S e q P a t t e r n S c a n {
my $seqRef = shift ( @_ ) ;
my $pattRef = shift ( @_ ) ;
8
# alle Sequenzen durchgehen
foreach my $seq ( @ { $seqRef }) {
# alle Muster durchgehen
foreach my $pattern ( @ { $pattRef }) {
# Anzahl Treffer des Musters in Sequenz finden
my $nMatches = ( $seq =~ s /( $pattern ) / $1 / gi ) ;
9
10
11
12
13
14
15
> Speichern , wie oft ( $nMatches ) $pattern
> in $seq gefunden wurde
16
17
}
18
}
19
20
> Für jede Sequenz alle gefundenen
> Muster zurückgeben
21
22
23
}
Die Subroutine würde dann folgende Skalare empfangen und in ihrer
Argumentliste bereitstellen:3
@sequenzen =
("gattaca", ...)
@_ =
⌢
$_[0]
ր
@muster =
("aac", "gct", ...)
$_[1]
ր
⌣
In der Subroutine fangen wir zunächst die beiden Zeiger in lokalen skalaren Variablen auf (Zeilen 6 und 7). In Zeile 10 sehen wir, wie wir die Zeiger
sozusagen in Listen zurückverwandeln“ können: durch Umschließen der die
”
Zeiger enthaltenden Skalare mit @{...}4 . Diesen Vorgang, bei dem aus ei3
Aus Platzgründen werden die Elemente der Argumentliste @_ hier in vertikaler Anordnung aufgeführt.
@{...}
126
KAPITEL 7. HASHES UND ZEIGER
nem Zeiger die ursprüngliche Datenstruktur wiederhergestellt wird, nennt
man übrigens Dereferenzierung .
Dereferenzierung
Nun der zweite Knackpunkt: wie wollen wir die Daten zurückgeben?
Schön wäre eine Liste, in der wir für jede Sequenz eine Liste der gefundenen
Motive aufnehmen könnten – zusammen mit der Zusatz-Information, wie oft
das jeweilige Motiv gefunden wurde. Das klingt nach der Verwendung von
Hashes – aber eine Liste von Hashes geht sicher nicht, da als Listenelemente
lediglich Skalare zugelassen sind. Doch halt – wenn Zeiger auf Listen Skalare
darstellen, wie ist es dann mit Zeigern auf Hashes?
Tatsächlich können wir mittels des Adress-Operators auch einen HashZeiger erhalten, wenn wir ihn auf einen Hash anwenden:
$hashRef = \%hash
Wir könnten also in unserer Subroutine für jede Sequenz einen Hash
erzeugen und mit dessen Hilfe die Anzahl Treffer für jeden Mustertyp zählen:
Listing 7.4: Subroutine zur Suche von Mustern in Sequenzen
1
2
3
sub m u l t i p l e S e q P a t t e r n S c a n {
my $seqRef = shift ( @_ ) ;
my $pattRef = shift ( @_ ) ;
4
# Ergebnisliste
my @result = () ;
# alle Sequenzen durchgehen
foreach my $seq ( @ { $seqRef }) {
# Hash für Treffer in Seq
my % matchesInSeq = () ;
# alle Muster durchgehen
foreach my $pattern ( @ { $pattRef }) {
# Anzahl Treffer des Musters in Sequenz finden ...
my $nMatches = ( $seq =~ s /( $pattern ) / $1 / gi ) ;
# ... und speichern
$matchesInSeq { $pattern } = $nMatches ;
}
push ( @result , \% matchesInSeq ) ;
}
return @result ;
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
}
Fertig! Nun können wir unsere Subroutine mit Zeigern auf je eine Liste
von Sequenzen und von Mustern aufrufen und erhalten eine nach Sequenzen geordnete Liste der Trefferzahlen zurück – was man sich etwa wie folgt
vorstellen kann:
4
Die geschweiften Klammern sind übrigens optional – statt @{$arrayRef} könnte man
also auch @$arrayRef schreiben. Der Übersichtlichkeit halber werden wir die Klammern
hier jedoch immer verwenden.
127
7.2. ZEIGER
@treffer =
⌢
$treffer[0]
$treffer[1]
$treffer[2]
..
.
⌣
→
→
→
("acc" => 3, "gat" => 1 ...)
("acc" => 1, "gat" => 2 ...)
("acc" => 2, "gat" => 1 ...)
Interessant ist dabei noch, dass die einzelnen Hash-Zeiger in der @trefferListe auf Hash-Daten zeigen, die innerhalb der Subroutine – ja, sogar innerhalb eines Blocks innerhalb der Subroutine – als lokale Hashes (%trefferInSeq)
erzeugt wurden! Außerhalb der Subroutine können wir also nicht mehr auf
die ursprünglichen Hashes zugreifen – aber die Speicherung von Zeigern auf
diese Hashes in der Rückgabeliste bewahrt die in den Hashes abgelegten
Daten vor dem Vergessen.
Diese in der Liste gespeicherten Hash-Zeiger können wir dann z. B. wie
folgt benutzen, um an die ursprünglichen Hash-Daten heranzukommen:
Listing 7.5: Aufruf der Mustersuch-Subroutine
1
my @treffer = & m u l t i p l e S e q P a t t e r n S c a n (\ @sequenzen , \➘
@muster ) ;
2
3
4
5
6
7
8
9
10
11
for ( my $i = 0; $i < scalar ( @treffer ) ; $i ++) {
print ( " Treffer in Sequenz Nr . $i :\ n " ) ;
my $trefferInSeq = $treffer [ $i ];
my @musterInSeq = keys (%{ $trefferInSeq }) ;
foreach my $muster ( @musterInSeq ) {
print ( " $muster wurde " , $trefferInSeq - >{ $muster }) ;
print ( " mal gefunden .\ n " ) ;
}
}
Wir verwenden eine for-Schleife statt eines foreach, um beim Durchgehen der Treffer in Zeile 4 die laufende Nummer der Sequenz ($i) ausgeben zu
können. In Zeile 5 speichern wir die individuelle Treffer-Liste für die jeweilige Sequenz – die ja in Form eines Zeigers auf einen Hash vorliegt! – in einer
skalaren Variablen namens $trefferInSeq zwischen. In Zeile 7 lassen wir uns
die Liste aller in der Sequenz gefundenen Motive mittels der keys-Funktion
geben, die wir auf die dereferenzierte Treffer-Liste (%{$trefferInSeq}, Zeile 6) anwenden. Nun gehen wir in der Schleife ab Zeile 8 alle gefundenen
Motive durch und geben aus, wie oft das jeweilige Muster gefunden wurde.
Dabei dereferenzieren wir in Zeile 8 nicht den gesamten Treffer-Hash, sondern greifen mittels des Element-Dereferenzierungs-Operators -> auf
einzelne Elemente der durch den Zeiger beschriebenen Hashes zu.
%{...}
ElementDereferenzierungsOperator
->
128
KAPITEL 7. HASHES UND ZEIGER
Dies funktioniert übrigens ganz ähnlich auch mit Zeigern auf Listen:
mittels
$listRef->[$i]
kann auf das $i-te Element einer durch einen Listen-Zeiger $listRef
beschriebenen Liste zugegriffen werden.
Sowohl Listen-, als auch Hash-Zeiger können übrigens direkt – und nicht
nur mittels des Adress-Operators aus bestehenden Listen bzw. Zeigern –
erzeugt werden. So könnten die im Folgenden
@liste = ( " a " , " b " , " c " ,) ;
$listRef = \ @liste ;
% hash = ( " a " = > 1 , " b " = > 2 , " c " = > 3) ;
$hashRef = \% hash
erzeugten Zeiger auch per
$listRef = [ " a " , " b " , " c " ]
$hashRef = { " a " = > 1 , " b " = > 2 , " c " = > 3}
anonyme Liste
anonymer Hash
zugleich deklariert und initialisiert werden. Da hier keine eigentlichen
Listen- bzw. Hash-Variablen angelegt werden, spricht man auch von anonymen Listen bzw. anonymen Hashes.
Die Syntax von Zeiger-Operationen sei im Folgenden noch einmal im Vergleich zu Operationen mit normalen“ Listen und Zeigern zusammengefasst
”
(s. auch perlref-Hilfe-Seite):
Tabelle 7.1: Listen und Listen-Zeiger
Operation
Liste
Listen-Zeiger
Deklaration und
Initialisierung
Zugriff auf Liste
(Beispiel)
Zugriff auf
Elemente
@list = (...)
$listRef = [...] (oder
$listRef = \@list)
chomp(@list)
chomp(@{$listRef})
$list[$n]
$listRef->[$n]
129
7.2. ZEIGER
Tabelle 7.2: Hashes und Hash-Zeiger
Operation
Hash
Hash-Zeiger
Deklaration und
Initialisierung
Zugriff auf Hash
(Beispiel)
Zugriff auf
Elemente
%hash = (...)
$hashRef = {...} (oder
$hashRef = \%hash)
@k = keys(%hash)
@k = keys(%{$hashRef})
$hash{$key}
$hashRef->{$key}
Da sowohl Listen als auch Hashes beliebige Skalare als Elemente aufnehmen können und Zeiger nun mal Skalare sind, können Sie mit Zeigern
beliebig verschachtelte Datenstrukturen erstellen (s. auch unter perldsc in
der Perl-Hilfe). Das mag zunächst furchtbar kompliziert und abschreckend
erscheinen – doch lassen sich viele Probleme mit Zeigern sehr elegant und
einfach lösen, so dass man sie nach einiger Zeit nicht mehr missen möchte.
Außerdem haben Sie bereits den halben Weg in Richtung objektorientiertes Programmieren zurückgelegt – dort wird nämlich auch massiv von
Zeigern gebrauch gemacht, wenn auch meistens in einer eher versteckten
Art und Weise.
In diesem Zusammenhang noch ein wenig Terminologie: Werden die Daten, die in einer Subroutine verarbeitet werden sollen, direkt übergeben,
spricht man auch von einem Aufruf mit Wertübergabe (call by value).
Innerhalb der Subroutine wird dabei eine Kopie der zu verarbeitenden Werte angelegt, und diese Kopie ist es, die Perl in der Argumentliste @_ einer
Subroutine zur Verfügung stellt. Veränderungen, die an diesen Werten vorgenommen werden, haben keinerlei Einfluss auf die Originalwerte, die ja außerhalb der Subroutine residieren. Übergeben wir beim Subroutinen-Aufruf
hingegen einen Zeiger auf eine Datenstruktur – man spricht von call by reference –, verweist dieser Zeiger auf das Original, und alle Veränderungen,
die wir an der referenzierten Datenstruktur durchführen, wirken sich daher
auch auf den aufrufenden Programmteil aus. Man sollte sich also ob dieser
Seiteneffekte ganz genau überlegen, ob und welche Veränderungen man an
by reference-übergebenen Daten vornimmt! Generell ist es zweckmäßig, Subroutinen so zu programmieren, dass sie sich entweder wie klassische Funktionen oder wie Prozeduren verhalten:
objektorientierte
Programmierung
call by value
call by reference
Seiteneffekte
Funktion
Prozeduren
130
KAPITEL 7. HASHES UND ZEIGER
Tipp 7.1: Entweder rein oder raus!
Versuchen Sie Ihre Subroutinen möglichst nach einem der folgenden beiden Strickmuster zu programmieren:
1. Funktion – Keine Veränderungen an by reference übergebenen Argumenten vornehmen; Verarbeitungsergebnis mittels return an den
Aufrufer zurückgeben
2. Prozedur – Kein Rückgabewert per return; Veränderung von by
reference übergebenen Argumenten – bei Dokumentation der Seiteneffekte! – erlaubt
${...}
Die Tatsache, dass bei der Übergabe by reference auch bei großen Datenstrukturen lediglich ein kleiner (wenige Bytes großer) Zeiger übergeben
wird, macht sich auch in der Ausführungsgeschwindigkeit des SubroutinenAufrufs bemerkbar – muss doch beim Übergeben einer langen Liste by value
die gesamte Liste kopiert werden! Bei der Übergabe von sehr langen Zeichenketten kann es sich übrigens lohnen, auch hier nur einen Zeiger auf die
skalare Variable, die die Zeichenkette enthält, zu übergeben. Wie Tabelle
7.3 zeigt, erhalten wir einen Zeiger auf einen Skalar – wie gehabt – mittels
des Adress-Operators, und die Dereferenzierung erfolgt wiederum mittels
geschweifter Klammern, diesmal mit vorangestelltem $.
Tabelle 7.3: Skalare und Skalar-Zeiger
Operation
Skalar
Skalar-Zeiger
Deklaration und
Initialisierung
Zugriff auf Skalar
(Beispiel)
$scal = "gattaca"
$scalRef = \"gattaca"
(oder \$scal)
print($scal)
print(${$scalRef})
Übungen
7.4 Schreiben Sie ein Programm, das eine Subroutine enthält, die die
Schnittmengen-Operation mit Hashes implementiert – die also
aus zwei (wie?) übergebenen Hashes einen weiteren Hash erzeugt
und zurückgibt, der nur diejenigen Schlüssel enthält, die in beiden
Ausgangs-Hashes enthalten waren.
7.2. ZEIGER
131
Aufgaben
7.1 Programme benötigen zu ihrem ordnungsgemäßem Funktionieren häufig noch bestimmte Informationen über die Konfiguration
des Systems, unter dem sie ausgeführt werden sollen. Diese
Informationen werden den Programmen meist in Form von
Textdateien (häufig mit der Endung .ini, .cnf, .conf oder rc)
zur Verfügung gestellt. Solche Dateien enthalten oft SchlüsselWert-Paare der Form
db_path = /usr/local/embl
alignment_tool = clustalw
optimized_alignment = yes
...
Schreiben Sie eine Subroutine, die eine solche Datei (etwa
aufgabe7.1.ini in ~/perl4bio) ausliest und die enthaltenen
Schlüssel-Wert-Paare als Hash zurückgibt. Denken Sie daran,
überzählige Leer- und Tabulatorzeichen aus Schlüsseln und
Werten zu entfernen!
7.2 Schreiben Sie ein Programm, das eine Nucleotid-Sequenz in eine
Aminosäure-Sequenz übersetzt (translatiert). Implementieren Sie
dazu eine Subroutine, die die zu übersetzende Sequenz als Zeichenkette sowie den zu verwendenden genetischen Code als Hash
übergeben bekommt. (Falls Sie keine Lust haben, den Hash im
Hauptprogramm durch explizite Angabe aller 64 Codons samt
zugehöriger Aminosäure-Reste zu initialisieren, lesen Sie den Code aus der Datei genetic_code_1.csv bzw. genetic_code_3.csv
im perl4bio/data-Verzeichnis ein – je nachdem, ob sie den Einoder Dreibuchstaben-Code für Aminosäuren bevorzugen.)
7.3 Leider ist es wegen der Degeneriertheit des genetischen Codes nicht möglich, von einer Protein-Sequenz eindeutig auf die
zugrunde liegende DNA-Sequenz zu schließen. Schreiben Sie
dennoch ein Programm, dass ein solche reverse Translation“
”
durchführt, indem es zu jedem Aminosäure-Rest alle möglichen
Codons ausgibt. (Überlegen Sie sich eine anschauliche Art der
Ausgabe des Übersetzungs-Ergebnisses!) Verwenden Sie einen
Hash, der für jeden Aminosäure-Typ einen Zeiger auf eine Liste
enthält, die die jeweiligen Codons enthält. Die zum Aufbau dieser Datenstruktur nötigen Informationen können Sie – wie in der
vorherigen Aufgabe – auch gerne aus einer Datei einlesen.
132
KAPITEL 7. HASHES UND ZEIGER
Kapitel 8
Biomolekulare Datenformate
Nach Studium dieses Kapitels können Sie
• die wichtigsten Anlaufstellen für biomolekulare Sequenz- und Strukturdaten nennen
• Einträge, die aus der EMBL-Nukleotid-Datenbank stammen, in Ihren
Perl-Programmen einlesen und weiterverarbeiten
• Strukturinformationen aus Protein Databank-Dateien extrahieren und
in Ihren Perl-Programmen nutzen
8.1
Biomolekulare Datenbanken
Die jährliche Datenbank-Sonderausgabe der Zeitschrift Nucleic Acids Research berichtet von einer (noch immer steigenden) Zahl von mehreren hundert, z. T. sehr spezialisierten biomolekularen Datenbanken, die der wissenschaftlichen Gemeinschaft von den verschiedensten Institutionen angeboten
werden. Hier nur eine kleine Auswahl an Instituten und den von ihnen angebotenen Datenbanken, die heutzutage jede Biowissenschaftlerin und jeder
Biowissenschaftler kennen sollte; für tiefergehende Informationen sei auf die
Vorlesungen Molekularbiologische Datenbanken“ verwiesen, die jedes Se”
mester für verschiedene Zielgruppen stattfinden.
• Hinxton ist ein verschlafenes Nest in der Nähe von Cambridge, in dem
es einen Pub und eine Bushaltestelle gibt, an der so selten jemand einsteigen möchte, dass Wartende vom Busfahrer gerne übersehen werden, wenn sie nicht rechtzeitig aufspringen und durch exzessives Win133
134
EBI
FastA
EMBL
ensembl!
NCBI
PubMed
OMIM
Blast
GenBank
Entrez
SwissProt
RCSB
PDB
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
ken auf sich aufmerksam machen. Aber der Schein des Hinterwäldlerischen trügt: In Hinxton liegt – gleich neben dem vom Wellcome Trust (die größte britische Forschungsfinanzierungseinrichtung) betriebenen Sanger-Genomforschungs-Zentrum – das European Bioinformatics Institute (EBI , http://www.ebi.ac.uk), das Teil des verteilten Europäischen Molekularbiologischen Laboratoriums (EMBL) ist.
Hier residiert – neben zahlreichen anderen, per WWW verfügbaren
Datenbanken und Analyseprogrammen wie dem lokalen AlignmentProgramm FastA, von dem das bereits vorgestellte .fasta-Format
seinen Namen hat – die EMBL-Sequenzdatenbank . Außerdem ist
Hinxton die Heimat von ensembl! (www.ensembl.org), dem europäischen Portal für komplette Genomsequenzen.
• Der im 17. Jahrhundert von ausgewanderten englischen Katholiken
gegründete US-Bundesstaat Maryland ist die Heimat des National
Center for Biotechnology Information (NCBI ), des großen amerikanischen Bioinformatik-Zentrums. Es gehört zur National Library of
Medicine, die ihrerseits der US-Gesundheitsbehörde (National Institute of Health) unterstellt ist und liegt nur ca. 5 Meilen von Washington D. C. entfernt. Die bekanntesten Web-Angebote des NCBI sind
wohl der Zugang zur medizinischen Literaturdatenbank PubMed , die
Datenbank über genetisch bedingte Krankheiten (OMIM , von online
Mendelian inheritage in men), das lokale Alignment- und Datenbanksuchprogramm Blast und die Sequenzdatenbank GenBank . Alle Angebote sind von der NCBI-Startseite http://www.ncbi.nlm.nih.gov über
das Web-Interface Entrez aus zu erreichen.
• Am über Basel, Genf, Lausanne und andere Orte verteilten Schweizer Institut für Bioinformatik wird echte Schweizer Präzisionsarbeit
geleistet: Hier wird u. a. die Protein-Datenbank SwissProt gepflegt
(Zugriff unter http://www.expasy.org), in die nur solche Informationen aufgenommen werden, die den strengen Qualitätskontrollen der
SwissProt-Mitarbeiterinnen und -Mitarbeiter genügen. Während sozusagen Hinz und Kunz Daten in EMBL und GenBank einspeisen
kann, wird hier manuell die Spreu vom Weizen getrennt.
• Ebenfalls über mehrere Orte verteilt sind die Institute der Research
Collaboratory for Structrural Bioinformatics RCSB, die die Protein Databank PDB (http://www.rcsb.org/pdb) betreiben – die erste
Adresse für 3D-Strukturdaten. In dieser von der Rutgers State University of New Jersey, dem San Diego Supercomputer Center der Universität von Kalifornien und dem ganz in der Nähe des NCBI gelegenen
Center for Advanced Research in Biotechnology betriebenen Datenbank landen sowohl experimentell (per Röntgenbeugung oder Kernspinresonanz) bestimmte als auch theoretisch berechnete Strukturen.
8.2. BIOPOLYMERSEQUENZEN
135
Dabei werden – entegen dem, was der Name der Datenbank zunächst
nahelegt – nicht nur Proteinstrukturen, sondern auch die räumlichen
Strukturen von Oligonucleotiden und Protein-Ligand-Komplexen berücksichtigt.
Alle erwähnten Datenbanken lassen sich – komplett oder in Form von
Auszügen – in Form großer Textdateien (auch flat files genannt) per WWW
oder FTP herunterladen. Zudem nutzen auch zahlreiche Bioinformatik-Programme Formate wie GenBank, EMBL oder PDB, um eigene Daten und
Berechnungsergebnisse zu speichern. Daher wollen wir uns im Folgenden anhand zweier dieser Formate exemplarisch damit beschäftigen, wie wir Daten,
die in diesen Formaten vorliegen, mit unseren eigenen Perl-Programmen verarbeiten können. Dabei geht es nicht primär darum, wie man die einzelnen
Zeilen einer Datei ausliest – das können Sie ja schon aus dem Handgelenk –,
sondern vielmehr darum, die in den Datensätzen enthaltenen Informationen
z. B. innerhalb einer Subroutine zu extrahieren und in einer Form verfügbar
zu machen, die dem aufrufenden Programm einen leichten Zugriff darauf
gewährt.1
8.2
Biopolymersequenzen
Ob man Sequenzdaten vom EBI, vom NCBI oder dem japanischen Pendant,
der DNA Databank of Japan (DDBJ ), herunterlädt, ist letztenendes Geschmacksache – die drei Großen“ gleichen ihren Datenbestand täglich ab,
”
so dass sie eigentlich alle das Gleiche anbieten, wenn auch in verschiedenen
Formaten. Als überzeugte Europäerinnen und Europäer werden wir uns im
Folgenden jedoch auf das EMBL-Format beschränken.
Werfen wir also einen Blick auf einen Beispiel-Eintrag, wie man ihn bei
einer Suche in der EMBL-Datenbank als Ergebnis erhalten mag2 :
ID
XX
AC
XX
SV
XX
DT
DT
XX
DE
XX
KW
XX
OS
OC
OC
XX
RN
HSC3AAREC
standard ; mRNA ; HUM ; 1449 BP .
Z73157 ;
Z73157 .1
01 - JUL -1996 ( Rel . 48 , Created )
28 - JAN -1998 ( Rel . 54 , Last updated , Version 5)
H . sapiens mRNA for C3a anaphylatoxin receptor
C3a anaphylatoxin receptor .
Homo sapiens ( human )
Eukaryota ; Metazoa ; Chordata ; Craniata ; Vertebrata ; Eu teleostomi ; Mammalia ;
Eutheria ; Primates ; Catarrhini ; Hominidae ; Homo .
[1]
1
Wie bereits erwähnt, nennt man ein solches Vorgehen auch Parsen.
und der als C3aR-Homo_sapiens.embl im perl4bio/data-Verzeichnis des KursPakets zu finden ist
2
flat files
DDBJ
136
RP
RX
RX
RA
RA
RT
RT
RL
XX
RN
RC
RA
RT
RL
RL
RL
XX
RN
RP
RA
RT
RL
RL
RL
XX
DR
DR
XX
FH
FH
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
FT
XX
SQ
//
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
1 -1449
MEDLINE ; 96350520.
PUBMED ; 8765043.
Crass T . , Raffetseder U . , Martin U . , Grove M . , Klos A . , Koe hl J . ,
Bautsch W .;
" Expression cloning of the human C3a anaphylatoxin recep tor ( C3aR ) from
differentiated U -937 cells ";
Eur . J . Immunol . 26(8) :1944 -1950(1996) .
[2]
Revised by [3]
Bautsch W .;
;
Submitted (07 - JUN -1996) to the EMBL / GenBank / DDBJ datab ases .
Crass T . , Hannover Medical School , Institute of Medical M icrobiology ,
Carl - Neuberg - Str . 1 , Hannover , Germany , 30625
[3]
1 -1449
Bautsch W .;
;
Submitted (28 - JAN -1998) to the EMBL / GenBank / DDBJ datab ases .
Bautsch W . , Hannover Medical School , Institute of Medica l Microbiology ,
Konstanty - Gutschow - Str . 8 , Hannover , Germany , 30625
GOA ; Q16581 .
SWISS - PROT ; Q16581 ; C3AR_HUMAN .
Key
Location / Qualifiers
source
1..1449
/ db_xref =" taxon :9606"
/ mol_type =" mRNA "
/ organism =" Homo sapiens "
/ clone =" pTC10 "
/ cell_type =" myelomonocytic cell line "
/ cell_line =" U -937"
1..1449
/ db_xref =" GOA : Q16581 "
/ db_xref =" SWISS - PROT : Q16581 "
/ evidence = EXPERIMENTAL
/ note =" G - protein coupled receptor ; coding sequence of a 4.5
kbp cDNA clone "
/ product =" C3a anaphylatoxin receptor "
/ function =" receptor for proinflammatory complement sp lit
product C3a "
/ protein_id =" CAA97504 .1"
/ translation =" M A S F S A E T N S T D L L S Q P W N E P P V I L S M V I L S L T F L L G L P G N G L V L
WVAGLKMQRTVNTIWFLHLTLADLLCCLSLPFSLAHLALQGQWPYGRFLCKLIPSIIVL
NMFASVFLLTAISLDRCLVVFKPIWCQNHRNVGMACSICGCIWVVAFVMCIPVFVYREI
FTTDNHNRCGYKFGLSSSLDYPDFYGDPLENRSLENIVQPPGEMNDRLDPSSFQTNDHP
WTVPTVFQPQTFQRPSADSLPRGSARLTSQNLYSNVFKPADVVSPKIPSGFPIEDHETS
PLDNSDAFLSTHLKLFPSASSNSFYESELPQGFQDYYNLGQFTDDDQVPTPLVAITITR
LVVGFLLPSVIMIACYSFIVFRMQRGRFAKSQSKTFRVAVVVVAVFLVCWTPYHIFGVL
SLLTDPETPLGKTLMSWDHVCIALASANSCFNPFLYALLGKDFRKKARQSIQGILEAAF
SEELTRSTHCPSNNVISERNSTTV "
CDS
Sequence 1449 BP ; 331
atggcgtctt tctctgctga
cccccagtaa ttctctccat
aatgggctgg tgctgtgggt
ttcctccacc tcaccttggc
cacttggctc tccagggaca
atcattgtcc tcaacatgtt
tgtcttgtgg tattcaagcc
tctatctgtg gatgtatctg
cgggaaatct tcactacaga
tcattagatt atccagactt
gttcagccgc ctggagaaat
catccttgga cagtccccac
tcactcccta ggggttctgc
cctgctgatg tggtctcacc
agcccactgg ataactctga
tctagcaatt ccttctacga
ggccaattca cagatgacga
ctagtggtgg gtttcctgct
ttccgaatgc aaaggggccg
gtggtggtgg ctgtctttct
ttgcttactg acccagaaac
attgctctag catctgccaa
gattttagga agaaagcaag
gagctcacac gttccaccca
actgtgtga
A ; 380 C ; 310 G ; 428 T ; 0 other ;
gaccaattca actgacctac tctcacagcc
ggtcattctc agccttactt ttttactggg
ggctggcctg aagatgcagc ggacagtgaa
ggacctcctc tgctgcctct ccttgccctt
gtggccctac ggcaggttcc tatgcaagct
tgccagtgtc ttcctgctta ctgccattag
aatctggtgt cagaatcatc gcaatgtagg
ggtggtggct tttgtgatgt gcattcctgt
caaccataat agatgtggct acaaatttgg
ttatggagat ccactagaaa acaggtctct
gaatgatagg ttagatcctt cctctttcca
tgtcttccaa cctcaaacat ttcaaagacc
taggttaaca agtcaaaatc tgtattctaa
taaaatcccc agtgggtttc ctattgaaga
tgcttttctc tctactcatt taaagctgtt
gtctgagcta ccacaaggtt tccaggatta
tcaagtgcca acacccctcg tggcaataac
gccctctgtt atcatgatag cctgttacag
cttcgccaag tctcagagca aaacctttcg
tgtctgctgg actccatacc acatttttgg
tcccttgggg aaaactctga tgtcctggga
tagttgcttt aatcccttcc tttatgccct
gcagtccatt cagggaattc tggaggcagc
ctgtccctca aacaatgtca tttcagaaag
a tggaatgag
a ttgccaggc
c acaatttgg
c tcgctggct
c atcccctcc
c ctggatcgc
g atggcctgc
g ttcgtgtac
t ctctccagc
t gaaaacatt
a acaaatgat
t tctgcagat
t gtatttaaa
t cacgaaacc
c cctagcgct
t tacaattta
g atcactagg
c ttcattgtc
a gtggccgtg
a gtcctgtca
t catgtatgc
c ttggggaaa
c ttcagtgag
a aatagtaca
60
120
180
240
300
360
420
480
540
600
660
720
780
840
900
960
1020
1080
1140
1200
1260
1320
1380
1440
1449
8.2. BIOPOLYMERSEQUENZEN
137
Als erstes fällt auf, dass (fast) jede Zeile mit einer zweibuchstabigen
Abkürzung beginnt, die als Zeilencodes (line code) bezeichnet werden. Verschiedene Zeilencodes leiten Datenzeilen verschiedener Bedeutung ein; eine
kleine Übersicht ist in der folgenden Tabelle zu finden:
Tabelle 8.1: EMBL-Zeilencodes
Code
Bedeutung
ID
identification – Zusammenfassung einiger
Informationen wie EMBL-Name der Sequenz
und Molekültyp
accession number – eindeutiger Schlüssel zur
Identifikation des Datensatzes
date – Geschichte des Eintrags (Erstelldatum,
Revisionsdatum etc.)
description – menschenlesbare Beschreibung
keyword – Schlüsselwörter, die zur
Datenbanksuche verwendet werden können
organism species – Ursprungsorganismus der
Sequenz
organism classification – Systematische
Stellung des Ursprungsorganismus’
reference x – Informationen zu Literaturstellen
database crossreference – Querverweise auf
verwandte Datenbankeinträge in anderen
Datenbanken
feature table – Liste von zusätzlichen
Informationen über Sequenzabschnitte
sequence header – Informationen über die
folgende Sequenz
2 Leerzeichen – Zeilen, die die eigentliche
Sequenzinformation enthalten
Platzhalter – Zeilen ohne Information
Ende eines Datensatzes
AC
DT
DE
KW
OS
OC
RX
DR
FT
SQ
" "
XX
//
Es gibt noch zahlreiche weitere Zeilencodes – über deren genaue Bedeutung informieren Sie sich bitte auf der EBI-Website.
Wie gesagt, wir wollen die Daten mittels einer Subroutine aus einem
solchen EMBL-Datensatz extrahieren und Perl-gerecht bereitstellen – wobei
wir davon ausgehen, dass wir den Datensatz als Liste der einzelnen Zeilen
übergeben.
138
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
Eine erste Idee könnte sein, mittels regulärer Ausdrücke bei allen Zeilen die Zeilencodes von den eigentlichen Daten zu trennen und erstere als
Schlüssel und letztere als Werte in einem Hash zu verwenden:
Listing 8.1: EMBL-Eintrag parsen, Version 1
1
2
3
4
5
6
7
8
9
10
11
12
sub parseEMBLEntry {
my % data = () ; # Ergebnis - Hash
# Alle Zeilen durchgehen
foreach my $line ( @_ ) {
# Falls Zeilencode gefunden ...
if ( $line =~ m /^(..) \ s +(.+) /) {
# Zeileninhalt unter Zeilencode in Hash ablegen
$data { $1 } = $2 ;
}
}
return % data
}
Hier untersuchen wir jede Zeile des Eintrags auf eine Zeichenkette, die am
Anfang einer Zeile steht (^) und mit zwei beliebigen Zeichen (..) beginnt, die
von mindestens einem whitespace (\s+) gefolgt wird (nur für den Fall, dass
der Abstand nicht durch mehrere Leerzeichen, sondern durch ein TabulatorZeichen \t erzeugt wird). Alle folgenden Zeichen bis zum Zeilenende (.+)
lassen wir durch Klammerung in $2 zurückgeben und mit dem Schlüssel (dem
in $1 geretteten Zeilencode) im Ergebnis-Hash %data ablegen. Eventuelle
terminale Zeilenumbruch-Zeichen werden dabei nicht berücksichtigt, da der
. ja alles außer dem Zeilenumbruch erkennt.
Da die Schlüssel eines Hashes immer eindeutig sind, geben wir hier im
Hash das jeweils letzte Vorkommen eines jeden Zeilentyps zurück. Das ist
noch nicht ganz das, was wir wollen – genau genommen sind noch zwei
Proleme zu lösen:
1. Wir müssen dafür sorgen, dass die Daten aus mehrzeilige Datentypen
wir OC oder FT akkumuliert werden statt den jeweils letzten Eintrag zu
überschreiben
2. Diejenigen Zeilen, die die eigentliche Sequenz beinhalten, erfordern
eine Sonderbehandlung, da wir wohl kaum zwei Leerzeichen als HashSchlüssel verwenden wollen. Bei dieser Gelegenheit könnten wir noch
alle nicht zur Sequenz gehörenden Zeichen (Leerzeichen, Basenpositionen) entfernen.
Problem Nr. 1 ist schnell gelöst: Falls bereits ein Hash-Element für den
aktuellen Zeilencode existiert, konkatenieren wir dieses einfach mit den neu
eingelesenen Daten (wobei wir noch einen Zeilenumbruch dazwischen packen):
8.2. BIOPOLYMERSEQUENZEN
139
Listing 8.2: EMBL-Eintrag parsen, Version 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sub parseEMBLEntry {
my % data = () ; # Ergebnis - Hash
# Alle Zeilen durchgehen
foreach my $line ( @_ ) {
# Falls Zeilencode gefunden ...
if ( $line =~ m /^(..) \ s +(.+) /) {
# Falls bereits Schlüssel für Zeilencode ➘
existiert ...
if ( exists ( $data { $1 }) ) {
# Zeileninhalt anhängen
$data { $1 } = $data { $1 } . " \ n$2 " ;
}
else {
# Zeileninhalt unter Zeilencode in Hash ablegen
$data { $1 } = $2 ;
}
}
}
return % data
}
In Zeile 8 wird geprüft, ob bereits ein Hash-Element mit dem aktuellen
Zeilencode existiert. Falls dem so ist, wird der aktuelle Zeileninhalt in Zeile
10 an die bereits bestehenden Daten angehängt; falls nicht, wird in Zeile 14
ein entsprechendes Hash-Element angelegt.
Auch Problem Nr. 2 bedarf keiner größeren Anstrengung. Sequenzeinträge sind ja durch zwei Leerzeichen als Zeilencode gekennzeichnet; fragen
wir also einfach ab, ob der aktuelle Zeilencode gleich " " ist und leiten ggfs.
eine Sonderbehandlung ein:
Listing 8.3: EMBL-Eintrag parsen, Version 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sub parseEMBLEntry {
my % data = () ;
# Ergebnis - Hash
$data { SEQ } = " " ; # Sequenz - Eintrag initialisieren
# Alle Zeilen durchgehen
foreach my $line ( @_ ) {
# Falls Zeilencode gefunden ...
if ( $line =~ m /^(..) \ s +(.+) /) {
# Falls Sequenzzeile ...
if ( $1 eq " " ) {
# whitespaces und Ziffern entfernen
$line =~ s /[\ s \ d ]// g ;
$data { SEQ } = $data { SEQ } . $line ;
}
# Keine Sequenzzeile
else {
140
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
# Falls bereits Schlüssel für Zeilencode ➘
existiert ...
if ( exists ( $data { $1 }) ) {
# Zeileninhalt anhängen
$data { $1 } = $data { $1 } . " \ n$2 " ;
}
else {
# Zeileninhalt unter Zeilencode in Hash ➘
ablegen
$data { $1 } = $2 ;
}
16
17
18
19
20
21
22
23
24
}
25
}
26
}
return % data
27
28
29
}
Da wir auf alle Fälle immer mindestens eine Sequenzzeile finden werden
(ein EMBL-Eintrag enthält ja immer eine Sequenz), können wir dafür in
Zeile 3 bereits ein Hash-Element (mit einem selbst gewählten Schlüssel, hier
SEQ) mit dem Leerstring initialisieren. Dann fragen wir beim Durchgehen der
einzelnen Datenzeilen in Zeile 9 ab, ob der Zeilencode aus zwei Leerzeichen
besteht – wir also gerade eine Sequenzzeile in Arbeit haben. Falls dem so ist,
entfernen wir in Zeile 11 aus der Datenzeile alle whitespaces (\s) und Ziffern
(\d) und hängen die so erhaltene Teilsequenz in Zeile 11 an die (eventuell)
bereits vorhandenen Sequenzdaten an. Falls wir nicht auf eine Sequenzzeile
gestoßen sind, fahren wir ab Zeile 16 wie zuvor fort.
Nun ist unsere Subroutine einsatzbereit, und wir könnten sie z. B. wie
folgt nutzen:
Listing 8.4: EMBL-Subroutin verwenden
my % seqData = & parseEMBLEntry ( @entryLines ) ;
print ( " Description of EMBL entry $seqData { AC }:\ n " ) ;
print ( " $seqData { DE }\ n " ) ;
print ( " The entry ’s sequence is $seqData { SEQ }\ n " ) ;
Als Ausgabe würden wir bei Verwendung unserer Beispieldatei Folgendes
erhalten:
Description of EMBL entry Z73157 ;:
H . sapiens mRNA for C3a anaphylatoxin receptor
The entry ’ s sequence is a t g g c g t c t t t c t c t g c t g a ...
Natürlich ist die Subroutine noch nicht perfekt, wie schon das Semikolon
hinter der accession number anzeigt. Weiterhin werden Multiline-Daten, die
auch in Form mehrerer getrennter Blöcke in einem EMBL-Eintrag auftauchen dürfen, gnadenlos zu einem einzigen Eintrag im Ergebnis-Hash zusam-
8.3. PROTEINSTRUKTUREN
141
mengeklatscht werden. Wenn man z. B. einen Blick auf diejenigen Zeilen
wirft, die sich auf Literaturstellen beziehen, sieht man ein, dass es wenig
Sinn macht, die AutorInnen aller Artikel in einem einzigen und die Titel
der Artikel in einem einzigen anderen Hash-Element vorliegen zu haben.
Wenn Sie also an diesen Informationen interessiert sind, sind Sie herzlich
dazu eingeladen, die parseEMBLEntry-Subroutine dahingehend zu erweitern!
Übungen
8.1 Entwerfen Sie eine Subroutine, die eine EMBL-Datei (z. B.
C3aR.embl im ~/perl4bio/data-Verzeichnis) – die auch mehrere
Einträge enthalten darf – einliest, die einzelnen Einträge voneinander trennt und mit obiger Subroutine durchparst. Geben
Sie die Ergebnisse des Parsens als eine Liste von Zeigern auf die
jeweiligen Ergebnis-Hashes zurück. Demonstrieren Sie die Funktion Ihres Programms durch Ausgabe der ID, der Beschreibung
und der Sequenz jeden Eintrages.
8.3
Proteinstrukturen
Auch das Format der Protein Databank läßt sich gut automatisch per Perl
verarbeiten – alle Zeilen sind streng 80 Spalten breit, und die ersten 6
Spalten enthalten immer das PDB-Äquivalent zum EMBL-Zeilencode. Allerdings wird die Sache dadurch etwas verkompliziert, dass eine .pdb-Datei
die räumlichen Daten mehrerer Moleküle enthalten kann – man denke nur
an Proteine wie Hämoglobin, die gleich mit mehreren Untereinheiten aufwarten3 :
HEADER
TITLE
COMPND
COMPND
COMPND
COMPND
SOURCE
SOURCE
SOURCE
SOURCE
SOURCE
KEYWDS
EXPDTA
AUTHOR
REVDAT
REMARK
REMARK
REMARK
REMARK
REMARK
REMARK
REMARK
REMARK
...
3
OXYGEN TRANSPORT
22 - JAN -98
1 A3N
DEOXY HUMAN HEMOGLOBIN
MOL_ID : 1;
2 MOLECULE : HEMOGLOBIN ;
3 CHAIN : A , B , C , D ;
4 BIOLOGICAL_UNIT : ALPHA - BETA - ALPHA - BETA TETRAME R
MOL_ID : 1;
2 O R G A N I S M _ S C I E N T I F I C : HOMO SAPIENS ;
3 ORGANISM_COMMON : HUMAN ;
4 TISSUE : BLOOD ;
5 CELL : RED CELL
OXYGEN TRANSPORT , HEME , RESPIRATORY PROTEIN , ERYTH ROCYTE
X - RAY DIFFRACTION
J . TAME , B . VALLONE
1
29 - APR -98 1 A3N
0
1
2
2 RESOLUTION . 1.8 ANGSTROMS .
3
3 REFINEMENT .
3
PROGRAM
: REFMAC
3
AUTHORS
: MURSHUDOV , VAGIN , DODSON
3
Auszüge aus der Datei hemoglobin.pdb aus perl4bio/data
142
REMARK
REMARK
DBREF
DBREF
DBREF
DBREF
SEQRES
SEQRES
SEQRES
SEQRES
...
SEQRES
SEQRES
SEQRES
SEQRES
HET
HET
HET
HET
HETNAM
HETSYN
FORMUL
FORMUL
HELIX
HELIX
HELIX
HELIX
...
HELIX
HELIX
HELIX
HELIX
LINK
LINK
LINK
LINK
CRYST1
ORIGX1
ORIGX2
ORIGX3
SCALE1
SCALE2
SCALE3
ATOM
ATOM
ATOM
ATOM
ATOM
ATOM
ATOM
ATOM
...
ATOM
ATOM
ATOM
ATOM
TER
HETATM
HETATM
HETATM
HETATM
...
END
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
999 1 A3N B
999 1 A3N D
1 A3N A
1
1 A3N B
2
1 A3N C
1
1 A3N D
2
1 A 141
2 A 141
3 A 141
4 A 141
9
10
11
12
HEM
HEM
HEM
HEM
5
6
1
2
3
4
SWS
P02023
1 1 NOT IN ATOMS LIST
SWS
P02023
1 1 NOT IN ATOMS LIST
141 SWS
P01922
HBA_HUMAN
1
141
146 SWS
P02023
HBB_HUMAN
2
146
141 SWS
P01922
HBA_HUMAN
1
141
146 SWS
P02023
HBB_HUMAN
2
146
VAL LEU SER PRO ALA ASP LYS THR ASN VAL LYS ALA ALA
TRP GLY LYS VAL GLY ALA HIS ALA GLY GLU TYR GLY ALA
GLU ALA LEU GLU ARG MET PHE LEU SER PHE PRO THR THR
LYS THR TYR PHE PRO HIS PHE ASP LEU SER HIS GLY SER
D
D
D
D
146 LEU LEU GLY ASN VAL LEU VAL CYS VAL LEU ALA HIS HIS
146 PHE GLY LYS GLU PHE THR PRO PRO VAL GLN ALA ALA TYR
146 GLN LYS VAL VAL ALA GLY VAL ALA ASN ALA LEU ALA HIS
146 LYS TYR HIS
A 142
43
B 147
43
C 142
43
D 147
43
HEM PROTOPORPHYRIN IX CONTAINING FE
HEM HEME
HEM
4( C34 H32 N4 O4 FE1 )
HOH
*455( H2 O1 )
1 PRO A
4 SER A
35 1
2 PRO A
37 TYR A
42 5
3 ALA A
53 ALA A
71 1
4 MET A
76 ALA A
79 1
28
29
30
31
28 PRO D
58 ALA D
76 1
29 LEU D
78 ASP D
94 5
30 PRO D 100 GLU D 121 5
31 PRO D 124 ALA D 142 1
FE
HEM A 142
NE2 HIS A 87
FE
HEM B 147
NE2 HIS B 92
FE
HEM C 142
NE2 HIS C 87
FE
HEM D 147
NE2 HIS D 92
62.650
82.430
53.530 90.00 99.61 90.00 P 1 21 1
1.000000 0.000000 0.000000
0.00000
0.000000 1.000000 0.000000
0.00000
0.000000 0.000000 1.000000
0.00000
0.015962 0.000000 0.002703
0.00000
0.000000 0.012132 0.000000
0.00000
0.000000 0.000000 0.018947
0.00000
1 N
VAL A
1
10.720 19.523
6.163 1.00 21.36
2 CA VAL A
1
10.228 20.761
6.807 1.00 24.26
3 C
VAL A
1
8.705 20.714
6.878 1.00 18.62
4 O
VAL A
1
8.164 20.005
6.015 1.00 19.87
5 CB VAL A
1
10.602 22.000
5.966 1.00 27.19
6 CG1 VAL A
1
10.307 23.296
6.700 1.00 31.86
7 CG2 VAL A
1
12.065 21.951
5.544 1.00 31.74
8 N
LEU A
2
8.091 21.453
7.775 1.00 16.19
4499 CD2
4500 CE1
4501 NE2
4502 OXT
4503
4504 FE
4505 CHA
4506 CHB
4507 CHC
HIS
HIS
HIS
HIS
HIS
HEM
HEM
HEM
HEM
D
D
D
D
D
D
D
D
D
146
146
146
146
146
147
147
147
147
32
6
19
4
19
17
22
19
4
N
C
C
O
C
C
C
N
7.731
6.387
7.623
4.887
-9.936
-9.241
-9.674
-6.815
31.069
32.655
32.407
29.117
1.00
1.00
1.00
1.00
17.67
20.04
18.57
12.80
C
C
N
O
23.422
23.731
24.726
23.923
-6.796
-7.321
-9.855
-5.936
30.821
34.251
30.205
27.471
1.00
1.00
1.00
1.00
11.73
11.08
11.65
13.94
FE
C
C
C
Ob und wie viele verschiedene Polypeptidketten in einem Protein-Eintrag
enthalten sind, können wir denjenigen COMPND-Zeilen entnehmen, die einen
CHAIN:-Eintrag enthalten (von denen es pro Datensatz auch mehrere geben
kann): Im obigem Falle von Hämogobin finden wir eine solche Zeile, die vermerkt, dass 4 Ketten vorliegen, die A, B, C und D heißen. Das entsprechende
Feld in der .pdb-Datei zum einkettigen Protein Myoglobin z. B. sieht wie
folgt aus:
COMPND
3 CHAIN : NULL ;
Mit diesen Informationen können wir bereits eine Subroutine schreiben,
die uns die Namen oder Ids aller Ketten eines PDB-Eintrages zurückliefert.
8.3. PROTEINSTRUKTUREN
143
Wir gehen diesmal davon aus, dass wir den Inhalt der .pdb-Datei als eine einzige Zeichenkette mit Zeilenumbrüchen vorliegen haben, die wir (aus
Geschwindigkeits- und Speicherplatzgründen) als Zeiger auf die eigentliche
Zeichenkette übergeben:
Listing 8.5: Kettennamen in PDB-Datei
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
sub g e t C h a i n I D s F r o m P D B E n t r y {
# Zeiger auf Eintrag - String aus Argumentliste
my $entryRef = shift ( @_ ) ;
# Ergebnisliste für Kettennamen initialisieren
my @chainIds = () ;
# Alle COMPND - Zeilen mit CHAIN - Angabe finden und
# die Kettennamen daraus extrahieren
while ( $ { $entryRef } =~ m /^ COMPND \ s *\ d *\ sCHAIN : (.+?)➘
;? $ / gm ) {
# einzelne Kettennamen durch Trennung an
# Kommata (+ whitespace ) freilegen
my @ids = split (/ ,\ s */ , $1 ) ;
# alle Kettennamen durchgehen
for ( my $i = 0; $i < scalar ( @ids ) ; $i ++) {
# falls Kettenname gleich " NULL "
if ( $ids [ $i ] =~ m /^ NULL /) {
# durch einen Leerstring ersetzen
push ( @chainIds , " " ) ;
}
else {
# sonst einfach A~ 14 bernehmen
push ( @chainIds , $ids [ $i ]) ;
}
}
}
# Falls keine Kettennamen gefunden wurden ...
if ( scalar ( @chainIds ) == 0) {
# Leerstring als Dummy - Name
@chainIds = ( " " ) ;
}
# Namen der Ketten zur A~ 41 ckgeben
return @chainIds ;
}
Interessant wird’s ab Zeile 8: Ab hier suchen wir im (dereferenzierten)
Eintrag all diejenigen COMPND-Zeile, die Informationen über Kettennamen
(CHAIN:) des Eintrages enthalten. COMPND gehört zu so genannten single continued -Datensätzen (in PDB-Sprech“); das sind solche Typen von Infor”
mationen, die nur einmal in einem PDB-Eintrag vorkommen, sich aber über
mehrere Zeilen erstrecken dürfen. Bei der globalen Suche nach Zeilen, die
mit COMPND beginnen, kommt uns ein neuer Modifikator des m//-Operators
gelegen: m (von multiple lines). Dieser Modifikator verändert das Verhalten
m
144
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
von m// dahingehend, dass sich die Lokatoren ^ und $ nicht mehr nur auf
Anfang und Ende der gesamten Zeichenkette, sondern auch auf Anfang und
Ende jeder einzelnen (durch \n abgeschlossenen) Zeile innerhalb einer Zeichenkette beziehen. (Randbemerkung: Ein weiterer nützlicher Modifikator
ist s (von single line), der dafür sorgt, dass . auch den Zeilenumbruch \n
erkennt.) Ab der 2. Zeile eines single continued -Datensatzes wird die Zeilennummer vor den eigentlichen Daten notiert; daher müssen wir zwischen
COMPND und den eigentlichen Daten (CHAIN: (.+?);?) außer whitespaces (\s)
optional auch Ziffern (\d*) zulassen. Beachten Sie weiterhin, dass wir am
Zeilenende ein optionales Semikolon (;?$) zulassen (welches nicht mehr zu
den eigentlichen Daten gehört) – was allerdings erfordert, das wir den vorhegehenden Quantor vom greedy- in den bescheidenen“ Modus schalten
”
(.+?).
s
Falls wir fündig werden und eine COMPND-Zeile mit Kettennamen entdecken, lassen wir alles auf CHAIN: Folgende zurückgeben und behandeln
die erhaltene Zeichenkette nach dem CSV-Prinzip (comma-separated values,
Zeile 11), um schließlich die Namen der einzelnen Ketten zu erhalten. Diese
gehen wir ab Zeile 13 noch einzeln durch – falls wir das Schlüsselwort NULL
finden (Zeile 15), schieben wir einen – für eine solche namenlose Kette angemessenen – Leerstring in die Ergebnisliste @chainIds (Zeile 17), sonst den
gefundenen Kettennamen (Zeile 21). Ab Zeile 26 schließlich behandeln wir
den Fall, in dem gar kein Kettenname gefunden wurde – auch dann wollen
wir einen Leerstring als Dummy-Kettennamen verwenden.
So schön funktioniert das aber leider nur mit neueren PDB-Einträgen
(ab Version 2.0 des PDB-Formats) – davor wurden die Spalten 71 – 80 mit
dem sog. PDB ID code (dem PDB-Pendant einer accession number) und
einer laufenden Nummer gefüllt. Dies sei hier an den ersten paar Zeilen des
Eintrages für Hühnereiweiß-Lysozym verdeutlicht:
HEADER
TITLE
COMPND
COMPND
COMPND
SOURCE
HYDROLASE (O - GLYCOSYL )
01 - SEP -95
193 L
THE 1.33 A STRUCTURE OF TETRAGONAL HEN EGG WHITE LYSOZY ME
MOL_ID : 1;
2 MOLECULE : LYSOZYME ;
3 CHAIN : NULL
MOL_ID : 1;
193 L
193 L
193 L
193 L
193 L
193 L
2
3
4
5
6
7
Die folgende Subroutine zum Einlesen eines PDB-Eintrages aus einer
Datei trägt diesem Unterschied zwischen altem und neuem Format Rechnung:
Listing 8.6: Einlesen einer PDB-Datei
1
2
3
sub r e a d P D B E n t r y F r o m F i l e {
# Filename als erstes Argument
my $filename = shift ( @_ ) ;
4
5
6
7
# Eintrag initialisieren
my $entry = " " ;
# Falls Datei erfolgreich geöffnet ...
145
8.3. PROTEINSTRUKTUREN
if ( open ( PDB , $filename ) ) {
# Alle zeilen einlesen ...
my @entry = <PDB >;
close ( PDB ) ;
# ... und aneinanderhängen
$entry = join ( " " , @entry ) ;
# ID - Code extrahieren
my $id = substr ( $entry , 62 , 4) ;
# Terminale whitespaces und ggfs . ID - Code und
# laufende Nummer ( altes PDB - Format !) entfernen
$entry =~ s /\ s *( $id \ s *\ d +) ? $ // gm ;
}
# Falls Datei nicht geöffnet werden konnte
else {
# Fehlermeldung ausgeben
die ( " Couldn ’t open . pdb file $filename ! " ) ;
}
# Zeiger auf Eintrag zurückgeben
return \ $entry ;
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
}
Hier machen wir uns zu Nutze, dass die PDB-ID (hier 193L) immer in
den Spalten 63 – 66 der ersten Zeile (Perl-Zählweise: 62 - 65) eines Eintrages zu finden ist, so dass wir diese in Zeile 15 einfach per substr extrahieren
können. In Zeile 18 entfernen wir dann – wieder unter Verwendung des
m-Modifikators – vom Ende ($) jeder Zeile alle eventuellen vorhandenen terminalen whitespaces (\s*) sowie die optionale (Fragezeichen ? hinter dem
geklammerten Teilausdruck!) ID samt Zeilennummer.
Die Spezialität der Protein Databank ist ja die Bereitstellung von 3DStrukturen von Biomolekülen. Genau genommen enthält ein PDB-Eintrag
die räumlichen Koordinaten aller Atome des jeweiligen Moleküls – was bei
Hämoglobin z. B. wie folgt aussieht:
ATOM
ATOM
ATOM
ATOM
ATOM
ATOM
ATOM
ATOM
...
1
2
3
4
5
6
7
8
N
CA
C
O
CB
CG1
CG2
N
VAL
VAL
VAL
VAL
VAL
VAL
VAL
LEU
A
A
A
A
A
A
A
A
1
1
1
1
1
1
1
2
10.720
10.228
8.705
8.164
10.602
10.307
12.065
8.091
19.523
20.761
20.714
20.005
22.000
23.296
21.951
21.453
6.163
6.807
6.878
6.015
5.966
6.700
5.544
7.775
1.00
1.00
1.00
1.00
1.00
1.00
1.00
1.00
21.36
24.26
18.62
19.87
27.19
31.86
31.74
16.19
N
C
C
O
C
C
C
N
Von den zahlreichen Spalten, die ein solcher ATOM-Eintrag hat, sollen uns
im Rahmen dieses Kurses nur die in der folgenden Tabelle aufgeführten
interessieren:
146
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
Tabelle 8.2: Spalten in PDB-ATOM-Einträgen
Spalten
1–4
7 – 11
13 – 16
18 – 20
22
23 – 26
31 – 38
39 – 46
47 – 54
77 – 78
Bedeutung
ATOM – Zeilencode“
”
laufende Nummer des Atoms
Name des Atoms
Art des Aminosäure-Restes
Name der Kette (Leerstring, falls kein Name
angegeben!)
laufende Nummer des Restes in der Kette
x-Koordinate in Å (1 Ångstrøm = 10−10 m =
100 pm)
y-Koordinate
z-Koordinate
Elementsymbol (ab Vers. 2.0)
Über die Bedeutung weiterer Spalten sowie Einträge für Heteroatome,
Disulfid-Brücken etc. informieren Sie sich bitte auf der PDB-Homepage.
Lassen Sie uns nun noch eine Subroutine entwerfen, die alle zu einer
Kette gehörende ATOM-Einträge (bzw. einige der darin enthaltenen Informationen) in Form eines Zeigers auf eine Liste von Hash-Zeigern zurückgibt:
Listing 8.7: Atomliste aus PDB-Datei
1
2
3
4
5
sub getAtomsInPDBChain {
# Zeiger auf Eintrag - String aus Argumentliste
my $entryRef = shift ( @_ ) ;
# Name der Kette als 2. Argument
my $chainID = shift ( @_ ) ;
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Liste der Atome initialisieren
my @atoms = () ;
# Alle Zeilen finden , die ...
while ( $ { $entryRef } =~ m /
^ ATOM
# ATOM am Anfang
.{9}
#
(\ w )
# Elementsymbol
.{3}
#
(\ w {3})
# Art des Aminosäure - Restes
\s
#
$chainID
# richtigen Name der Kette
\s*
(\ d +)
# lfd . Nr . d . Aminosäure
\s+
#
8.3. PROTEINSTRUKTUREN
(\ S +)
# x - Koordinate
\s*
#
(\ S +)
# y - Koordinate
\s*
#
(\ S +)
# z - Koordinate
/ gmx ) {
# ... haben
# Hash für einzelnes Atom
my % atom = () ;
$atom { TYPE } = $1 ;
# Elementsymbol
$atom { AA_TYPE } = ucfirst ( lc ( $2 ) ) ;
# Aminosäure - Typ
$atom { AA_NO } = $3 ;
# lfd . Nr . Aminos .
# Wir benutzen selbstverständlich SI # Einheiten statt Angström ... ; -)
$atom { X } = $4 * 100; # x - Koordinate
$atom { Y } = $5 * 100; # y - Koordinate
$atom { Z } = $6 * 100; # z - Koordinate
# Zeiger auf Atom - Hash in Liste aufnehmen
push ( @atoms , \% atom ) ;
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
}
# Zeiger auf Atom - Liste zurückgeben
return \ @atoms ;
40
41
42
43
147
}
In der Schleife ab Zeile 10 lernen wir – in Zeile 27 – einen neuen Modifikator kennen: x (von extended, erweitert – bezieht sich auf die Syntax regulärer
Ausdrücke). Dieser erlaubt es, einen regulären Ausdruck über mehrere Zeilen
zu ziehen und Kommentare einzufügen. In einem solchen Ausdruck müssen
Leerzeichen und #-Zeichen dann allerdings ein Aufhebungszeichen vorangestellt bekommen, damit sie wieder als Bestandteil des Ausdrucks angesehen werden. Im Ausdruck selbst mogeln wir übrigens ein bisschen – da
das Elementsymbol erst ab Version 2.0 von PDB verfügbar ist, nehmen wir
stattdessen einfach das erste Zeichen des Atomnamens als Elementsymbol.4
Wenn der Ausdruck greift, wird in Zeile 28 ein neuer Hash angelegt, der
in den folgenden Zeilen mit einigen Schlüssel-Wert-Paaren belegt wird, die
die aus dem ATOM-Eintrag extrahierten Informationen repräsentieren. Dabei wollen wir die großbuchstabigen Aminosäure-Abkürzungen, wie sie in
der PDB verwendet werden (LYS etc.) in die übliche Schreibweise, bei der
lediglich der erste Buchstabe groß geschrieben wird, umwandeln. Zu diesem Zweck verwandeln wir die in $2 aufegfangene Zeichenkette zunächst
per lc komplett in Kleinbuchstaben und wenden dann auf das Ergebnis die
4
Dies ist natürlich noch keine saubere Lösung – denken Sie an Elemente mit zweibuchstabigem Symbol, wie z. B. Chlor (Cl) oder Eisen (Fe)! Glücklicherweise aber haben alle
Elemente, die die Grundstruktur von Proteinen und Nucleinsäuren bilden, einbuchstabige
Kürzel, und Heteroatome werden in PDB-Files in separaten HETATM-Zeilen aufgeführt, die
wir hier nicht weiter berücksichtigen.
x
148
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
ucfirst-Funktion an, die das erste Zeichen der übergebenen Zeichenkette
kapitalisiert. (Es gibt auch die lcfirst-Funktion, die sicherstellt, dass das
ucfirst
lcfirst
erste Zeichen ein Kleinbuchstabe ist.)
In Zeile 39 wird dann ein Zeiger auf diesen Hash in die Atomliste geschoben, und zum Schluss wird – in Zeile 42 – ein Zeiger auf die gesamte
Atomliste zurückgegeben.
Unsere drei PDB-Subroutinen könnten wir dann z. B. wie folgt verwenden:
Listing 8.8: PDB-Subroutinen verwenden
1
2
3
4
5
6
7
8
my $entryRef = & r e a d P D B E n t r y F r o m F i l e ( " $ENV { HOME }/➘
perl4bio / data / hemoglobin . pdb " ) ;
my @chains = & g e t C h a i n I D s F r o m P D B E n t r y ( $entryRef ) ;
foreach my $chain ( @chains ) {
my $atoms = & getAtomsInPDBChain ( $entryRef , $chain ) ;
print ( " The $chain chain has " , scalar ( @ { $atoms }) , " ➘
atoms .\ n " ) ;
print ( " It starts with a " , $atoms - >[0] - >{ AA_TYPE } , " ➘
residue .\ n " ) ;
print ( " The first atom is of element type " , $atoms ➘
- >[0] - >{ TYPE } , " \ n " ) ;
}
...was uns die Ausgabe
The A chain has 1069
It starts with a VAL
The first atom is of
The B chain has 1116
It starts with a HIS
The first atom is of
The C chain has 1069
It starts with a VAL
The first atom is of
The D chain has 1116
It starts with a HIS
The first atom is of
atoms .
residue .
element type
atoms .
residue .
element type
atoms .
residue .
element type
atoms .
residue .
element type
N
N
N
N
bescheren würde. Bemerkenswert ist in diesem Zusammenhang vielleicht
noch Zeile 6 – hier haben wir eine verkettete Dereferenzierung, bei der
zunächst (durch den ersten ->) das 1. Element der durch den Listen-Zeiger
$atoms referenzierten Liste dereferenziert wird. Bei diesem Element handelt es sich ja um einen Zeiger auf einen Hash, und so können wir gleich
die zweite Dereferenzierung anschließen – hier auf das Hash-Element mit
dem Schlüssel AA_TYPE, was uns den Typ der Aminosäure, zu der das Atom
gehört, beschert. Ähnliches geschieht auch in der nächsten Zeile, wo wir uns
das Element-Symbol des ersten Atoms der Kette zurückgeben lassen.
8.3. PROTEINSTRUKTUREN
149
Wie auch bei EMBL, ist dies alles erst ein bescheidener Anfang dessen, was möglich ist. Nun sollten Sie in der Lage sein, beliebig komplizierte
Flatfiles zu parsen und das Ergebnis Ihrer Analyse in beliebig komplexe
Datenstrukturen zu verpacken.
150
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
Übungen
8.2 POV-Ray (von persistance of vision, http://www.povray.org) ist
ein Open-Source-Raytracer – also ein Programm zu Erzeugung
photorealistischer dreidimensionaler Computer-Graphiken mit
Hilfe einer ray tracing ( Strahlrückverfolgung“) genannten Me”
thode. Dabei werden mittels einer Szenenbeschreibungssprache
geometrische Objekte (Kugeln, Quader etc.), denen bestimmte
Oberflächeneigenschaften (Farbe, Reflexionseigenschaften; kurz:
eine Textur) zugewiesen werden können, im Raum platziert.
Aus solchen Angaben berechnet POV-Ray dann eine Ansicht
der virtuellen Welt.
Benutzen Sie POV-Ray, um ein Bild eines Kalottenmodells
der dreidimensionlen Struktur eines in einer PDB-Datei gespeicherten Proteins zu erzeugen. Platzieren Sie dazu Kugeln,
die die Atome repräsentieren sollen, an den in der PDB-Datei
gegebenen Koordinaten. Der POV-Ray-Befehl für eine Kugel
lautet:
sphere {<x,y ,z > radius_E texture {atom_E }}
Es
gibt
bereits
eine
entsprechende
POV-Ray-Datei
(PDB_template.pov im ~/perl4bio/data-Verzeichnis), in der
diejenige Stelle, an der Sie die sphere-Anweisungen einfügen
müssen, mit //PDB gekennzeichnet ist.
Erzeugen Sie eine Zeichenkette, die für jedes Atom eine entsprechende Zeile enthält (wobei Sie x, y und z durch die
Atomkoordinaten und das E in radius_E und atom_E durch
das jeweilige Elementsymbol ersetzen), lesen Sie die Datei
PDB_template.pov ein, ersetzen Sie in der Datei die Zeichenkette
//PDB durch Ihre Kugel-Anweisungen, speichern Sie die Datei
unter einem neuen Namen und rufen Sie POV-Ray von Ihrem
Programm aus wie folgt auf:
povray -Iihre datei.pov -Oprotein.png -WBreite -HHöhe
Höhe und Breite stehen für die Auflösung des Bildes in
Pixeln (z. B. 800 × 600 Pixel). Wenn Sie wollen, können Sie
nach Beendigung von POV-Ray auch gleich noch einen Aufruf
des universellen Bildbetrachtungsprogramms display anhängen:
display protein.png
151
8.3. PROTEINSTRUKTUREN
Aufgaben
8.1 Schreiben Sie eine Subroutine, die die feature table von EMBLDatensätzen genauer analysiert und die Ergebnisse in einer geeigneten Datenstruktur zur Verfügung stellt.
8.2 Entwerfen
Sie
eine
Subroutine,
die
die
Sekundärstrukturinformationen (also die Positionen der α-Helices
und β-Faltblatt-Bereiche innerhalb der Aminosäuresequenz) für
eine in einer PDB-Datei gespeicherte Kette extrahiert. Hinweis:
Die Sequenzpositionen von α-Helices und β-Faltblättern werden für jede Kette in individuellen HELIX- bzw. SHEET-Zeilen
vermerkt.
8.3 Schreiben Sie ein Programm, das die Primärstruktur (Sequenz) einer Polypeptidkette aus den SEQRES-Datensätze eines
PDB-Eintrages extrahiert und diese dann mit Hilfe der in der
vorherigen Übung gewonnenen Sekundärstrukturinformationen
annotiert. (Alternativ können Sie die Sequenz einer Kette auch
anhand der von der vorgestellten Subroutine getAtomsInPDBChain
bereitgestellten Atomliste rekonstruieren) Die Programm soll
eine Ausgabe erzeugen, die etwa wie folgt aussieht (mit 20
Aminosäure-Resten pro Zeile):
ArgGlyTyrSerLeuGlyAsnTrpValCysAlaAlaLys
HHHHHH------HHHHHHHHHHHHHHHHHHHHHHHHHHH
GlnAlaThrAsnArgAsnThrAspGlySerThrAspTyr
------EEEEEEEEE---------------EEEEEEEEE
Anmerkung; Nach ungeschriebenem Gesetz werden α-helikale
Bereiche mit H und β-Faltblätter mit E gekennzeichnet.
8.4 Schreiben Sie eine Subroutine, die aus einem PDB-Eintrag alle
Aminosäure-Reste (Charakterisiet duch eine Zeichenkette wie
LYS74A, also Art des Restes plus Position in sowie Name der
Kette) heraussucht, die innerhalb einen bestimmten Radius
um ein frei wählbares Atom des Proteins (oder frei wählbare
Koordinaten) liegen! Damit ein Aminosäurerest als innerhalb“
”
des Radius’ gelegen gilt, soll es ausreichen, dass mindestens ein
Atom des Restes entsprechend nahe liegt. Zur Erinnerung: Der
Abstand zwischen zwei Punkten lässt sich nach dem Satz des
Pythagoras berechnen:
r=
p
∆x2 + ∆y 2 + ∆z 2
152
KAPITEL 8. BIOMOLEKULARE DATENFORMATE
Kapitel 9
Modularisierung
Nach Studium dieses Kapitels können Sie
• Subroutinen und Variablen in Module verpacken, so dass sie von all
Ihren Programmen verwendet werden können
• CPAN als die Quelle für fertige Perl-Module nennen
• einige einfache, aber nützliche Funktionen des BioPerl-Projektes nutzen
• Perl-Programme schreiben, die automatisch Informationen aus dem
World Wide Web herunterladen
9.1
Recycling 3 - Module
Mit dem Aufruf externer Programme und der Verwendung von Subroutinen kennen Sie bereits zwei effizienzsteigernde Methoden zur Code-Wiederverwendung. Aber da die größte Tugend von Programmiererinnen und
Programmierern die Faulheit ist, haben sie für praktisch alle Programmiersprachen eine weitere Methode entwickelt, um sich auch noch das Copy &
”
Paste“ von Subroutinen-Code von einem Programmier-Projekt zum anderen
zu sparen. Der Trick besteht darin, Subroutinen, die man in mehreren Programmen verfügbar haben möchte, in eigene Dateien – sogenannte Module
– auszulagern.
Ein Perl-Modul ist eine Quellcode-Datei, die ganz normale Perl-Anweisungen – vorzugsweise zahlreiche Subroutinen – enthält und sich in drei Dingen von einer Perl-Programmdatei unterscheidet:
153
Module
154
KAPITEL 9. MODULARISIERUNG
1. Der Name der Modul-Datei endet auf .pm (perl module) statt auf .pl.
2. Vor der ersten Zeile Code steht das Schlüsselwort package, gefolgt vom
Dateinamen ohne die .pm-Endung.
package
3. Nach der letzten Zeile Code steht eine einsame 1, gefolgt von einem
Semikolon ;.
use
Es hat sich eingebürgert, für Module Namen zu verwenden, die mit einem
Großbuchstaben beginnen; ein Modul, das Ihre Lieblings-Bioinformatik-Subroutinen enthält, könnte z. B. BioInf.pm heißen und würde zu Beginn dementsprechend eine package BioInf-Zeile enthalten. Die 1; am Dateiende kommt
übrigens dann zum Zuge, wenn Sie ein Perl-Modul in ein eigenes Programm
einbinden. Dies geschieht – wie bei Pragmata – mittels use, also in unserem
Beispiel durch ein
use BioInf
zu Beginn Ihres Programms. Die use-Anweisung importiert dann den
Code, der im Modul steht, und führt ihn auch aus; und die 1; stellt dann
zugleich den letzten Befehl“ und sein Ergebnis – also TRUE – dar, was use
”
zu der Annahme verleitet, dass das Einbinden des Moduls erfolgreich war.
Vergisst man die 1;, wird das importierende Programm i. d. R. mit einer
Fehlermeldung abgebrochen.
Das Minimal-Gerüst für ein BioInf-Modul – die BioInf.pm-Datei – sähe
also wie folgt aus:
package BioInf ;
# Platz für Ihre Subroutinen etc .
1;
Da Module nicht dazu gedacht sind, als eigenständige Programme ausgeführt zu werden, brauchen Sie die Dateirechte von .pm-Dateien nicht auf
ausführbar“ zu setzen und können auch auf die Shebang-Zeile verzichten.
”
Packen wir nun einige Subroutinen in das Modul hinein:
9.1. RECYCLING 3 - MODULE
155
Listing 9.1: Modul BioInf.pm, 1. Version
package BioInf ;
use strict ;
use warnings ;
# returns the reverse of a sequence
sub reverseSeq {
my $seq = reverse ( shift ( @_ ) ) ;
return $seq
}
# returns the complement of a DNA sequence
sub complSeq {
my $seq = shift ( @_ ) ;
$seq =~ tr / ACGTacgt / TGCAtgca /;
return $seq ;
}
# returns the reverse - complement of a sequence
sub revComplSeq {
return & reverseSeq (& complSeq ( @_ ) ) ;
}
1;
Wenn wir nun dieses Modul in unserem Programm (das wir im gleichen Verzeichnis wie BioInf.pm speichern) mittels use BioInf importieren,
können wir auf die darin bereitgestellten Subroutinen wie folgt zugreifen:
Listing 9.2: Benutzung des Moduls BioInf.pm
use BioInf ;
my $seq = " gattaca " ;
print ( " Komplementäre Sequenz zu $seq : " ) ;
print (& BioInf :: complSeq ( $seq ) , " \ n " ) ;
print ( " Revers - komplementäre Sequenz zu $seq : " ) ;
print (& BioInf :: revComplSeq ( $seq ) , " \ n " ) ;
Dem eigentlichen Namen der Subroutine müssen wir also noch den Modulnamen, gefolgt von zwei Doppelpunkten (::) voranstellen. Das mag zwar
zunächst etwas umständlich erscheinen, ergibt aber durchaus Sinn – so kann
man zwischen zufällig gleichnamigen Subroutinen aus verschiedenen Modulen unterscheiden, falls man mehrere Module importiert.
Wie gesagt, die use-Anweisung führt den im zu importierenden Modul enthaltenen Code aus. Subroutinen werden dabei zwar nicht im engeren Sinne ausgeführt, aber es ist durchaus möglich, Perl-Code in Modulen
156
KAPITEL 9. MODULARISIERUNG
ausführen zu lassen – etwa um im Modul benötigte globale Variablen zu
deklarieren und zu initialisieren. Als Beispiel wollen wir das BioInf-Modul
dahingehend erweitern, dass es einen Hash, der den genetischen StandardCode repräsentiert, bereitstellt (und auch intern in einer neuen Subroutine
– translate – benutzt):
Listing 9.3: BioInf.pm, 2. Version
1
package BioInf ;
2
3
4
use strict ;
use warnings ;
5
6
7
# file from which to read the genetic code table
my $CODE_FILE = " $ENV { HOME }/ perl4bio / data /➘
genetic_code_3 . csv " ;
8
9
10
11
# hash containing translations of
# codons into amino acids
our % CODON2AA = () ;
12
13
14
15
16
17
18
19
20
21
22
23
24
if ( open ( CODE , $CODE_FILE ) ) {
until ( eof ( CODE ) ) {
my $line = < CODE >;
if ( $line =~ m /(\ w {3}) ,\ s *(\ S +) /) {
$CODON2AA { $1 } = $2 ;
}
}
close ( CODE ) ;
}
else {
die ( " Couldn ’t open genetic code file $CODE_FILE " ) ;
}
25
26
27
28
29
30
# returns the reverse of a sequence
sub reverseSeq {
my $seq = reverse ( shift ( @_ ) ) ;
return $seq
}
31
32
33
34
35
36
37
# returns the complement of a DNA sequence
sub complSeq {
my $seq = shift ( @_ ) ;
$seq =~ tr / ACGTacgt / TGCAtgca /;
return $seq ;
}
38
39
40
# returns the reverse - complement of a sequence
sub revComplSeq {
9.1. RECYCLING 3 - MODULE
return & reverseSeq (& complSeq ( @_ ) ) ;
41
42
157
}
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# translates a DNA sequence into protein
sub translate {
my $dna = uc ( shift ( @_ ) ) ;
my $prot = " " ;
for ( my $pos = 0; $pos < length ( $dna ) ; $pos = $pos + ➘
3) {
my $codon = substr ( $dna , $pos , 3) ;
if ( exists ( $CODON2AA { $codon }) ) {
$prot = $prot . $CODON2AA { $codon };
}
else {
$prot = $prot . " ---" ;
}
}
return $prot ;
}
59
60
1;
Beim Laden des Moduls werden nun die in den Zeilen 7 – 24 enthaltenen
Perl-Anweisungen ausgeführt, die dafür sorgen, dass der Hash %CODON2AA1
schließlich mit den Werten der in $CODE_FILE angegebenen Datei gefüllt wird.
Aber Hoppla – plötzlich steht da ein our vor einer Variablen! Was hat das
zu bedeuten? Nun, normalerweise ist die Sichtbarkeit“ von Variablen auf
”
das Modul beschränkt, in dem sie deklariert werden. Erst our sorgt dafür,
dass wir auf den %CODON2AA-Hash auch vom aufrufenden Programm aus –
wie in Zeile 10 – zugreifen können:
1
Bei der Namensgebung haben wir zum einen berücksichtigt, dass Konstanten
üblicherweise Bezeichner in Großbuchstaben verliehen bekommen, und zum anderen schließen wir uns der Gepflogenheit an, bei Daten oder Prozeduren, die etwas mit Konvertierung
zu tun haben, das englisch Wort to durch 2 zu ersetzen – was sich im Englischen genau
gleich anhört.
our
158
KAPITEL 9. MODULARISIERUNG
Listing 9.4: Benutzung des Moduls BioInf.pm, 2. Version
1
use BioInf ;
2
3
4
5
6
7
8
9
10
my $seq = " gattaca " ;
print ( " Komplementäre Sequenz zu $seq : " ) ;
print (& BioInf :: complSeq ( $seq ) , " \ n " ) ;
print ( " Revers - komplementäre Sequenz zu $seq : " ) ;
print (& BioInf :: revComplSeq ( $seq ) , " \ n " ) ;
print ( " Translation von $seq : " ) ;
print (& BioInf :: translate ( $seq ) , " \ n " ) ;
print ( " Codon ATG codiert für $BioInf :: CODON2AA { ATG }\ n " )➘
;
Wenn es Ihnen zu umständlich ist, den Bezeichnern der vom Modul bereitgestellten Variablen und Subroutinen immer noch den Modulnamen und
die Doppelpunkte voranzustellen, können Sie auch gezielt Bezeichner in den
Namensraum desjenigen Programms exportieren, das das Modul benutzt.
Ohne auf die Details einzugehen, hier die nötigen Veränderungen am Kopf
der Modul-Datei – in der Sie gleich noch zwei neue Methoden kennen lernen,
Listen mit einzelnen Wörtern zu füllen:
1
package BioInf ;
2
3
4
5
6
use Exporter ;
@ISA = qw ( Exporter ) ;
@EXPORT = qw (% CODON2AA translate ) ;
@EXPORT_OK = qw ( reverseSeq complSeq revComplSeq ) ;
7
8
9
10
qw
qq
Exporter
use strict ;
use warnings ;
...
Die qw-Funktion liefert eine Liste von Zeichenketten zurück, wobei jede Zeichenkette einem der zwischen den Klammern qw(...) aufgeführten
Wörter entspricht. Ein Leerzeichen dient dabei als Trennzeichen zwischen
einzelnen Wörtern. qw behandelt die einzelnen Wörter dabei, als wären sie in
einfach Anführungszeichen ’ eingeschlossen – falls man also Variablennamen
angibt, werden diese nicht interpretiert und durch ihre Werte ersetzt. Dies
ließe sich mittels der qq-Funktion erreichen, die die Wörter wie in doppelten
Anführungszeichen " eingeschlossen behandelt.
Das aber nur am Rande – eigentlich geht es hier ja um das Exportieren
von Bezeichnern. Verwenden Sie dazu in Ihrem Modul ein weiteres Modul,
den Exporter (wird standardmäßig zusammen mit Perl installiert), der hier
in Zeile 3 eingebunden wird. Das Konstrukt mit der @ISA-Liste (Zeile 4)
entstammt der objektorientierten Seite von Perl und bedeutet hier, dass sich
das eigene Modul wie ein Exporter verhalten soll. Für Sie interessant wird es
dann ab Zeile 5: Alle Bezeichner, die Sie in die Liste @EXPORT einfügen (hier
9.1. RECYCLING 3 - MODULE
159
%CODON2AA und translate), werden dem importierenden Programm durch
den Exporter automatisch bekannt gemacht, so dass sie (wie in den Zeilen 9
und 10) ohne vorangestelltes BioInf:: benutzt werden können:
1
use BioInf
2
3
4
5
6
7
8
9
10
my $seq = " gattaca " ;
print ( " Komplementäre Sequenz zu $seq : " ) ;
print (& BioInf :: complSeq ( $seq ) , " \ n " ) ;
print ( " Revers - komplementäre Sequenz zu $seq : " ) ;
print (& BioInf :: revComplSeq ( $seq ) , " \ n " ) ;
print ( " Translation von $seq : " ) ;
print (& translate ( $seq ) , " \ n " ) ;
print ( " Codon ATG codiert für $CODON2AA { ATG }\ n " ) ;
Bezeichner, die in @EXPORT_OK aufgeführt sind, können Sie bei Bedarf
immerhin noch mit
use BioInf qw(reverseSeq complSeq)
explizit in den Namensraum des benutzenden Programms importieren
(hier die Funktionen reverseSeq und complSeq). Falls Sie use eine solche
Liste von Bezeichnern als Argument mitgeben, werden zunächst nur die
darin aufgeführten Bezeichner importiert; um auch wieder die in @EXPORT
definierten Bezeichner zu importieren, müssen Sie zusätzlich noch das Element :DEFAULT mit in die Liste aufnehmen:
use BioInf qw(:DEFAULT reverseSeq complSeq)
Generell sollten Sie aber vorsichtig mit dem Im- und Export von Bezeichnern sein – wie gesagt, wenn zwei Module zufällig gleichnamige Bezeichner
exportieren wollen, kommt es zu Problemen. Nicht umsonst heißt es in der
Hilfe zum Exporter (perldoc Exporter):
Do not export method names!
Do not export anything else by default without a good reason!
Man mag sich fragen, woher die use-Anweisung eigentlich weiß, wo die zu
importierenden Modul-Anweisungen liegen. Hier greift ein ähnlicher Mechanismus wie bei den Suchpfaden für ausführbare Programme (Umgebungsvariable PATH). Zunächst geht use durch eine Liste definierter Standard-Pfade –
hier liegen diejenigen Module, die mit Perl gleich mitgeliefert werden oder als
systemweite Pakete bereitgestellt werden. (Normalerweise ist auch das aktuelle Verzeichnis . im Standard-Suchpfad enthalten, so dass Module, die im
gleichen Verzeichnis wie das laufende Perl-Programm liegen, ebenfalls gefunden werden.) Falls allerdings eine Umgebungsvariable namens PERL5LIB existiert, wird zunächst noch eine Suche in den darin aufgeführten Pfaden durchgeführt. Sie können sich übrigens von innerhalb eines Perl-Programms die
gesamte Suchliste ansehen – Perl stellt sie in der Spezial-Variablen @INC zur
@INC
160
%INC
KAPITEL 9. MODULARISIERUNG
Verfügung. Eine andere Spezial-Variable schließlich, der Hash %INC, enthält
zu jedem in einem Programm geladenen Modul (Hash-Schlüssel: Modulname mit Endung .pm) den vollständigen Pfad zur Modul-Datei (zugehöriger
Hash-Wert).
Durch explizites Setzen der Umgebungs-Variablen PERL5LIB – z. B. in
ihrem Shell-Profil – können Sie selbst einen Ort für Ihre Perl-Module bestimmen. Es ist unter UNIX-artigen Betriebssystemen üblich, Programmbibliotheken (wie z. B. Perl-Module) in Unterverzeichnissen eines lib-Verzeichnisses (von library, Bibliothek) abzulegen – so sind Ihnen ja im LinuxTeil des Kurses bei der Vorstellung des klassischen UNIX-Verzeichnisbaumes
sicher bereits systemweite Bibliotheksverzeichnisse wie /lib, /usr/lib und
/usr/local/lib aufgefallen. Ihre eigenen, privaten Perl-Module sollten Sie
allerdings in einem lib-Unterverzeichnis Ihres Home-Verzeichnisses ablegen.
Erstellen Sie dazu mittels
~$ mkdir -p /lib/perl Enter die nötige Verzeichnisstruktur. Als Nächstes fügen Sie bitte folgende Zeilen am Ende der Datei .bashrc in Ihrem home-Verzeichnis (also in ~/.bashrc)
ein:
if [ $PERL5LIB ]
then
PERL5LIB =~/ lib / perl :~/ perl4bio / lib : $PERL5LIB
else
export PERL5LIB =~/ lib / perl :~/ perl4bio / lib
fi
Damit ist sicher gestellt, dass
1. die Umgebungsvariable PERL5LIB bei jedem log-in auf den Suchpfad für
Ihre Perl-Module in ~/lib/perl gesetzt wird (wobei wir hier außerdem
noch den Pfad zum Verzeichnis ~/perl4bio/lib hinzufügen, in welchem
Beispielmodule wie BioInf.pm sowie einige Modul-Musterlösungen residieren)2 und
2. ein eventuell vom Systemadministrator/von der Systemadministratorin bereits gesetzter Wert von PERL5LIB nicht überschrieben, sondern
ergänzt wird.
Wenn Sie nun künftig ihre Perl-Module in Ihrem ~/lib/perl-Verzeichnis
ablegen, sollten diese problemlos von Ihren Perl-Programmen gefunden werden. Übrigens können (und sollten!) Sie in Ihrem Modul-Verzeichnis auch
weitere Unterverzeichnisse anlegen, um Ihre sicher bald sehr zahlreichen
Perl-Module etwas zu ordnen. So könnten sich mit der Zeit etwa folgende
2
Um die Änderungen
gleich wirksam zu machen, teilen Sie diese der Shell durch ein ~$
source .bashrc Enter mit.
9.1. RECYCLING 3 - MODULE
161
Bioinformatik-Moduldateien in Ihrem Perl-Bibliotheksverzeichnis ~/lib/perl
ansammeln3 :
~/ lib / perl / BioInf . pm
~/ lib / perl / BioInf / Pattern . pm
~/ lib / perl / BioInf / Random . pm
~/ lib / perl / BioInf / Restriction . pm
~/ lib / perl / BioInf / DB / EMBL . pm
~/ lib / perl / BioInf / DB / PDB . pm
Dabei müssen Sie lediglich beachten, dass sich die Namen von Modulen in
den Unterverzeichnissen nach dem Pfad relativ zum Modul-Stammverzeichnis
(in Ihrem Falle also ~/lib/perl) richten, wobei die Verzeichnistrennzeichen
(UNIX/Linux /, Windows \) durch :: ersetzt werden. Das Modul PDB.pm
(das die Subroutinen zum Einlesen und Parsen von Protein Databank-Dateien
enthalten könnte) im Unterverzeichnis DB des BioInf-Unterverzeichnisses
(gesamter Pfad also ~/lib/perl/BioInf/ DB/PDB.pm) müsste demnach mit
package BioInf :: DB :: PDB ;
use strict ;
use warnings ;
sub r e a d P D B E n t r y F r o m F i l e {
...
beginnen. Zur Verwendung in eigenen Programmen können Sie dieses
Modul dann mit
use BioInf::DB::PDB
einbinden und auf die darin enthaltenen Subroutinen z. B. mit
$entry = & BioInf :: DB :: PDB :: r e a d P D B E n t r y F r o m F i l e ( " $ENV {➘
HOME }/ perl4bio / data / lysozym . pdb " )
zugreifen.
3
Genau diese beispielhaften Module und Unterverzeichnisse finden Sie übrigens auch
~
im /perl4bio/lib-Verzeichnis.
162
KAPITEL 9. MODULARISIERUNG
Tipp 9.1: Sinnvoll modularisieren
Die Erstellung und Verwendung von Modulen stellt die allgemeinste Form
der Wiederverwendung von Perl-Code dar. Um voll davon profitieren zu
können, sollten Sie einige Dinge beachten:
• Wann immer Sie eine Subroutine erstellen – verschwenden Sie ein
paar Gedanken daran, ob Sie diese nicht so formulieren können,
dass sie Ihnen später bei ähnlichen Problemen erneut nützlich sein
könnte.
• Packen Sie die Subroutine dann in ein passendes vorhandenes Modul
– oder erstellen Sie ein neues.
• Achten Sie darauf, dass Ihre Module systemunabhängig programmiert sind – machen Sie z. B. nicht zu viele Annahmen über die
Pfade zu irgendwelchen Dateien.
• Oder noch besser: Codieren Sie niemals absolute Dateipfade explizit in Ihren Quellcode – legen Sie solche (und andere ähnlich systemabhängige) Informationen stattdessen in TextDateien (gewöhnlich mit Endungen wie .conf, .cnf oder .ini versehen) ab, die Sie rasch ändern können und die von Ihren Modulen
eingelesen werden.
CPAN
Schließlich muss hier noch CPAN Erwähnung finden - das comprehensive perl archive network (http://www.cpan.org). Wie hieß es doch gleich in
Kapitel 1: Was immer Du auch in Perl programmierst – schon morgen wirst
Du ein Modul finden, das genau das leistet, was Du gestern erst mühsam
programmiert hast! Und finden wird man diese Modul im Zweifelsfalle auf
der CPAN-Website, die auch eine ausgezeichnete Such-Funktion und für jedes Modul eine (meist) ausführliche Dokumentation der darin enthaltenen
Konstanten und Subroutinen bereitstellt.
9.1. RECYCLING 3 - MODULE
163
Übungen
9.1 Üben Sie sich in Teamarbeit! Sie und Ihr Partner/Ihre Partnerin entwickeln je ein Perl-Modul, das folgenden Spezifikationen
entspricht:
1. Ein BioInf::Pattern-Modul soll eine Subroutine enthalten, die in einer übergebenen (Nukleinsäure- oder Protein)Sequenz global nach einem ebenfalls übergebenen Muster
sucht, wobei auch überlappende Treffer gefunden werden
sollen. Ein drittes (optionales) Argument soll angeben, ob
die Sequenz als linear (bei Weglassen oder bei Übergabe eines FALSE-Wertes, etwa 0) oder zirkulär (bei Übergabe von
TRUE, etwa 1) angesehen werden soll (was leichte Anpassungen an Ihrem Muster-Such-Algorithmus erfordert). Als
Rückgabewert soll die Subroutine eine aufsteigend sortierte Liste der Positionen der Treffer zurückgeben. (Einigen
Sie sich mit Ihrem Partner/Ihrer Partnerin, ob die Positionsangaben nach informatischer oder biologischer Zählweise
erfolgen soll, die erste Base also die Position 0 oder 1 hat!)
2. Ein weiteres Modul (BioInf::Restriction) soll eine Subroutine enthalten, die einen Zeiger auf eine Liste, die die
Positionen von Schnittstellen eines Restriktionsenzyms darstellen, als erstes Argument erwartet. Ein zweites Argument soll der Länge der untersuchten Sequenz entsprechen.
Ein drittes und letzte (optionales) Argument soll schließlich wiederum als boolscher Wert verstanden werden, das
darüber entscheidet, ob die Fragmentlängen für eine lineare oder eine zirkuläre Sequenz berechnet werden sollen. Als
Rückgabewert soll die Subroutine eine aufsteigend sortierte
Liste der beim Verdau mit dem jeweiligen Enzym erwarteten Fragmentlängen zurückgeben.
Schreiben Sie dann jeweils unter Verwendung Ihres eigenen
Moduls und des Ihres Partners/Ihrer Partnerin ein Programm,
das eine EMBL-Sequenz (z. B. pBR322.embl in ~/perl4bio/data)
einliest, nach den Schnittstellen eines beliebigen Restriktionsenzyms sucht, die Fragmentlängen berechnet und ausgibt.
(Sie können das Programm auch gerne zusammen mit Ihrem
Parntner/Ihrer Partnerin erstellen.)
164
9.2
BioPerl
Debian
objektorientierte
Programmierung
Bio::Perl
KAPITEL 9. MODULARISIERUNG
BioPerl
BioPerl ist ein 1995 ins Leben gerufenes Open-Source-Projekt, das es sich
zum Ziel gesetzt hat, Perl-Module zu entwickeln, die das Erstellen von
Bioinformatik-Software in Perl zu vereinfachen. BioPerl stellt also keine fertigen Analyse-Programme für Sequenzen, Strukturen etc. zur Verfügung, sondern vielmehr einen Baukasten, dessen Bausteine man für eigene (Programmier-)Konstrukte verwenden kann.
Auf der Homepage von BioPerl (http://www.bioperl.org) finden Sie Anleitungen zur Installation und Benutzung von BioPerl-Modulen. Besonders
einfach ist die Installation, wenn Sie die Linux-Distribution Debian verwenden (http://www.debian.org), denn für Debians Paket-Manager existiert
bereits ein fertiges BioPerl-Bündel.
Ein Großteil von BioPerl wurde objektorientiert programmiert – eine
Seite von Perl, die wir im Folgenden lediglich kurz streifen werden, über die
Sie sich aber z. B. per perldoc in den Hilfe-Seiten perlboot und perltoot
gerne selbst weiter informieren können. Ansonsten stellt das in BioPerl enthaltene Modul Bio::Perl einige nützliche Funktionen in einer Art und Weise
zur Verfügung, die die Verwendung innerhalb unseres bisherigen – imperativen oder prozeduralen – Programmierstils erlaubt.
Die wohl nützlichsten neuen Subroutinen, die von Bio::Perl bereitgestellt (und automatisch in den Namensraum des importierenden Programms
exportiert) werden, sind wohl diejenigen zum Einlesen und Schreiben von
Sequenz-Daten. Dabei werden die verschiedensten Sequenz-Formate unterstützt (und sogar weitgehend automatisch erkannt). Es gibt sowohl Funktionen zum Einlesen von einzelnen Datensätzen als auch von Dateien, die
mehrere Einträge enthalten:
my $hsC3aR = read_sequence ( " $ENV { HOME }/ perl4bio / data /➘
C3aR - Homo_sapiens . embl " ) ;
my @C3aRSeqs = read_all_sequences ( " $ENV { HOME }/ perl4bio /➘
data / C3aR . embl " ) ;
Objekt
Was genau wird aber eingelesen? Nun, hier kommen wir mit der Objektorientiertheit von BioPerl in Kontakt. Die beiden Funktionen geben nämlich
Sequenz-Objekte zurück – read_sequence genau ein solches Objekt und
read_all_sequences eine Liste davon. Objekte sind nämlich zunächst einmal
Zeiger, also Skalare – und könne daher in einer Liste gespeichert werden.
Nun sind Objekte aber ganz besondere Zeiger. Nämlich Zeiger, die auf
eine Ansammlung von Daten und Subroutinen zeigen – wobei die Subroutinen dazu verwendet werden können, die im Objekt gespeicherten Daten
zu manipulieren oder auszugeben oder neue Daten im Objekt abzulegen.
Das BioPerl-Sequenz-Objekt $hsC3aR von oben beherbergt z. B. alle Daten, die auch in der EMBL-Datei, die ihm zugrunde liegt, enthalten waren
9.2. BIOPERL
165
– ganz ähnlich wie die Hash-Zeiger, die die parseEMBLEntry-Subroutine aus
dem letzten Kapitel zurückgibt. Allerdings greifen wir jetzt nicht direkt über
Hash-Schlüssel auf die einzelnen Informationen (die man bei Objekten Attribute nennt) zu, sondern über die dem Objekt zugeordneten Subroutinen
(jetzt Methoden gennant):
print ( " Seqeuenz von ’" , $hsC3aR - > description ) ;
print ( " ’ mit accession number " , $hsC3aR - >➘
accession_number , " :\ n " ) ;
print ( $hsC3aR - > seq , " \ n " ) ;
Zum Aufruf der Objekt-Methoden müssen die in den Objekt-Variablen
gespeicherten Zeiger wiederum dereferenziert werden, was hier – wie bei der
Element-Dereferenzierung von Listen- und Hash-Zeigern – mittels des ->Operators geschieht, gefolgt vom Namen der Methode, die man aufrufen
möchte. Die description-Methode z. B. gibt den Inhalt des DE- (description-) Feldes des EMBL-Eintrages wieder, während die accession_nunmberMethode – kaum überraschend – die accession number der Sequenz liefert.
Die seq-Methode schließlich gibt – wie das Hash-Element mit dem Schlüssel
SEQ aus unserer parseEMBLEntry-Subroutine – die eigentliche Sequenz als ununterbrochene Kette von Nucleotidsymbolen zurück.
Bisher haben wir bei aller Objektorientiertheit nichts gesehen, was wir
mit unserem schnöden Hash nicht auch gekonnt hätten – warum also der
ganze Wirbel um objektorientierte Programmierung? Eine Vorahnung mag
folgender Methodenaufruf vermitteln:
$hsC3aRProt = $hsC3aR->translate
Die tanslate-Methode erzeugt ein neues Sequenz-Objekt $hsC3aRProt,
das nun eine Aminosäure-Sequenz enthält – und diese wird durch Translation der in $hsC3aR gespeicherten DNA-Sequenz ermittelt. Dabei waren nicht
wir es, die eine Subroutine (um deren Bereitstellung per use oder expliziter Programmierung wir uns hätten kümmern müssen) aufgerufen haben
– vielmehr haben wir das Sequenz-Objekt darum gebeten, sich selbst zu
übersetzen! Das ist ein großer Vorteil der objektorientierten Programmierung: die Daten wissen“ selbst genau, wie sie manipuliert werden können –
”
und tun dies auch bei Bedarf. Mit der Bereitstellung von Objekten können
wir noch besser als mit klassischen Subroutinen die Wiederverwendung von
Code fördern – und dazu noch sicherstellen, dass der Code nur auf solche
Daten angewendet wird, für die er gedacht war.
Nun aber zurück zu BioPerl. Das Bio::Perl-Modul stellt auch Subroutinen zur Verfügung, um Sequenz-Objekte wieder in Dateien zurückzuschreiben. Das sähe dann z. B. so aus:
write_sequence(">path/to/C3aR-Homo_sapiens.gb", ’genbank’, $hsC3aR)
Diese Zeile würde das Sequenz-Objekt, das ja aus einer EMBL-Datei
erzeugt wurde, als GenBank-Datei exportieren.
Attribute, von
Objekten
Methoden
166
KAPITEL 9. MODULARISIERUNG
Übungen
9.2 Schreiben Sie ein Programm, das alle in einer EMBLDatei mit mehreren Datensätzen (etwa C3aR.embl in Ihrem
~/perl4bio/data-Verzeichnis) enthaltenen Einträge unter Verwendung von Bio::Perl einliest und in eine GenBank-Datei
schreibt. Hinweis: Erinnern Sie sich daran, wie man an eine bestehende Datei weiteren Text anhängt?
Zwei besondere Schmankerln beruhen auf BioPerls Fähigkeit, bei Bedarf
selbsttätig Informationen aus dem Internet zu beziehen. So erhalten Sie z.
B. mit
my $prot = get_sequence(’swissprot’, "C3AR_HUMAN")
den SwissProt-Eintrag zum spezifizierten Molekül per Internet frisch aus
der Schweiz! Anderes Beispiel:
my $result = blast_sequence($hsC3aRProt->seq)
führt eine Homologiesuche für das angegebene Sequenz-Objekt unter
Verwendung des NCBI-Blast-Servers durch. Das Ergebnis der Suche können
Sie dann z. B. mit
write_blast(">path/to/hsC3aR.blast", $result)
in einer Datei archivieren oder mittels der zahlreichen dem $resultObjekt bekannten Methoden weiter untersuchen.
An dieser Stelle wollen wir die kurze Vorstellung des extrem umfangreichen BioPerl-Projektes beenden und uns der Frage zuwenden, wie wir selbst
Programme schreiben können, die auf das Internet zugreifen.
9.3
Internet
World Wide Web
WWW
Internet-Dienste
TCP/IP
Berners-Lee, Tim
E-Mail
SMTP
Web-Clients
Wer Internet sagt, meint heutzutage meistens eigentlich das World Wide Web (WWW ). Dabei ist das Internet wesentlich mehr als die bunte
Welt von amazon und eBay – denn WWW ist lediglich einer von zahlreichen Diensten, der im weltweiten Netz der per TCP/IP (transmission
control protocol/internet protocol ) miteinander kommunizierenden Rechner
angeboten wird. Allerdings hat erst das 1990 von Tim Berners-Lee am
europäischen Teilchenbeschleuniger CERN (Centre Européen de Recherche
Nucléaire, europäisches Kernforschungszentrum) bei Genf für die rasche Publikation von Daten entwickelte WWW das Internet massentauglich“ ge”
macht.
Weitere wichtige Internet-Dienste sind z. B. verschiedene Protokolle zum
Versenden und Empfangen von E-Mail : SMTP (simple mail transfer
167
9.3. WEB-CLIENTS
protocol ), POP3 (post office protocol vers. 3 ) und IMAP (internet mail
access protocol ). Software und Datenbank-Flatfiles werden häufig per FTP
(file transfer protocol ) und dessen sichere (da verschlüsselte) Variante SCP
(secure copy protocol ) bereitgestellt, Rechner werden per telnet oder SSH
(secure shell ) ferngesteuert, und in letzter Zeit haben Dienste zum Tauschen
von, ähm... sagen wir ganz allgemein: Dateien einen gewissen Ruf erworben.
Schließlich sei noch das Telefonieren über das Internet (VOIP, von voice
over ip) erwähnt, das einen Siegeszug anzutreten sich derzeit anschickt. Generell dient TCP/IP dabei als Transportmedium für die genannten höheren
Protokolle, die dem TCP/IP quasi huckepack aufgeladen werden.
Bei aller Vielfalt beruhen fast alle Internet-Dienste auf einem einzigen
Prinzip: der Client-Server-Architektur . Ein Server ist ein Programm,
das vor allem eines tut: darauf warten, dass ein anderes Programm – der
Client ( Kunde“) – etwas von ihm will. Client und Server können dabei
”
auf dem selben Rechner laufen – wie im Falle von graphischen Anwendungen (den Clients) und dem X-Server auf Ihrem Linux-Rechner – oder, wie
beim Zugriff auf eine Webseite, bis zu 12.800 km voneinander entfernt sein.4
Prinzipiell läuft die Kommunikation aber immer nach ein und dem selben
Schema ab:
Client
Anf rage (request)
−−−−−−−−−−−−−−→
←−−−−−−−−−−−−−−−
Antwort (response)
POP3
IMAP
FTP
SCP
telnet
SSH
VOIP
Client-ServerArchitektur
Server
Client
Server
Wir wollen das Wechselspiel von request (Anfrage) und response (Antwort) anhand der Kommunikation zwischen einem WWW-Client (auch Browser genannt) und WWW-Server verfolgen. Beginnen wir mit dem Eintippen
der Adresse einer Webseite – eines so genannten URLs (uniform resource locator ), etwa http://www.expasy.org/sprot/sprot-search.html. Der URL
beginnt mit http:, was dem Browser anzeigt, dass das hypertext transfer
protocol – ein textorientiertes Protokoll, das (wie die anderen höheren Internet-Protokolle) per TCP/IP durchs Internet reist – zur Kommunikation
mit dem Zielrechner (dem Web-Server) benutzt werden soll. Der Name des
Zielrechners steht hinter den beiden Schrägstrichen: www.expasy.org. Bevor
der Browser nun den angegebenen Rechner kontaktieren kann, muss er jedoch noch dessen Internet- oder IP-Adresse ermitteln – einen (meist als
durch Punkte getrennte Zahlen dargestellten) 4-Byte-Wert, anhand dessen
jeder Rechner im Internet eindeutig identifiziert werden kann. Dazu bemüht
er den domain name service (DNS ), einen weiteren Internet-Dienst in Form
einer Art verteilter Datenbank, die Informationen darüber enthält, welche
IP-Adresse zu welchem Rechnernamen gehört.
4
Häufig nennt man nicht nur die Programme, sondern auch die Rechner, auf denen sie
laufen, Client bzw. Server.
Request
Response
Browser
URL
http:
IP-Adresse
DNS
168
KAPITEL 9. MODULARISIERUNG
Nachdem der Browser nun den Servernamen durch eine DNS-Anfrage in
die IP-Adresse des Servers umgewandelt hat, schickt er diesem eine Anfrage
mit der Bitte, die hinter dem Rechnernamen angegebene Webseite (die Datei
sprot-search.html im Verzeichnis sprot) zurückzuliefern.
HTML
Web-Seite
web page
web site
home page
Hyperlink
query string
Auch die Antwort kommt wieder als Text daher – und zwar in Form
des gewünschten HTML-Dokumentes, das der Server auf seiner Festplatte
vorrätig gehalten hat. HTML – die hypertext markup language – ist eine
Seitenbeschreibungssprache, die es erlaubt, Text und eingefügte Graphiken
logisch und layouttechnisch zu gliedern. Der Browser interpretiert die im Dokument enthaltenen HTML-Anweisungen und stellt den Text dann dementsprechend formatiert dar – was uns in unserem Beispiel die Einstiegsseite zur
Swissprot-Datenbank beschert. Ein HTML-Dokument nennt man übrigens
auch Web-Seite (engl. web page) – nicht zu verwechseln mit web site,
womit man die Gesamtheit aller von einer Startseite (der home page) aus
erreichbaren Web-Seiten meint.
Ein HTML-Dokument kann bekanntermaßen sogenannte Hyperlinks
enthalten – Anweisungen an den Browser, weitere Webseiten anzufordern,
falls ihn der Nutzer/die Nutzerin per Mausklick dazu auffordert. Welcher
URL dabei benutzt werden soll, steht in der entsprechenden HTML-Anweisung.
Die SwissProt-Seite bietet – wie viele andere interaktive Webseiten –
neben Hyperlinks auch die Möglichkeit, mit Hilfe von Eingabefeldern individualisierte Anfragen an den Webserver zu formulieren. Bei der SwissProtEinstiegsseite haben wir z. B. die Möglichkeit, den Server zu bitten, die
SwissProt-Datenbank nach bestimmten Schlüsselwörtern – etwa C3a, Species Human – zu durchsuchen. In diesem Falle werden die Nutzer-Eingaben
in Form von Schlüssel-Wert-Paaren in Form einer Zeichenkette (dem query
string ) an den URL angehängt – was zu URL-Monstern wie
http :// us . expasy . org / cgi - bin / get - entries ? db = sp & db = tr & DE➘
= C3a & GNc = AND & GN =& OC = Homo + sapiens & wild =1& view = full &➘
num =100
CGI
führt. Da der Server natürlich nicht für alle möglichen Anfragen fertige
HTML-Seiten parat haben kann, werden Antwort-Seiten für solche individuelle Anfragen dynamisch erzeugt. Dazu ruft der Server ein Programm
– hier get-entries im cgi-bin-Verzeichnis – auf und übergibt diesem die
Schlüssel-Wert-Paare der Anfrage. Auf deren Basis erzeugt das Programm
dann eine HTML-Seite mit den gewünschten Informationen (wie hier eine
Treffer-Liste der Datenbanksuche). Dieses Verfahren nennt man übrigens
CGI , von common gateway interface
Nun wollen wir aber den Bogen zurück zu Perl schlagen. Perl ist ja eine
Sprache, die auch gerne zur Automatisierung von systemnahen Vorgängen
benutzt wird – und wenn ein Mensch mit Hilfe eines Web-Browsers mit ei-
169
9.3. WEB-CLIENTS
nem Web-Server kommunizieren kann, sollte ein Perl-Programm das doch
wohl auch können! Und tatsächlich bietet Perl alles, was man braucht, um
Aufgaben automatisch quer durch das Internet zu lösen. Mit Perl kann man
sogar die verschiedensten Arten von Servern programmieren – wir wollen
uns hier allerdings auf die Client-Seite konzentrieren, genauer: auf die Programmierung von Web-Clients.
Und so einfach, wie es der Name des benötigten Moduls (LWP::Simple,
von library WWW for perl ) verspricht, ist es auch wirklich:
LWP::Simple
use LWP :: Simple ;
print ( get ( " http :// www . expasy . org / sprot / sprot - search .➘
html " ) ) ;
Die von LWP::Simple exportierte Subroutine get versucht das von dem
in ihrem Argument angegebenen URL referenzierte HTML-Dokument herunterzuladen. Schlägt dies fehl, so liefert sie ein undef zurück – eine gute
Möglichkeit, den Erfolg eines solchen requests zu überprüfen.
An dem ausgegebenen HTML-Code fällt nun leider auf, dass er – mangels Aufbereitung durch einen Browser – nicht wirklich gut lesbar ist. Wir
könnten den erhaltenen String natürlich irgendwo speichern und uns diese Datei dann mit einem Browser ansehen – alternativ könnten wir aber
auch versuchen, möglichst viel HTML los zu werden, um eine einigermaßen
lesbare Nur-Text-Variante zu erhalten. Dabei können wir uns zu Nutze machen, dass praktisch alle HTML-Anweisungen – die sogenannten tags (engl.
für Markierungen, Anhänger) – mit je einer spitzen Klammer beginnen und
enden: <head>, <body>, <p>, <h1>, <table>... Ein regulärer Ausdruck wie
s/<.+?>//gs
sollte in erster Näherung die meisten HTML-Tags entfernen5 . Beachten
Sie bitte die Verwendung des ?-Quantors, um nicht auf einen Schlag alles
vom ersten < bis zum letzten > zu entfernen, und des s-Modifikators, da
HTML-Tags auch Zeilenumbrüche enthalten dürfen. Weiterhin stören noch
die HTML-Sonderzeichen, die stets mit einem & beginnen und mit einem
Semikolon ; enden. Auch diese werden wir mit einem globalen substitute
los (bzw. ersetzen sie durch den Unterstrich _ um anzudeuten, dass sich an
dieser Stelle einmal ein Sonderzeichen befand):6
s/&.+?;/_/g
Schließlich stören uns womöglich noch multiple Leerzeilen, die wir mit
s/n+/n/g
zu einzelnen Leerzeilen zusammenschrumpfen.
5
Nicht
erkannt
werden
z.
B.
Tags,
die
zusätzliche
>
enthalten,
etwa
<img src="bild.png" alt="A -> B">
6
Falls man einzelne HTML-Sonderzeichen in menschenlesbarer Form erhalten möchte,
kann man diese selbstverständlich zuvor durch einzelne s/.../.../-Operationen in die
entsprechenden Zielzeichen übersetzen.
Tags
170
KAPITEL 9. MODULARISIERUNG
Tipp 9.2: HTML nutzen, ohne es zu können
Mit oben beschriebenen Methoden bekommt man meist recht schnell eine
einigermaßen menschenlesbare Nur-Text-Version einer HTML-Seite; gerade für die automatische Weiterverabteitung von Web-Inhalten – z. B.
der Extraktion von Informationen mittels regulärere Ausdrücke – erweist
es sich jedoch oft als sinnvoll, auf dem Original-HTML-Code zu arbeiten, da dieser ja gerade aufgrund der Strukturierung durch HTML-Tags
zusätzliche Markierugspunkte liefert, an denen man sich orientieren kann.
Wenn Sie also mittels eines Perl-Programms Daten aus einer Website weiterverabeiten möchten, schauen Sie sich die Website daher ruhig in einem
Text-Editor an und schauen Sie nach, wo und wie die Daten, an denen Sie
eigentlich interessiert sind, im HTML-Code versteckt sind – selbst falls
Sie (noch) kein HTML können!
Rebase
Nun ist die SwissProt-Einstiegsseite sicher nicht die spannendste Webseite, die man sich als Text ansehen möchte. Interessanter wäre eine Webseite,
die dynamisch erzeugt wird und u. U. bei jedem Aufruf verschiedene Informationen liefert. Ein Beispiel wäre die Restriktions-Enzym-Datenbank
Rebase der Firma New England Biolabs, bei der man sich unter
http://rebase.neb.com/cgi-bin/enewget?4
über die im letzen Vierteljahr neu bekannt gewordenen Restriktionsenzyme informieren kann. Ein Progrämmchen wie
Listing 9.5: Automatische Web-Abfrage
use LWP :: Simple ;
my $newEnz = get ( " http :// rebase . neb . com / cgi - bin / enewget➘
?4 " ) ;
if ( defined ( $newEnz ) ) {
$newEnz =~ s / <.+? >// gs ;
$newEnz =~ s /&.+?;/ _ / g ;
$newEnz =~ s /\ n +/\ n / g ;
print ( $newEnz ) ;
}
else {
die ( " Keine Seite geliefert ! " ) ;
}
könnte z. B. dahingehend ausgebaut werden, jeden Tag nachzusehen,
ob ein neues Restriktionsenzym gefunden wurde, das an einer bestimmte
Erkennungssequenz schneidet.
GET
POST
Leider lassen sich nicht alle interaktiven Websites auf diese Art (dem
GET -Verfahren) bedienen – oft wird auch eine alternative Art der Übermittlung der Eingabe des Benutzers/der Benutzerin verwendet (POST -
9.3. WEB-CLIENTS
171
Verfahren), bei der die Schlüssel-Wert-Paare nicht per URL übertragen werden7 . Das stellt aber auch kein unüberwindliches Hindernis dar – die PerlModule LWP::UserAgent und HTTP::Request stellen alles nötige bereit, um
auch solche Websites automatisch abfragen zu können. Hier kommen erneut
die objektorientierten Eigenschaften von Perl ins Spiel. Falls Sie also auch
diese Seite von Perl kennen lernen möchten, wären diese Module – zusammen
mit den bereits erwähnten Hilfe-Seiten perlboot und perltoot – vielleicht
ein ganz guter Einstiegspunkt.
Übungen
9.3 Schreiben
Sie
ein
Programm,
das
die
neuesten
Restriktionsenzym-Daten von Rebase herunterlädt, die Namen und Erkennungssequenzen der neuen Enzyme aus dem
heruntergeladenen Text extrahiert und dann automatisch
Restriktionskarten für einer Liste von Sequenzen erstellt.
Verwenden Sie dazu die in Übung 9.1 auf S. 163 enwickelten
Module BioInf::Pattern und BioInf::Restriction und denken
Sie daran, die Erkennungssequenzen vor der Suche nach Schnittstellen in geeignete reguläre Ausdrücke umzuwandeln (Stichwort
ambiguitive Basensymbole“; beschränken Sie sich jedoch, wie
”
in Übung 5.1 auf S. 83, auf N)!
7
Dies gilt z. B. auch für das Entrez -Webinterface; das NCBI bietet jedoch besondere
Zugriffsmethoden (ESearch, EGet) zur Datenbankabfrage an, die auch mit LWP::Simple
funktionieren.
172
KAPITEL 9. MODULARISIERUNG
Aufgaben
9.1 Erstellen Sie ein Perl-Modul BioInf::DB::PDB, das die in Abschnitt 8.3 ab S. 141 entwickelten Methoden zum Einlesen
und Verarbeiten von PDB-Dateien bereitstellt; wenn Sie wollen,
können Sie auch die Subroutinen aus Aufgabe 8.2 auf S. 151 und
Aufgabe 8.4 auf S. 151 hinzufügen. Testen Sie das Modul, indem
Sie das Programm zur Erzeugung einer 3D-Darstellung einer Proteinstruktur aus Übung 8.2 auf S. 150 so umschreiben, dass es
auf das Modul zugreift statt auf im Programm selbst definierte
Subroutinen.
9.2 Auf der PDB-Website wird jeden Monat das Molecule of the
”
month“ gekürt. Schreiben Sie ein Programm, das zunächst
die PDB-ID des aktuellen Moleküls des Monats ermittelt, die
zugehörige Strukturdaten herunterlädt und ein 3D-Bild davon
erzeugt. Die PDB-ID können Sie dem HTML-Quelltest der
Webseite unter
http://www.rcsb.org/pdb/static.do?p=education discussion/molecule of the month/current month.html
entnehmen;
suchen
Sie
im
HTML-Quelltext
nach
structureId=XXXX, wobei X für ein beliebiges alphanumerisches Zeichen steht; sollten mehrere PDB-IDs vorhanden
sein, beschränken Sie sich auf die erste. Die eigentliche PDBDatei können Sie dann von
http://www.rcsb.org/pdb/downloadFile.do?fileFormat=pdb&compression=NO&structureId=XXXX
herunterladen, wobei Sie XYYY durch die zuvor ermittelte
PDB-ID ersetzen. Zur 3D-Visualisierung schließlich könne Sie
das Programm verwenden, das Sie in der vorherigen Aufgabe
bzw. in Übung 8.2 auf S. 150 geschrieben haben.
Kapitel 10
Mehr Biologie
Nach Studium dieses Kapitels können Sie
• in Ihren Programmen beliebig verteilte Zufallszahlen erzeugen
• Zufallszahlen nutzen, um zufällige Nucleinsäure- und ProteinSequenzen zu generieren
• in Sequenzen Mutationen nach eigenen Wahrscheinlichkeitsvorgaben
setzen
• Gegenüberstellungen von Sequenzen erzeugen, anhand derer Sie sich
einen ersten Eindruck über deren Ähnlichkeitsgrad machen können
10.1
Zufall und Mutation
Nach der – in weiten Teilen der Welt – anerkannten Evolutionstheorie von
Charles Darwin 1 hat der Zufall in der Entwicklung der zahlreichen Arten
von Lebewesen, die unseren Planeten bevölkern, eine entscheidende Rolle
gespielt. Durch die ständige Erzeugung von Varianten (Mutationen) der
jeweils vorherrschenden genetischen Ausstattung hat er erst das Rohmaterial geschaffen, mit dem die Selektion arbeiten konnte, um die unter den
gegebenen Umständen jeweils am besten angepaßte Variante auszuwählen
( survival of the fittest“ heißt keinesfalls Überleben des Tüchtigsten“, son”
”
dern des Passendsten“!). Diese Erkenntnis stellt eine großartige Leistung
”
dar – insbesondere wenn man berücksichtigt, dass wir den molekularen Hintergrund von Mutation erst seit Mitte des 20. Jahrhunderts (Veröffentlichung
1
1809 – 1882
173
Darwin, Charles
Mutationen
Selektion
174
Watson, James
Crick, Francis
rand
Zufallszahl
KAPITEL 10. MEHR BIOLOGIE
der DNA-Struktur durch James Watson und Francis Crick : 1953) kennen2 .
Da wir nun wissen, dass Mutationen letztlich auf Veränderungen in der
DNA-Sequenz eines Genoms – die wir als Zeichenkette darstellen können –
beruhen, können wir die Zufallskomponente der Evolution auch per Computer nachspielen. Dazu benötigen wir eine Methode, in unseren Programmen den Zufall wirken zu lassen. Perl stellt zu diesem Zweck die Funktion
rand (von engl. random, zufällig“, wahllos“) zur Verfügung, die eine Zu”
”
fallszahl von 0 bis a zurückgibt, wobei die 0 in der Menge der möglichen
Rückgabewerte eingeschlossen ist, a jedoch nicht (mathematisch durch [0,
a) ausgedrückt):
Listing 10.1: Zufallszahlen
for ( my $i = 0; $i < 10; $i ++) {
my $zufallszahl = rand (4) ;
print ( " $i . Zufallszahl : $zufallszahl \ n " ) ;
if ( $zufallszahl == 4) {
print ( " Dies wird niemals eintreten !\ n " ) ;
}
}
Dieses Programm wird bei Ausführung eine Ausgabe ähnlich der Folgenden erzeugen:
0.
1.
2.
3.
4.
5.
6.
7.
8.
9.
Zufallszahl :
Zufallszahl :
Zufallszahl :
Zufallszahl :
Zufallszahl :
Zufallszahl :
Zufallszahl :
Zufallszahl :
Zufallszahl :
Zufallszahl :
2.68405868701304
0.622857856926089
0.339866369184662
3.61090639642114
3.03983270707643
0.686142139078527
2.26990228666669
3.27923524303
2.24162815507738
3.91808446706546
Wenn Sie dieses Programm laufen lassen, sollte die Ausgabe selbstverständlich andere Werte liefern – sonst wären das ziemlich lausige Zufallszahlen!
Das Argument von rand ist übrigens optional – wird es weggelassen,
liefert rand eine Zufallszahl aus dem Intervall [0, 1).
Tatsächlich ist es gar nicht so einfach, mit einem Computer – einer Maschine, die ja für ihre hohe Präzision gerade beim Umgang mit Zahlen be2
Bereits 1944 jedoch hatte Erwin Schrödinger – einer der Väter der Quantenphysik – in
seinem Essay Was ist Leben? gefolgert, dass die kleinsten materiellen Träger genetischer
Information in der Größenordnung einzelner Atome liegen müssten. Der von ihm daraufhin
geprägte Begriff des aperiodischen Kristalls“ hat in der Struktur der DNA eine glänzende
”
Bestätigung erhalten.
10.1. ZUFALL UND MUTATION
175
kannt ist – echte Zufallszahlen zu erzeugen. Vielmehr handelt es sich um
Pseudozufallszahlen, die einer iterativen mathematischen Funktion entstammen, deren Funktionswert auf wenig vorhersehbare Weise vom Wert
Ihres Argumentes abhängt. Für jede neue Zufallszahl wird das letzte Ergebnis – also die letzte Zufallszahl – als neues Argument verwendet3 .
Pseudozufallszahlen
Da stellt sich die Frage, welcher Wert als erstes in diese Funktion eingesetzt wird. Hierfür bastelt sich Perl eine Zahl (seed oder Samen genannt)
zurecht, in die mehr oder minder zufällige Systemdaten eingehen – etwa
die aktuelle Uhrzeit, die Menge an belegtem Arbeitsspeicher, die laufende
Nummer des aktuellen Prozesses etc.
Falls man – z. B. zum Zwecke des Debuggens – eine reproduzierbare
Zahlenfolge haben möchte, kann man diese Startzahl mittels der srandAnweisung auch konkret vorgeben:
srand
srand(42)
Fügt man eine solche Anweisung an den Anfang von Listing 10.1 auf S.
174 ein, erhält man bei jedem Lauf die selbe Zahlenfolge als Ausgabe.
Die von rand erzeugten Zufallszahlen sind im angegebenen Bereich [0, a)
gleichverteilt – die von rand benutzte Wahrscheinlichkeitsdichtefunktion ist also 0 für x < 0 bzw x ≥ a und konstant 1/a für das Intervall [0, a).
Hierbei handelt es sich um eine kontinuierliche Verteilung – wirklich alle
reellen Zahlen von 0 bis (ausschließlich) 1 können vorkommen. Theoretisch
zumindest.
An dieser Stelle muss noch ein Wort zu reellen Zahlen in Computerprogrammen fallen. Es ist ja gerade ein Charakteristikum der Menge der reeller
Zahlen, dass sie auch alle unendlichen, nicht-periodischen Dezimalbrüche
enthält. Um einen solche Zahlenwert wirklich exakt zu darzustellen, müsste
man tatsächlich alle der unendlich vielen Nachkommastellen speichern –
was mit einem naturgemäß immer endlichen Computerspeicher schlichtweg
unmöglich ist. Daher behandelt man reelle Zahlen im Computer meist als
sog. Fließkommazahlen – also Zahlen mit einer begrenzten Anzahl an
Nachkommastellen, die ggfs. noch mit einem (ebenfalls endlichen!) Exponenten multipliziert werden, um einen größeren Zahlenbereich darstellen zu
können. Und die Rückgabewerte von rand beinhalten daher lediglich diejenigen Fließkommazahlen, die von Perl im Intervall [0, 1) auch dargestellt
werden können – was aber noch immer ganz schön viele sind.4
3
Um z. B. für kryptographische Anwendungen ganz besonders zufällige Zufallszahlen zu
erzeugen, sollte man sich nicht auf den in Perl eingebauten Zufallsalgorithmus verlassen,
sondern entsprechend spezialisierte Module einbinden.
4
Gerade für wissenschaftliche Anwendungen gibt es für praktisch alle Programmiersprachen – so auch für Perl – Bibliotheken, die das Rechnen mit einer beliebig hohen
Anzahl von Nachkommastellen ermöglichen.
Gleichverteilung
Wahrscheinlichkeitsdichtefunktion
kontinuierliche
Verteilung
Fließkommazahlen
176
KAPITEL 10. MEHR BIOLOGIE
rand liefert also sozusagen pseudo-reele Pseudo-Zufallszahlen“ 5 . In der
diskrete Verteilung
Klassen
”
Bioinformatik hat man es jedoch häufig auch mit diskreten Verteilungen zu
tun – also Zufallsereignisse, deren mögliche Ausgänge in Klassen eingeteilt
werden.
So steht man gelegentlich vor der Aufgabe, mehr oder minder zufällige
Nukleinsäure- oder Aminosäuresequenzen zu erzeugen – meist als NegativKontrolle für Programme, die in echten“ Sequenzen irgend welche Muster
”
(Bindungsstellen, Introns/Exons, Phosphorylierungsstellen etc.) aufspüren
sollen. Können wir die rand-Funktion nutzen, um z. B. eine solche zufällige
DNA-Sequenz zu erzeugen? Die Antwort lautet ganz klar: ja! Wir müssen lediglich die kontinuierliche Menge der möglichen Ergebnisse der rand-Funktion
in mehrere Intervalle unterteilen, die wir den verschiedenen Basentypen zuordnen:
0
|←
A
1
→| ←
C
2
→| ←
G
3
→| ←
T
4
→|
Sodann ermitteln wir, in welches Intervall die Ergebnisse der einzelnen
rand-Aufrufe jeweils fallen. Dazu läßt sich z. B. die int-Funktion benutzen,
die den ganzzahligen Anteil einer reellen Zahl zurückgibt:
int(rand(4))
liefert 0, 1, 2 oder 3 als Ergebnis – je nachdem, ob rand eine Zahl im
Intervall [0, 1), [1, 2), [2, 3) oder [3, 4) erzeugt. Somit können wir diesen
Ausdruck verwenden, um z. B. aus einer mit
@BASE_SYMBOLS = ("A", "C", "G", "T")
initialisierten Liste per
$BASE_SYMBOLS[int(rand(4))]
eines der vier möglichen Basen-Symbole zufällig auszuwählen.
Hier tauchen A, C, G und T mit genau gleichen Wahrscheinlichkeiten von
p = 0, 25 auf – aber wie sieht es denn mit einer Zufalls-Sequenz mit z. B.
beliebigem GC-Gehalt aus? Oder können wir gar die Häufigkeit jeden Basentyps vorgeben? Ja, wenn wir die den verschiedenen Basen zugeordneten
Teilintervalle der Ergebnismenge der rand-Funktion um so größer machen, je
häufiger die jeweilige Base in der Sequenz vorkommen soll. Genauer gesagt
soll die Länge eines jeden Teilintervalls proportional zur Wahrscheinlichkeit sein, dass das Zufallsereignis in die jeweilige Ereignisklasse fällt. Dann
schauen wir wiederum einfach nach, in welches der Intervalle die erzeugte
Zahl fällt und hängen die zugehörige Base an unsere Sequenz an. Da hier
aber die Intervallgrenzen sowieso nicht – wie im obigen Beispiel mit gleichen
Wahrscheinlichkeiten für alle Basen – mit den ganzzahligen Anteilen der er5
In Zukunft werden wir uns die beiden pseudos“ der Lesbarkeit wegen schenken –
”
hauptsache, Sie behalten sie im Gedächtnis!
177
10.1. ZUFALL UND MUTATION
haltenen Zufallszahl übereinstimmen wird, beziehen wir uns ab jetzt immer
auf das Einheitsintervall [0, 1), das der Ergebnismenge eines argumentlosen
rand-Aufrufs entspricht.
Die Intervall-Teilung, die zu einer Wunsch-Zusammensetzung von z. B.
20% A, 40% C, 30% G und 10% T gehören würde, kann man sich dann
graphisch wie folgt verdeutlichen:
0
|←
A
0,2
0,2
→| ←
C
0,4
0,6
→| ←
G
0,3
0,9
T
→| ← 0,1
Die 20% A nehmen also das 0,2 Einheiten breite Intervall von 0 bis (ausschließlich) 0,2 ein, C wird das 0,4 Einheiten (entsprechend 40%) breite Intervall [0,2, 0,6) zugeordnet, G wird mit den 0,3 Einheiten (30%) innerhalb
[0,6, 0,9) bedacht, und T muss sich mit den restlichen 0,1 Einheiten (10%)
in [0,9, 1) begnügen. Um zu bestimmen, in welches Intervall eine Zufallszahl
(im Intervall [0, 1)) fällt, benötigen wir also für jede Base die aufsummierten Intervallbreiten ihrer linken Vorgänger“– auch kummulative Vertei”
lungsfunktion genannt.
Folgendes Perl-Modul stellt u. a. drei Funktionen bereit, um mit diskreten kummulativen Verteilungsfunktionen Zufallssequenzen zu erzeugen. Eine
Funktion (calcCumDistr) berechnet aus einer Liste mit Häufigkeiten die korrespondierende kummulative Verteilungsfunktion, und eine zweite Funktion
(getRandomIntervalIndex) wählt anhand einer solchen kummulativen Verteilungsfunktion zufällig eins der ihr zugrunde liegenden Intervalle aus. Die
dritte Funktion (getRandomBase) schließlich gibt anhand einer Verteilungsfunktion mit 4 Elementen (entsprechend den vier Basen) ein Basensymbol
mit der richtigen Wahrscheinlichkeit zufällig zurück:
Listing 10.2: BioInf::Random-Modul (Ausschnitt)
1
package BioInf :: Random ;
2
3
4
use strict ;
use warnings ;
5
6
7
# List of base symbols
our @BASE_SYMBOLS = ( " A " , " C " , " G " , " T " ) ;
8
9
10
11
12
13
14
15
16
# expects a list of frequencies and
# returns the ( normalised ) cummulative
# distribution function
sub calcCumDistr {
my @composition = @_ ;
my @cumDistr = () ;
my $cumSum = 0;
for ( my $i = 0; $i < scalar ( @composition ) ; $i ++) {
1
→|
kummulative
Verteilungsfunktion
178
$cumSum = $cumSum + $composition [ $i ];
push ( @cumDistr , $cumSum ) ;
17
18
}
for ( my $i = 0; $i < scalar ( @composition ) ; $i ++) {
$cumDistr [ $i ] = $cumDistr [ $i ] / $cumSum ;
}
return @cumDistr ;
19
20
21
22
23
24
KAPITEL 10. MEHR BIOLOGIE
}
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# expects a ( normalised ) cummulative
# density function and returns a random
# integer in the range [0 , number of elments )
# with probabilities according to the
# density function
sub g e t R a n d o m I n t e r v a l I n d e x {
my @cumDistr = @_ ;
my $intervalPos = rand ;
for ( my $i = 0; $i < scalar ( @cumDistr ) ; $i ++) {
if ( $intervalPos < $cumDistr [ $i ]) {
return $i ;
}
}
}
40
41
42
43
44
45
46
47
48
# expects a ( normalised ) cummulative
# density function with 4 elements
# representing the values for A , C , G
# and T , respectively , and returns a
# random base symbol
sub getRandomBase {
return $BASE_SYMBOLS [& g e t R a n d o m I n t e r v a l I n d e x ( @_ ) ];
}
49
50
...
Die Subroutine calcCumDistr in den Zeilen 12 – 24 berechnet die kummulative Verteilungsfunktion, indem sie in der Schleife ab Zeile 16 nach
und nach alle relativen Häufigkeiten aufsummiert und die jeweilige bisherige Summe in die Liste der Funktionswerte aufnimmt. In den Zeilen 20 - 22
folgt noch ein Normierungsschritt; der wäre zwar nicht nötig, aber so bekommen wir garantiert – auch bei Verwendung von absoluten Häufigkeiten
– eine Verteilungsfunktion, deren Definitionsbereich immer im selben Intervall liegt. Dieses Intervall entspricht genau dem Wertebereich der argumentlosen rand-Funktion (nämlich [0, 1)), so dass wir z. B. der folgenden getRandomIntervalNumber-Subroutine kein zusätzliches Argument für die
obere Schranke der Zufallszahlen mitgeben müssen.
10.1. ZUFALL UND MUTATION
179
Die getRandomIntervalIndex-Subroutine (Zeilen 31 –41) erwartet lediglich eine normierte Verteilungsfunktion, wie sie mittels calcCumDistr aus
einer Häufigkeitsverteilung berechnet werden kann. Die Auswahl eines der
Häufigkeits-Intervalle geschieht dabei folgendermaßen: In Zeile 33 erzeugen
wir eine Zufallszahl im Intervall [0, 1) und in der Schleife von Zeile 35 bis 40
ermitteln wir, in welches Teilintervall der übergebenen Verteilungsfunktion
sie fällt. Dazu gehen wir deren Werte in aufsteigender Reihenfolge durch,
und sobald wir einen Verteilungswert finden, der größer als die gegebene
Intervallposition ist, können wir den aktuellen Index ($i) als ausgewähltes
Intervall zurückgeben
getRandomBase (Zeilen 48 – 52) schließlich macht nichts weiter als mit der
übergebenen (4-elementigen) Verteilungsfunktion per getRandomIntervalNumber
eines der Intervalle auszuwählen und ein Basensymbol zurückzugeben, das
dem zurückgegebenen Intervall-Index entspricht.
Ein kleines Programm, das sich diese Subroutinen zu Nutze macht, könnte
in etwa wie folgt aussehen:
Listing 10.3: Zufallssequenz
1
use BioInf :: Random ;
2
3
4
5
6
print ( " Zufallssequenz nach B a s e n w a h r s c h e i n l i c h k e i t ➘
erstellen .\ n " ) ;
print ( " Länge der gewünschten Sequenz : " ) ;
my $len = < STDIN >;
chomp ( $len ) ;
7
8
9
10
11
12
13
my @composition = () ;
for ( my $i = 0; $i < 4; $i ++) {
print ( " Häufigkeit von " , $BioInf :: Random ::➘
BASE_SYMBOLS [ $i ] , " : " ) ;
$composition [ $i ] = < STDIN >;
}
chomp ( @composition ) ;
14
15
16
my @cumDistr = & BioInf :: Random :: calcCumDistr (➘
@composition ) ;
print ( " Kumulative Verteilung : @cumDistr \ n " ) ;
17
18
19
20
21
22
23
my $seq = " " ;
for ( my $i = 0; $i < $len ; $i ++) {
$seq = $seq . & BioInf :: Random :: getRandomBase (➘
@cumDistr )
}
print ( " Zufallssequenz : $seq \ n " ) ;
foreach my $base ( @BioInf :: Random :: BASE_SYMBOLS ) {
180
print ( " Anteil $base : " , ( $seq =~ s / $base / $base / gi ) /➘
$len , " \ n " ) ;
24
25
KAPITEL 10. MEHR BIOLOGIE
}
In den Zeilen 3 – 6 erfolgt die Eingabe der gewünschten Länge der zu erzeugenden Zufallssequenz. In der Schleife ab Zeile 9 werden die gewünschten
Häufigkeiten der vier Basen abgefragt; dabei ist es – dank der Normierungsfunktion von calcCumDistr – egal, ob wir relative (prozentuale) oder absolute Wunsch-Häufigkeiten eingeben. In Zeile 15 wird aus den Häufigkeiten
deren kummulative Verteilungsfunktion berechnet und deren Werte in Zeile 16 zur Kontrolle ausgegeben. In der Schleife von Zeile 19 bis 21 wird
sodann die Zufallssequenz durch wiederholtes Aufrufen von getRandomBase
mit der ermittelten Verteilungsfunktion generiert. Schließlich geben wir in
den restlichen Zeilen die erhaltene Sequenz sowie deren relative Basenzusammensetzung aus, um sie mit den Sollwerten vergleichen zu können. Ein
Testlauf würde dann zu einer Ausgabe ähnlich der folgenden führen:
Kummulative Verteilung : 0.2 0.6 0.9 1
Zufallssequenz : ➘
ATTCACGGGGGCACCGAGCGGCTACACCCGGGCCGAGCAACACCCCCCAA
Anteil A : 0.24
Anteil C : 0.42
Anteil G : 0.28
Anteil T : 0.06
Die genaue Basenzusammensetzung der erzeugten Zufallssequenz weicht
natürlich von der Wunschvorstellung ab; aber nach dem Gesetz der großen
Zahlen sollten sich die relativen Basenhäufigkeiten mit steigender Sequenzlänge den vorgegebenen Wahrscheinlichkeiten annähern.
Sequence shuffling
maschinelles Lernen
neuronale Netze
Gelegentlich steht man jedoch vor der Aufgabe, eine Zufallssequenz mit
exakt der gleichen Basenzusammensetzung wie eine Vergleichssequenz zu generieren. Eine Möglichkeit, dies zu tun, besteht im sog. sequence shuffling .
Dabei werden Teile der Ausgangssequenz nach verschiedensten Algorithmen
aus der Sequenz entfernt und an anderer Stelle wieder eingefügt.
Shuffling verwendet man u. a. um Negativkontrollen für Algorithmen
zu erzeugen, die auf maschinellem Lernen beruhen – etwa so genannte neuronale Netze. Solche Algorithmen können dahingehend trainiert
werden zu unterscheiden, ob eine genomische DNA-Sequenz z. B. einen Promotor enthält oder nicht. Dazu muss der Algorithmus mit einem Satz von
Promotor- und einem Satz von Nicht-Promotor-Sequenzen gefüttert“ wer”
den. Um nun sicherzustellen, dass der Algorithmus am Ende des Lernvorgangs tatsächlich Promotoren anhand möglichst globaler Merkmale erkennt
– und sich nicht auf zufällig in allen Promotoren des Trainingssatz enthaltene
kurze Sequenzmotive konzentriert –, testet man den Algorithmus dann auf
geshuffelten Promotor-Sequenzen. Beim Shuffling bleiben kurzen Sequenzmotive nämlich zu einem gewissen Grad erhalten, während großräumige
10.1. ZUFALL UND MUTATION
181
Strukturen zerstört werden. Schlägt der Algorithmus auch nach dem Shuffling noch an und behauptet weiterhin, bei den geshuffelten Sequenzen handele es sich um Promotoren, sollte man das Training dringend mit einem
anderen Trainingssatz wiederholen.
Übungen
10.1 Schreiben Sie ein Programm (oder eine Subroutine für
BioInf::Random), das eine Sequenz einer wählbaren Anzahl von
Shuffle-Schritten unterzieht, bei denen jeweils ein Teilstück konstanter (aber wählbarer) Länge herausgeschnitten und an anderer
Stelle wieder eingesetzt wird. (Hinweis: Schauen Sie sich mittels
perldoc -f substr noch einmal die Syntax der substr-Funktion
an...)
Nun aber zurück zur Evolution. Diese baut sich ja selten komplett neue
zufällige DNA-Sequenzen auf, sondern arbeitet mit vorhandenem Material
und führt meist nur leichte Veränderungen an diesem durch. Um diesen Vorgang im Computer nachzuvollziehen, wollen wir eine Subroutine schreiben,
die in einer DNA-Sequenz eine Punktmutation vornimmt. Dabei müssen wir
den Zufall gleich an zwei Stellen wirken lassen:
1. Zunächst müssen wir bestimmen, an welcher Position die Mutation
stattfinden soll.
2. Dann müssen wir entscheiden, durch welche Base die ursprünglich an
der zu mutierenden Stelle vorhandenen Base ersetzt werden soll.
Eine zu mutierende Position können wir einfach ermitteln, indem wir als
Argument der rand-Funktion die Länge der Sequenz einsetzen:
$pos = int(rand(length($seq)))
Bei der Bestimmung der neuen Base wollen wir berücksichtigen, dass
verschiedene Umwandlungen von einer Base in eine andere durchaus mit
verschiedenen Wahrscheinlichkeiten erfolgen können. So beobachtet man z.
B., dass Transitionen (Umwandlungen einer Purin- in eine andere Purinbzw. einer Pyrimidin- in eine andere Pyrimidinbase, also A→G, G→A, C→T
und T→C) häufiger vorkommen als Transversionen (Umwandlungen einer Purinbase in eine Pyrimidinbase und umgekehrt; A→C, A→T, C→A, C→G,
G→C, G→T, T→A und T→G). Wenn wir (vereinfachend) annehmen, dass alle Transitionen und Transversionen mit jeweils gleicher Wahrscheinlichkeit
passieren, können wir eine entsprechende Mutationsmatrix aufstellen, die
Transitionen
Transversionen
Mutationsmatrix
182
KAPITEL 10. MEHR BIOLOGIE
angibt, wie wahrscheinlich die Umwandlung eines in der linken Spalte angegebenen Nucleotids in ein Nucleotid der oben aufgeführten Typen ist:6
A
C
G
T
A
C
G
T
c
b
a
b
b
c
b
a
a
b
c
b
b
a
b
c
a steht dabei für die Wahrscheinlichkeit einer Transition pro Zeiteinheit
(die z. B. einer Generation entsprechen könnte), b für die Wahrscheinlichkeit
einer Transversion, und c ist einfach der Betrag der Rest-Wahrscheinlichkeit,
dass gar nichts passiert (1−a−2×b). D. h., genau genommen ist b eigentlich
die halbe Wahrscheinlichkeit für eine Transversion, da es doppelt so viele
Transversions- wie Transitionsmöglichkeiten gibt.
mehrdimensionale
Liste
Wie können wir so eine Matrix in Perl repräsentieren? Mittels einer
mehrdimensionalen Liste! Angenommen, wir haben uns passende Werte
für a und b ausgedacht, können wir eine solche Liste wie folgt anlegen:
Listing 10.4: Mutationsmatrix initialisieren
sub g e t D N A M u t a t i o n M a t r i x
my $a = shift ( @_ ) ;
my $b = shift ( @_ ) ;
my $c = 1 - $a - 2* $b ;
my @mutationMatrix = (
["", "A", "C", "G",
[ " A " , $c , $b , $a ,
[ " C " , $b , $c , $b ,
[ " G " , $a , $b , $c ,
[ " T " , $b , $a , $b ,
);
return @mutationMatrix
}
{
"T"],
$b ] ,
$a ] ,
$b ] ,
$c ]
Der Zugriff auf ein Element einer solchen Matrix bzw. mehrdimensionalen Liste erfolgt gemäß
$element = $mutationMatrix[Zeile ]->[Spalte ]
Und ohne dass es uns schmerzlich zu Bewusstsein gekommen ist, haben
wir hinterrücks wieder Zeiger eingeführt! Die einzelnen Listenelemente von
@mutationMatrix sind nämlich Zeiger auf anonyme Listen, die ja mit [...]
erstellt werden können. Beim Zugriff auf ein Element der Matrix geben wir
6
Solche Matritzen werden gelegentlich auch Substitutions- oder Transitionsmatritzen genannt – letzteres in diesem Zusammenhang eine etwas unglückliche Bezeichnung,
da sich die Transition“ hier auf Übergänge im Allgemeinen bezieht, nicht speziell auf die
”
Purin-zu-Purin- bzw. Pyrimidin-zu-Pyrimidin-Umwandlungen.
10.1. ZUFALL UND MUTATION
183
nun zunächst Zeile als Index eines Elementes der äußeren Liste an und
erhalten damit einen Zeiger auf eine der inneren (anonymen) Listen, die den
einzelnen Zeilen der Matrix entsprechen. Per -> dereferenzieren wir nun ein
Element dieser inneren Liste und verwenden dabei Spalte als Index. Diese
Form der Repräsentation einer Matrix mag zwar zunächst etwas kompliziert
erscheinen, ist aber im Grunde genommen von bestechender Eleganz.
Eine weitere Subroutine führt dann auf Grundlage dieser Matrix die
eigentliche Mutation durch. Wir wollen dabei sicher gehen, dass bei Aufruf
von mutate auch wirklich eine Veränderung an der Sequenz durchgeführt
wird und nicht etwa der normalerweise wahrscheinlichste Fall, dass ein A
auch ein A bleibt, ein C ein C usw., eintritt. Dazu weisen wir diesem Fall
beim Berechnen der kummulativen Verteilungsfunktion statt c den Wert 0
zu.
Listing 10.5: Sequenz mutieren
# Expects as first argument a sequence to
# be mutated and considers the remaining
# arguments as mutation probability matrix .
# Creates a point - mutated version of the sequence
# according to probabilities given by the matrix .
sub mutate {
my $seq = shift ( @_ ) ;
my @mutMatrix = @_ ;
# pick a residue to mutate
my $pos = int ( rand ( length ( $seq ) ) ) ;
my $residue = uc ( substr ( $seq , $pos , 1) ) ;
# determine type of residue ...
my $type = 0;
# ... by comparing with first column of matrix
for ( my $i = 1; $i < scalar ( @mutMatrix ) ; $i ++) {
# if corresponding matrix element is found ...
if ( $mutMatrix [ $i ] - >[0] eq $residue ) {
$type = $i ; # remember its index
last ;
}
}
# create probability list from elements in
# row determined previously
my @probs = () ;
for ( my $j = 1; $j < scalar ( @mutMatrix ) ; $j ++) {
# exclude A to A , C to C etc
# ’ cause we promised mutation
if ( $j == $type ) {
push ( @probs , 0) ;
}
184
KAPITEL 10. MEHR BIOLOGIE
else {
push ( @probs , $mutMatrix [ $type ] - >[ $j ]) ;
}
}
# calculate distribution function
my @cumDistr = & calcCumDistr ( @probs ) ;
# pick an interval index
my $mutType = & g e t R a n d o m I n t e r v a l I n d e x ( @cumDistr ) ;
# get new symbol from first row of matrix
my $mutResidue = $mutMatrix [0] - >[ $mutType +1];
# mutate
substr ( $seq , $pos , 1) = $mutResidue ;
return $seq ;
}
Übungen
10.2 Die beiden Subroutinen zur Erzeugung von MutationsWahrscheinlichkeits-Matritzen und der Durchführung einer Mutation werden ebenfalls durch das BioInf::Random-Modul in
C /perl4bio/lib/BioInf bereitgestellt. Schreiben Sie unter Verwendung dieses Moduls und einer translate-Routine (z. B. aus
dem BioInf- oder dem Bio::Perl-Modul) ein Programm, das
abschätzt, welcher Prozentsatz an Punktmutationen auf DNAEbene stumm“ ist, d. h. keine Auswirkung auf die codierte
”
Protein-Sequenz hat. Führen Sie dazu eine größere Anzahl Muationen durch und vergleichen Sie die entsprechenden ProteinSequenzen vor und nach der jeweiligen Mutation.
Gauß, Carl Friedrich
Normalverteilung
Schließlich sei hier noch kurz auf eine andere, in allen Wissenschaften, die auch entfernt etwas mit Mathematik zu tun haben, sehr wichtige Wahrscheinlichkeitsfunktion eingegangen. Es handelt sich um die von
Carl Friedrich Gauß 7 während seiner Zeit als Professor für Astronomie
in Göttingen im Zusammenhang mit seiner Theorie der Messfehler gefundene Normalverteilung :
7
1777 – 1855
10.1. ZUFALL UND MUTATION
185
Definition :
Die Gauß’sche Normalverteilung ist definiert durch
1 x−µ 2
1
f (x) = √ e− 2 ( σ )
σ 2π
wobei µ die Lage des Maximums und σ 2 die Steilheit (kleines σ 2 entspricht
steiler Kurve) der sich ergebenden Glockenkurve bestimmen.
Perls rand-Funktion erzeugt ja gleichverteilte Zufallszahlen. Kann man
mit rand auch normalverteilte Zufallszahlen erzeugen? Die Antwort lautet:
Ja – mit einem kleinen Trick. Und der Trick (dessen mathematische Finesse
wir hier schlicht ignorieren wollen) lautet:
Listing 10.6: Normalverteilte Zufallszahlen
sub gauss {
my $y ;
my $r = 1;
until ( $r < 1) {
$y = 1 - 2* rand ;
$r = sqrt ( $y **2 + (1 - 2* rand ) **2) ;
};
return $y * sqrt ( -2 * log ( $r ) / $r )
}
Diese Funktion liefert gauß-verteilte Zufallszahlen mit µ = 0 und σ = 1.
186
KAPITEL 10. MEHR BIOLOGIE
10.2
Sequenzvergleich
Das Aufspüren von Ähnlichkeiten zwischen Nucleinsäure- oder Proteinsequenzen ist ein heiß umforschtes Gebiet der Bioinformatik. Allgemein wird
davon ausgegangen, dass die Sequenz von Biopolymeren deren Eigenschaften
determiniert, und da es ziemlich unwahrscheinlich ist, dass zwei ähnliche Sequenzen rein zufällig entstanden sind, hofft man, von der Sequenzähnlichkeit
auf andere Gemeinsamkeiten schließen zu können. Wie unwahrscheinlich
zufällige Ähnlichkeiten wirklich sind und ab welchem Grad von Ähnlichkeit
man auf gemeinsame Vorfahren und/oder ähnliche Funktion schließen darf,
ist weiterhin Gegenstand intensiver Forschungsarbeit.
Dot-Plot
Eine recht einfache Methode, sich einen ersten Überblick darüber zu
verschaffen, ob zwei Sequenzen ähnlich sind, besteht darin, beide Sequenzen
gegeneinander aufzutragen und überall dort, wo ein Rest der einen Sequenz
einen gleichen Partner in der anderen Sequenz hat, eine Markierung zu setzen. Das Ganze nennt sich dot plot und sieht dann etwa so aus:
MESFDADTNSTDLHSRPLFQ
M#
A
#
S #
#
#
F
#
#
S #
#
#
A
#
E #
T
# #
N
#
S #
#
#
T
# #
D
# #
#
L
#
#
L
#
#
S #
#
#
Q
#
P
#
W
N
#
E #
An der in diesem Dot-Plot deutlich hervortretenden diagonale Linie sieht
man, dass die beiden hier verglichenen Proteinsequenzen recht viele gemeinsame Aminosäure-Reste in Folge haben. Diagonale Linien mit der entgegengesetzten Neigung würden auf Inversionen hindeuten (bei Nucleinsäuren
deutlicher als bei Proteinen), und wenn eine Diagonale unterbrochen wurde und horizontal oder vertikal versetzt weitergeht, haben wir es mit einer
Deletion bzw. Insertion zu tun.
Hier der Perl-Code, der diese Grafiken erzeugt hat:
10.2. SEQUENZVERGLEICH
187
Listing 10.7: Dot-Plot
1
use Bio :: Perl ;
2
3
4
5
my @seqs = read_all_sequences ( " $ENV { HOME }/ perl / data /➘
C3aR . prot . fasta " ) ;
my $seq1 = $seqs [0] - > subseq (1 , 20) ;
my $seq2 = $seqs [2] - > subseq (1 , 20) ;
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
print ( " $seq2 \ n " ) ;
for ( my $i = 0; $i < length ( $seq1 ) ; $i ++) {
my $res1 = substr ( $seq1 , $i , 1) ;
print ( " $res1 " ) ;
for ( my $j = 0; $j < length ( $seq2 ) ; $j ++) {
my $res2 = substr ( $seq2 , $j , 1) ;
if ( $res1 eq $res2 ) {
print ( " # " ) ;
}
else {
print ( " " ) ;
}
}
print ( " \ n " ) ;
}
Hier benutzen wir Bio::Perl um in Zeile 3 eine Reihe von ProteinSequenzen aus einer FASTA-Datei auszulesen. In den beiden folgenden Zeilen werden zwei dieser Einträge per subseq auf die ersten 20 Reste zurechtgestutzt (eine Frage der Bildschirmauflösung...). Sodann geben wir die zweite
Sequenz (mit einem vorangestellten Leerzeichen) aus – sozusagen als Kopfzeile. In der for-Schleife ab Zeile 8 gehen wir die einzelnen Reste von Sequenz
1 durch und geben Sie auch jeweils am Zeilenanfang aus. In der inneren forSchleife werden dann die einzelnen Reste der zweiten Sequenz extrahiert und
jeweils mit dem aktuellen Rest von Sequenz 1 verglichen. Sind beide Reste
gleich, wird ein # ausgegeben, sonst ein Leerzeichen. Nach Beendigung der
inneren Schleife wird (Zeile 20) schließlich noch ein Zeilenumbruch eingeleitet, damit die Ausgabe mit dem nächsten Rest von Sequenz 1 wieder am
Zeilenanfang beginnen kann.
Die Prüfung auf Zeichengleichheit ist natürlich nur ein sehr grobes Maß
für die Ähnlichkeit zwischen Resten. Welches Gewicht ein Punkt in einem
Dot-Plot hat, könnte bei Aminosäure-Sequenzen z. B. davon abhängen, wie
ähnlich die beiden Aminosäuren in Bezug auf ihre physiko-chemischen Eigenschaften sind (hydrophob, polar, geladen...). Für solche Zwecke stellt man
Ähnlichkeitsmatritzen auf, die z. B. angeben, wie oft man in bekannterweise homologen Proteinen Umwandlungen zwischen Aminosäuretyp i und
Aminosäuretyp j beobachtet hat. Solche Matritzen, die Namen wie PAM
(percent accepted mutations) oder BLOSUM (blocks substitution matrix )
PAM
BLOSUM
188
KAPITEL 10. MEHR BIOLOGIE
tragen, kommen auch bei Homologiesuchprogrammen wie BLAST und FastA zum Einsatz.
Aber auch bei Nucleinsäuren lassen sich noch interessante Variationen
des Dot-Plot-Verfahrens durchführen:
Übungen
10.3 tRNA-Moleküle weisen eine kleeblattförmige Sekundärstruktur
auf, die dadurch zustande kommt, dass kurze Bereiche der Sequenz zueinander revers-komplementär sind und daher Haarnadelschleifen ausbilden können.
Schreiben Sie das Dot-Plot-Programm in Listing 10.7 auf S. 187
so um, dass es prinzipiell geeignet ist, autokomplementäre Bereiche in einer tRNA- (oder einer beliebigen anderen Nucleinsäure-)
Sequenz hervorzuheben.
10.2. SEQUENZVERGLEICH
189
Aufgaben
10.1 Sequenz-Shuffling, wie in Übung 10.1 auf S. 181 durchgeführt,
erzeugt eine Zufallssequenz mit exakt der gleichen Basenzusammensetzung wie die Ausgangssequenz, bei der kurzräumige Regelmäßigkeiten zu einem gewissen Grad erhalten bleiben. Schreiben Sie nun ein Programm (oder eine Subroutine), das eine komplett zufällige Sequenz mit genau vorgegebener Basenzusammensetzung erzeugt.
Hinweis: Benutzen Sie für jeden Basentyp einen Zähler, der angibt, wie viele Basen der jeweiligen Sorte noch in die Sequenz
einzubauen sind. Wählen Sie in einer Schleife entsprechend den
durch diesen Basenvorrat“ gegebenen Wahrscheinlichkeiten ei”
ne Base aus und hängen Sie diese an die entstehende Sequenz
an. Denken Sie daran, die Zählerstände und die sich daraus ergebenden Wahrscheinlichkeiten in jedem Schleifendurchlauf zu
aktualisieren!
10.2 Wie Sie in Übung 10.3 sicher bemerkt haben, sind Dot-Plots
von tRNAs (oder allgemein von Nucleinsäuren) aufgrund der
höheren Anzahl von Punkten viel unübersichtlicher als solche
von Proteinen (warum?). Schreiben Sie ein Programm, das in einem Dot-Plot solche Diagonalen hervorhebt, die eine wählbare
Mindestlänge aufweisen!
Hinweis: Der Kern Ihres Programm sollte aus drei aufeinanderfolgenden doppelt verschachtelten Schleifen bestehen. In der ersten
Doppelschleife initialisieren Sie die Elemente einer zweidimensionalen Liste, die den möglichen Positionen der einzelnen Punkte
entspricht, mit entweder einem . oder dem Leerzeichen – je nachdem, ob die beiden zu vergleichenden Reste von gleicher Art sind
oder nicht. In der zweiten Doppelschleife gehen Sie erneut alle
Punkte durch und überpfrüfen – mittels einer weiteren (dritten)
Schleifenebene –, ob der jeweilige Punkt Bestandteil einer Diagonalen der gegebenen Mindestlänge ist. Falls ja, markieren Sie ihn
mit einem #. In der dritten Doppelschleife schließlich geben Sie
den in der zweidimensionalen Liste gespeicherten Dot-Plot aus.
190
KAPITEL 10. MEHR BIOLOGIE
Anhang A
Perl
A.1
Spezial-Variablen
Folgende von Perls Spezial-Variablen werden in diesem Kurs behandelt:
Tabelle A.1: Ausgewählte Spezial-Variablen
Name
Bedeutung
$_
Standard-Argument für zahlreiche Perl-Funktionen,
dem von vielen Anweisungen (<>, foreach etc.)
automatisch ein Wert zugewiesen wird. Die
Verwendung von $_ macht Programme
unübersichtlich!
Enthält den Pfad zum gerade laufenden
Perl-Programm
Index des ersten Elementes einer Liste
(normalerweise 0). Sollte nicht geändert werden!
In diese Variablen werden die geklammerten Anteile
von Treffern regulärer Ausdrücke aufgefangen
Informationen über den Ausgang des externen
Programm-Aufrufs; Fehlercode (exit code) kann mit
$? >> 8 ermittelt werden
$0
$[
$1..$9
$?
Mehr Informationen über Perls Spezial-Variablen finden Sie auf der perlvarHilfeseite.
191
192
ANHANG A. PERL
Tabelle A.2: Ausgewählte Spezial-Variablen (Fortsetzung)
Name
Bedeutung
@_
Enthält bei Subroutinen-Aufruf die Liste der
übergebenen Argumente
Liste aller Kommandozeilenargumente, mit denen
das Perl-Programm aufgerufen wurde
Liste von Pfaden, in denen Perl nach Modulen
(.pm-Dateien) sucht
Enthält alle aktuellen Shell-Umgebungsvariablen in
Form von Variablenname-Variablenwert-Paaren
Enthält zur jeder geladenen Modul-Datei
(Hash-Schlüssel; Dateiname einschließlich der
Endung .pm) den jeweiligen Pfad (Hash-Werte)
@ARGV
@INC
%ENV
%INC
Mehr Informationen über Perls Spezial-Variablen finden Sie auf der perlvarHilfeseite.
A.2
Reguläre Ausdrücke
Hier eine Zusammenfassung der wichtigsten, in regulären Ausdrücken verwendeten Metazeichen:
Tabelle A.3: Vordefinierte Zeichen und Zeichenklassen
Syntax
Bedeutung
.
Jedes Zeichen außer \n
Wagenrücklauf (carriage return)
Zeilenumbruch (newline)
Seitenvorschub (form feed )
Tabulator
whitespace – jedes der obigen Zeichen sowie das
Leerzeichen; gleichbedeutend mit [ \r\n\f\t]
keines der Zeichen aus \s; gleichbedeutend mit [^\s]
alle Ziffern (digit; gleichbedeutend mit [0-9]
keines der Zeichen aus \d; gleichbedeutend mit [^\d]
alle alphanumerischen Zeichen (word character ;
gleichbedeutend mit [0-9a-zA-Z]
keines der Zeichen aus \w; gleichbedeutend mit [^\w]
\r
\n
\f
\t
\s
\S
\d
\D
\w
\W
A.2. REGULÄRE AUSDRÜCKE
Tabelle A.4: Angabe von Alternativen
Syntax
Bedeutung
(fasel|blah|blubb)
fasel oder blah oder blubb muss
gefunden werden
finde A oder C oder G oder T
alles außer x, y und z ist erlaubt
alles von A bis H ist erlaubt (nur mit
Buchstaben oder Ziffern verwenden!)
Klammern geben gefundene Muster in
den Spezialvariabeln $1 ... $9 zurück
[ACGT]
[^xyz]
[A-H]
(Muster)
Tabelle A.5: Lokatoren
Syntax
Bedeutung
^Muster
Muster muss am Anfang der Zeichenkette stehen
Muster$
Muster muss am Ende der Zeichenkette stehen
Tabelle A.6: Quantoren
Syntax
Bedeutung
?
Finde vorhergehendes Teilmuster 0- oder 1-mal
Finde vorhergehendes Teilmuster 0-mal oder beliebig
oft
Finde vorhergehendes Teilmuster mindestens 1-mal
Finde vorhergehendes Teilmuster mindestens n- und
höchstens m-mal
Finde vorhergehendes Teilmuster mindestens n-mal
Finde vorhergehendes Teilmuster genau n-mal
Finde kürzestesten Substring, der vorhergehendem
Teilmuster entspricht (Standardverhalten: finde
längsten Substring!)
*
+
{n,m}
{n,}
{n}
Quantor?
193
194
ANHANG A. PERL
Tabelle A.7: Modifikatoren
Syntax
Bedeutung
e
Interpretiere das Substitut nicht als Zeichenkette,
sondern als auszuwertenden Perl-Ausdruck (von
execute; nur für s///)
Globale Suche – bei m/.../: nacheinander alle
matches finden, bei s/.../.../: in einem Durchlauf
alle matches ersetzen
Suche groß/kleinschreibungsunempfindlich
durchführen (von case-insensitive)
Behandle Zeichenkette mit Zeilenumbrüchen wie
mehrere einzelne Zeilen (multiple lines) – die
Lokaltoren ^ und $ beziehen sich dann auf Anfang
und Ende einer jeden einzelnen Zeile (nicht des
Gesamt-Strings)
Behandle Zeichenkette wie eine einzige Zeile (single
line), auch wenn sie Zeilenumbrüche beinhaltet – .
erkennt dann auch \n
extended syntax – erlaubt Zeilenumbrüche und
Kommentare
g
i
m
s
x
195
A.3. PERLDOC
A.3
Perldoc
Folgende Tabelle führt die Namen einiger wichtiger perldoc-Seiten sowie
eine kurze Beschreibung ihres Inhaltes auf:
Tabelle A.8: Perldoc-Seiten
Name
Beschreibung
perltoc
Inhaltsverzeichnis (table of contents) aller
perldoc-Seiten
Allgemeiner Aufbau (Syntax) der Sprache einschl.
Flusskontroll-Anweisungen
Beschreibung aller Perl-Funktionen/Befehle (einschl.
File-Test -X)
Operatoren und ihre Prioritäten
Alle Spezialvariablen
Kompakte Übersicht über reguläre Ausdrücke
Ausführliche Übersicht über reguläre Ausdrücke
Schnellkurs in regulären Ausdrücken
Ausführliches Tutorial zu regulären Ausdrücken
Alles über Subroutinen
Einführung in Zeiger (Referenzen)
Wie man mit Zeigern beliebig komplexe
Datenstrukturen aufbauen kann
Einführung in objektorientiertes Perl
Eine etwas ausführlichere Einführung in Perls
objektorientierte Seite
perlsyn
perlfunc
perlop
pervar
perlreref
perlre
perlrequick
perlretut
perlsub
perlref
perldsc
perlboot
perltoot
Aufruf von perldoc mit der Option -f, gefolgt vom Namen einer PerlFunktion, öffnet perlfunc direkt an der zugehörigen Stelle. Falls perldoc
auf Ihrem Rechner nicht installiert ist, können sie alle Perl-Hilfe-Seiten auch
unter http://perldoc.perl.org on-line abrufen.
196
ANHANG A. PERL
Anhang B
Molekularbiologische
Abkürzungen
B.1
IUPAC-Codes
Die IUPAC, die International Union of Pure and Applied Chemistry, hat einbuchstabige Abkürzungen für Nucleotide und Aminosäuren definiert, welche
in der Bioinformatik gerne zur Repräsentation von Nukleotid- und Aminosäuresequenzen verwendet werden:
197
198
ANHANG B. MOLEKULARBIOLOGISCHE ABKÜRZUNGEN
Tabelle B.1: IUPAC-Codes für Nucleotide
Code
Bedeutung
A
Adenin
C, G, T – alles außer A
Cytosin
A, G, T – alles außer C
Guanin
A, C, T – alles außer G
G, T – Nucleotide mit Keto-Basen
A, C – Nucleotide mit Amino-Basen
A, C, G, T – jedes beliebige Nucleotid
A, G – Nucleotide mit Purin-Basen
C, G – stark bindende Nucleotide
Thymin
A, C, G – alles außer T
A, T – schwach (weak ) bindende Nucleotide
C, T – Nucleotide mit Pyrimidin-Basen
B
C
D
G
H
K
M
N
R
S
T
V
W
Y
Weitere IUPAC-Codes für Nucleotide sind U für Uracil und I für Inosin,
die beide in RNA gefunden werden.
199
B.1. IUPAC-CODES
Tabelle B.2: IUPAC-Codes für Aminosäuren
1-BuchstabenCode
Aminosäure
A
Alanin
Cystein
Asparaginsäure
Glutaminsäure
Phenylalanin
Glycin
Histidin
Isoleucin
Lysin
Leucin
Methionin
Asparagin
Prolin
Glutamin
Arginin
Serin
Threonin
Valin
Tryptophan
(beliebige Aminosäure)
Tyrosin
C
D
E
F
G
H
I
K
L
M
N
P
Q
R
S
T
V
W
X
Y
3-BuchstabenCode
Ala
Cys
Asp
Glu
Phe
Gly
His
Ile
Lys
Leu
Met
Asn
Pro
Gln
Arg
Ser
Thr
Val
Trp
Xxx
Tyr
MW
[g/mol]
89
121
132
146
165
75
156
131
147
131
149
132
116
146
175
105
119
117
203
---
181
Gelegentlich findet auch X für Positionen mit unbekannten Aminosäureresten
Verwendung.
200
B.2
ANHANG B. MOLEKULARBIOLOGISCHE ABKÜRZUNGEN
Der genetische Standard-Code
Tabelle B.3: Genetischer Standard-Code
Pos. 1
A
C
G
T
Pos. 2
Pos. 2
Pos. 2
Pos. 2
A
C
G
T
AAA - Lys
ACA - Thr
AGA - Arg
ATA - Ile
AAC - Asn
ACC - Thr
AGC - Ser
ATC - Ile
AAG - Lys
ACG - Thr
AGG - Arg
ATG - Met
AAT - Asn
ACT - Thr
AGT - Ser
ATT - Ile
CAA - Gln
CCA - Pro
CGA - Arg
CTA - Leu
CAC - His
CCC - Pro
CGC - Arg
CTC - Leu
CAG - Gln
CCG - Pro
CGG - Arg
CTG - Leu
CAT - His
CCT - Pro
CGT - Arg
CTT - Leu
GAA - Glu
GCA - Ala
GGA - Gly
GTA - Val
GAC - Asp
GCC - Ala
GGC - Gly
GTC - Val
GAG - Glu
GCG - Ala
GGG - Gly
GTG - Val
GAT - Asp
GCT - Ala
GGT - Gly
GTT - Val
TAA - ---
TCA - Ser
TGA - ---
TTA - Leu
TAC - Tyr
TCC - Ser
TGC - Cys
TTC - Phe
TAG - ---
TCG - Ser
TGG - Trp
TTG - Leu
TAT - Tyr
TCT - Ser
TGT - Cys
TTT - Phe
Anhang C
Nützliche Websites
Tabelle C.1: Websites, die im Kurs erwähnt werden
URL
Beschreibung
http://www.perl.org
http://perldoc.perl.org
http://www.activestate.com
http://www.cpan.org
Die Website zu Perl
Die Perl-Hilfeseiten online
Hier gibt’s u. a. Perl für Windows
Das Comprehensive Perl Archive
Network – die Site für Perl-Module
aller Art
Eine komplett nicht-kommerzielle
GNU/Linux-Distribution mit
ausgeklügelter Software-Verwaltung
Persistance of Vision – ein
Open-Source-Programm zum Erstellen
von 3D-Computergraphiken nach dem
Raytracing-Verfahren
Heimat des BioPerl-Projektes –
Perl-Module für Bioinformatik
http://www.debian.org
http://www.povray.org
http://www.bioperl.org
201
202
ANHANG C. NÜTZLICHE WEBSITES
Tabelle C.2: Websites, die im Kurs erwähnt werden (Fortsetung)
URL
Beschreibung
http://www.ebi.ac.uk
Das European Bioinformatics Institute
– Heimal der
EMBL-Nucleotid-Datenbank u.v.m.
Der Expert Protein Analysis Server
des Schweizer Insituts für
Bioinformatik
Das US-amerikanische National
Center for Biotechnology Information,
die Heimat von u. a. GenBank
Das Research Center for Structural
Biology stelt u. a. die Protein
Databank mit 3D-Strukturdaten zur
Verfügung
http://www.expasy.ch
http://www.ncbi.nlm
.nih.gov
http://www.rcsb.org
Anhang D
Buchempfehlungen
Tabelle D.1: Bücher zu Perl
Werk
Beschreibung
Hoffmann, Paul E.
(2003) Perl 5 for
Dummies
Sehr nett, wenn man den lockeren Stil
mag; stellt mehr Anweisungen und
Funktionen als dieser Kurs dar, geht aber
nicht näher auf Zeiger und Module ein
Stellt im Wesentlichen die gleichen Dinge
wie dieser Kurs vor, ist thematisch aber
etwas anders gegliedert
Tisdall, James
(2001) Beginning
Perl for
Bioinformatics
Wall, Larry et al.
(2000) Programming
Perl
Christiansen, Tom,
und Torkington,
Nathan (2003) Perl
Cookbook
Das Perl-Referenzhandbuch vom
Perl-Erfinder persönlich; neigt allerdings
gelegentlich zu einer leicht faulen“
”
Syntax
Sehr nützliche Sammlung von Tipps &
Tricks für alle möglichen alltäglichen
Perl-Probleme
Die Beschreibung der jeweiligen Werke entspricht der persönlichen Meinung der Veranstalterinnen und Veranstalter dieses Kurses!
Die meisten der aufgeführten Werke sind auch in deutscher Übersetzung
erhältlich, doch wirkt der oft etwas flapsige Stil im englischen Original deutlich natürlicher.
203
204
ANHANG D. BUCHEMPFEHLUNGEN
Index
!, 38
!=, 35
(), 21, 52
*, 21, 84
**, 21
*=, 59
+, 21, 84
++, 58
+=, 59
-, 21, 81
\, 26, 124
--, 58
-=, 59
->, 127
-d, 73
-e, 73
., 22, 82
.., 53
.=, 59
/, 21
<, 35
<=, 35
<=>, 56
<>, 29, 65
=, 24
==, 34
=>, 117
= , 80
>, 35, 70
>=, 35
>>, 70, 75
?, 40, 83
@ARGV, 56
@ , 99
@{...}, 125
[...], 81
[], 52, 53
#, 21
$, 24, 83
$0, 74
$1...$9, 86
$?, 75
$#, 57
$ , 59
${...}, 130
%, 21, 117
%ENV, 121
%INC, 160
%{...}, 127
&&, 37
^, 81, 83
{...}, 26
{n,m}, 84
Adress-Operator, 124
Algorithmus, 11
Alternativen, 193
an Datei anhängen, 70
and, 37
anhängen, an Datei, 70
anonyme Liste, 128
anonymer Hash, 128
Array, 52
Assembler, 14
Assemblerspachen, 14
Attribute, von Objekten, 165
Aufhebungszeichen, 26
Ausdrücke, 20
Ausdrücke, reguläre, 79, 192
Ausdruck, boolscher, 34
ausführbare Datei, 13
Ausgabe, 18, 100
205
206
Ausgangsbedingung, 49
ausschließendes Oder, 37
bedingte Verzweigung, 34
Bereichs-Operator, 53
Berners-Lee, Tim, 166
Bezeichner, 24
Bio::Perl, 164
BioPerl, 164
Blast, 134
Block, 35
BLOSUM, 187
Boole, George, 34
boolscher Ausdruck, 34
Browser, 167
call by reference, 129
call by value, 129
CGI, 168
chomp, 30
Client, 167
Client-Server-Architektur, 167
close, 64
cmp, 56
Code, genetischer, 200
comma separated value, 91
Compiler, 15
cos, 27
CPAN, 162
Crick, Francis, 174
Darwin, Charles, 173
data field, 91
Datei, überschreiben, 70
Datei, anhängen an, 70
Datei, ausführbare, 13
Datei, neu anlegen, 70
Dateihandle, 30, 64
Daten, numerische, 20
Daten, Zeichenketten, 20
Datenfeld, 91
Datensatz, 91
Datentypen, 24
DDBJ, 135
Debian, 164
INDEX
debugging, 44
defined, 107
Deklaration, 107
Dekrementoperator, 58
delete, 117
Dereferenzierung, 126
Dictionary, 116
die, 67
Dienste, Internet-, 166
Direktzugriff, 70
diskrete Verteilung, 176
DNS, 167
Dot-Plot, 186
e, 90
E-Mail, 166
EBI, 134
Eingangsbedingung, 49
Einrückung, 35, 43
einschließendes Oder, 37
Element-Dereferenzierungs-Operator,
127
else, 35
elsif, 36
EMBL, 134
Endlosschleife, 48
ensembl, 134
Entrez, 134
Entweder-Oder, 37
eof, 64
eq, 35
escape character, 26
EVA-Prinzip, 40
exec, 75
executable binary file, 14
executable file, 13
exists, 117
exit, 68
Exitcode, 68
exp, 27
Exporter, 158
FastA, 134
Feld, 52
207
INDEX
Filehandle, 30, 64
flat files, 135
Fließkommazahlen, 175
for, 57
foreach, 59
FTP, 167
Funktion, 27, 98, 129
Funktionale Sprachen, 16
g, 87
Gauß, Carl Friedrich, 184
ge, 35
GenBank, 134
genetischer Code, 200
GET, 170
Gleichverteilung, 175
glob, 73
globale Variablen, 103
grep, 90
Gruppierung, 81
gt, 35
Höhere Programmiersprachen, 14
Hash, 116
Hash, anonymer, 128
Hauptprogramm, 98
home page, 168
HTML, 168
http:, 167
Hyperlink, 168
i, 80
Identifier, 24
if, 34
IMAP, 167
Imperative Sprachen, 16
Index, 52
Indizierung, 51
Initialisierung, 25, 107
Inkrementoperator, 58
int, 27
Interpreter-Pfad, 19
Internet, 166
Internet-Dienste, 166
Interpolation, 26
Interpreter, 15
IP-Adresse, 167
Iteration, 47
IUPAC-Code, 197
join, 92
Joker, 73
Kapselungsprinzip, 106
Kellerspeicher, 113
keys, 119
Klammerung, 21
Klassen, von Ereignisssen, 176
Klassen, Zeichen-, 81
Kommentar, 21
Konkatenation, 22
Konstante, 28
Kontext, Listen-, 65
Kontext, skalarer, 65
Kontext, void-, 68
kontinuierliche Verteilung, 175
Kontrollanweisungen, 34, 57
Kontrollfluss, 34
kummulative Verteilungsfunktion, 177
last, 60
Laufzeit, 15
Laufzeitfehler, 45
lc, 28
lcfirst, 148
le, 35
length, 28
lexikalische Ordnung, 35
lexikalische Sortierung, 55
Liste, 52
Liste, anonyme, 128
Liste, mehrdimensionale, 182
Listen-Konstruktor, 52
Listen-Kontext, 65
Listenvariablen, 52
Literale, 20
localtime, 71
log, 27
logische Operatoren, 37
Logische Sprachen, 16
208
lokale Variablen, 103
Lokatoren, 83, 193
lt, 35
LWP::Simple, 169
m, 143
m/.../, 80
Map, 116
maschinelles Lernen, 180
Maschinensprache, 14
Maskierung, 104
mehrdimensionale Liste, 182
Menge, 119
Metazeichen, 81
Methoden, 165
Mnemonics, 14
Modifikator, e, 90
Modifikator, g, 87
Modifikator, i, 80
Modifikator, m, 143
Modifikator, s, 144
Modifikator, x, 147
Modifikatoren, 80, 194
Module, 153
Multimenge, 119
Muster, 80
Mutationen, 173
Mutationsmatrix, 181
my, 104
NCBI, 134
ne, 35
neuronale Netze, 180
next, 60
Normalverteilung, 184
not, 38
numerische Daten, 20
numerische Sortierung, 55
INDEX
OMIM, 134
open, 64
Open Source, 16
operation codes, 14
Operatoren, 20
or, 37
Ordinal-Operator, lexikalisch, 56
Ordinal-Operator, numerisch, 56
Ordnung, lexikalische, 35
our, 157
package, 154
PAM, 187
Parsing, 16
pattern, 80
PDB, 134
perldoc, 22, 195
Pointer, 124
pop, 54
POP3, 167
pos, 87
POST, 170
Pragma, 106
print, 18
printf, 92
Priorität, 21
Programmverifikation, 47
Programmiersprache, 14
Programmtestung, 46
Prozedur, 98
Prozedurale Sprachen, 16
Prozeduren, 129
Prozess, 14
Pseudozufallszahlen, 175
PubMed, 134
push, 54
qq, 158
Quantoren, 83, 193
Objekt, 16, 164
objektorientierte Programmierung, 16, Quellcode, 14
Quellcode-Formatierung, 41
129, 164
query string, 168
Objektorientierte Sprachen, 16
qw, 158
Oder, ausschließendes, 37
Oder, einschließendes, 37
qx, 75
209
INDEX
Rückgabe, 100
Rückgabewert, 27
rand, 174
RCSB, 134
read, 71
Rebase, 170
record, 91
Referenz, 124
reguläre Ausdrücke, 79, 80, 192
Rekursion, 109
Request, 167
Response, 167
return, 100
reverse, 55, 66
runtime error, 45
s, 144
s/.../.../, 89
scalar, 52, 65
Schlüssel-Wert-Listen, 115
Schleife, Endlos-, 48
Schleifen, 47, 57
Schleifenrumpf, 48
schwache Typisierung, 45
SCP, 167
seek, 71
Seiteneffekte, 105, 129
Selektion, 173
semantische Fehler, 46
Sequence shuffling, 180
Server, 167
Shebang-Zeile, 19
shift, 54
Sichtbarkeitsbereiche, 103
sin, 27
Skalar, 24
skalare Variable, 24
Skalarer Kontext, 65
slice, 53
Slice-Operator, 53
SMTP, 166
sort, 55
Sortierung, 55
source code, 14
Spezialvariablen, 56, 191
splice, 54
split, 91
sprintf, 93
sqrt, 27
srand, 175
SSH, 167
Stack, 113
Stack overflow, 113
Standard-Ausgabe, 30
Standard-Eingabe, 30
Standard-Fehlerkonsole, 30
Stapelüberlauf, 113
Stapelspeicher, 113
starke Typisierung, 45
stat, 74
STDERR, 30
STDIN, 30
STDOUT, 30
strict, 106
Strings, 20
sub, 98
Subroutine, 98
Substitutionsmatrix, 181
substr, 28
SwissProt, 134
syntaktische Analyse, 16
Syntax, 45
Syntaxfehler, 45
system, 75
Systemzeit, 71
tab-separated value, 92
Tags, 169
TCP/IP, 166
tell, 71
telnet, 167
time, 71
tr/.../.../, 89
Transitionen, 181
Transitionsmatrix, 181
Transversionen, 181
Typisierung, 45
210
uc, 28
ucfirst, 148
undef, 107
unless, 38
unlink, 74
unshift, 54
until, 49
URL, 167
use, 106, 154
values, 119
Variablen, 23
Variablen, globale, 103
Variablen, Listen-, 52
Variablen, lokale, 103
Variablen, skalare, 24
Variablen, Spezial-, 56, 191
Vergleichsoperator, 34
Verteilung, diskrete, 176
Verteilung, Gleich-, 175
Verteilung, kontinuierliche, 175
Verteilung, Normal-, 184
Verteilungsfunktion, kummulative, 177
Verzweigung, bedingte, 34
Virtual Machine, 15
void-Kontext, 68
VOIP, 167
Wahrscheinlichkeitsdichtefunktion, 175
Wahrheitstabellen, 37
warnings, 107
Watson, James, 174
web page, 168
web site, 168
Web-Seite, 168
while, 48
Wildcards, 73
World Wide Web, 166
WWW, 166
x, 23, 147
xor, 37
Zählvariable, 48
Zeichenketten, 20
INDEX
Zeichenklassen, 81
Zeichenklassen, 192
Zeiger, 124
Zufallszahl, 174
Zuordnungsoperator, 117
Zuse, Konrad, 40
Zuweisungs-Operator, 24