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: torsten.crass@bioinf.med.uni-goettingen.de 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