15  Ein- und Ausgabe

15.1 Konsole

Das Betriebssystem stellt für ein Programm üblicherweise 3 Kanäle (streams) zur Verfügung:

  • Standardeingabekanal stdin
  • Standardausgabekanal stdout und
  • Standardfehlerausgabekanal stderr.

Wenn das Programm in einem Terminal (oder Konsole bzw. Shell) gestartet wird, kann das Programm über stdin die Tastatureingaben einlesen und Ausgaben über stdout sowie stdout erscheinen im Terminal.

  • Schreiben nach stdout: print(),println(),printstyled()
  • Schreiben nach stderr: print(strerr,...), println(stderr,...), printstyled(stderr,...)
  • Lesen von stdin: readline()

15.1.1 Eingaben

Die Sprache Python stellt eine Funktion input() zur Verfügung:

ans = input("Bitte eine positive Zahl eingeben!")

Die Funktion gibt den Prompt aus, wartet auf eine Eingabe und liefert die Eingabe als string zurück.

In Julia kann man diese Funktion so implementieren:

function input(prompt = "Eingabe:")
    println(prompt)
    flush(stdout)
    return chomp(readline())
end
input (generic function with 2 methods)

Anmerkungen

  • Schreibanweisungen werden von modernen Betriebssystemen gebuffert. Mit flush(stdout) wird die Leerung des Buffers und sofortige Schreiboperation erzwungen.
  • readline() liefert einen String zurück, der mit einer Newline \n endet. Die Funktion chomp() entfernt einen eventuellen Zeilenumbruch vom Ende eines Strings.
a = input("Bitte 2 Zahlen eingeben!")
"34 56"

15.1.2 Verarbeitung der Eingabe

split(str) zerlegt einen String in “Wörter” und liefert einen (array of strings):

av = split(a)
2-element Vector{SubString{String}}:
 "34"
 "56"

parse(T, str) versucht, str in den Typ T umzuwandeln:

v = parse.(Int, av)
2-element Vector{Int64}:
 34
 56

parse() erzeugt einen Fehler, wenn der String sich nicht als Wertangabe von Typ T parsen lässt. Man kann den Fehler mit try/catch abfangen oder die Funktion tryparse(T, str) verwenden, die in so einem Fall nothing zurückgibt - worauf man dann z.B. mit isnothing() testen kann.

15.1.3 Einzelne Tastenanschläge einlesen

15.2 Formatierte Ausgabe mit dem Printf-Makro

Oft möchte man Zahlen oder Strings mit einer strikten Formatvorgabe - Gesamtlänge, Nachkommastellen, rechts/linksbündig usw - ausgeben.

Dazu definiert das Paket Printf die Makros @sprintf und @printf, welche sehr ähnlich wie die gleichnamigen C-Funktionen arbeiten.

using Printf

x = 123.7876355638734

@printf("Ausgabe rechtsbündig  mit max. 10 Zeichen Platz und 3 Nachkommastellen: x= %10.3f", x)
Ausgabe rechtsbündig  mit max. 10 Zeichen Platz und 3 Nachkommastellen: x=    123.788

Das erste Argument ist ein String, der Platzhalter (hier: %10.3) für auszugebende Variablen enthält; gefolgt von diesen Variablen als weitere Argumente.

Platzhalter haben die Form

%[flags][width][.precision]type

wobei die Angaben in eckigen Klammern alle optional sind.

Typangaben im Platzhalter

%s string
%i integer
%o integer octal (base=8)
%x, %X integer hexadecimal (base=16) with digits 0-9abcdef or 0-9ABCDEF, resp.
%f floating point number
%e floating point number, scientific representation
%g floating point, uses %f or %e depending on value

Flags

Pluszeichen rechtsbündig (Standard)
Minuszeichen linksbündig
Null mit führenden Nullen

Width

Anzahl der minimal verwendeten Zeichen (wenn nötig, werden auch mehr genommen)

15.2.1 Beispiele:

using Printf    # Paket laden nicht vergessen!
@printf("|%s|", "Hallo")     # string mit Platzhalter für String
|Hallo|

Die senkrechten Striche sind nicht Teil des Platzhalters. Sie sollen die Begrenzung des Ausgabefeldes anzeigen.

@printf("|%10s|", "Hallo")   # Minimallänge, rechtsbündig
|     Hallo|
@printf("|%-10s|", "Hallo")     # linksbündig
|Hallo     |
@printf("|%3s|", "Hallo")     # Längenangabe kann überschritten werden 
                              # besser eine 'kaputt formatierte' Tabelle als falsche Werte!
|Hallo|
j = 123
k = 90019001
l = 3342678

@printf("j= %012i, k= %-12i, l = %12i", j, k, l)   #  0-Flag für führende Nullen
j= 000000000123, k= 90019001    , l =      3342678

@printf und @sprintf können wie alle Makros wie Funktionen aufgerufen werden:

@printf("%i %i", 22, j)
22 123

– oder wie Makros, also ohne Funktionsklammern und ohne Komma:

@printf "%i %i" 22 j
22 123

@printf kann als erstes Argument noch einen Stream übergeben bekommen.

Ansonsten besteht die Argumentliste aus

  • Formatstring mit Platzhaltern
  • Variablen in der Reihenfolge der Platzhalter, in Anzahl und Typ zu den Platzhaltern passend
@printf(stderr, "Erstes Resultat: %i %s\nZweites Resultat %i", 
                                   j, "(geschätzt)"       ,k)
Erstes Resultat: 123 (geschätzt)
Zweites Resultat 90019001

Das Makro @sprintf druckt nichts, sondern liefert den ausgefüllten formatierten String zurück:

str = @sprintf("x = %10.6f", π );
str
"x =   3.141593"

15.2.2 Formatierung der Gleitkommazahlen:

Bedeutung des Precision-Wertes:

  • %f und %e-Format: maximale Anzahl der Nachkommastellen
  • %g-Format: maximale Anzahl von ausgegebenen Ziffern (Vor- + Nachkommastellen)
x = 123456.7890123456

@printf("%20.4f   %20.4e", x, x)     # 4 Nachkommastellen
         123456.7890             1.2346e+05
@printf("%20.7f %20.7e", x, x)     # 7 Nachkommastellen
      123456.7890123        1.2345679e+05
@printf("%20.7g %20.4g", x, x)    # insgesamt 7 bzw. 4 Stellen
            123456.8            1.235e+05

15.3 Dateioperationen

Dateien werden

  • geöffnet \(\Longrightarrow\) dabei ensteht ein neues stream-Objekt (zusätzlich zu stdin, stdout, stderr)
  • dann kann dieser stream gelesen und geschrieben werden
  • geschlossen \(\Longrightarrow\) stream-Objekt wird von Datei getrennt
stream = open(path, mode)
  • path: Dateiname/pfad
  • mode:
"r"    read, öffnet am Dateianfang
"w"    write, öffnet am Dateianfang (Datei wird neu angelegt oder überschrieben)
"a"    append, öffnet zum Weiterschreiben am Dateiende

Schreiben wir mal eine Datei:

file = open("datei.txt", "w")
IOStream()
@printf(file, "%10i\n", k)
println(file, " zweite Zeile")
close(file)

Schauen wir uns die Datei an:

;cat datei.txt
  90019001
 zweite Zeile

…und jetzt öffnen wir sie wieder zum Einlesen:

stream = open("datei.txt", "r")
IOStream()

readlines(stream) liefert alle Zeilen einer Textdatei als Vector von Strings.

eachline(stream) liefert einen Iterator über die Zeilen der Datei.

n = 0
for line in eachline(stream)    # Lese zeilenweise
    n += 1
    println(n, line)            # Drucke mit Zeilennummer
end
close(stream)
1  90019001
2 zweite Zeile

15.4 Pakete für Dateiformate

Für die Ein- und Ausgabe in den verschiedensten Dateiformaten existieren Julia-Pakete, z.B.

und viele andere mehr…

15.4.1 DelimitedFiles.jl

Dieses Paket ermöglicht das bequeme Abspeichern/Einlesen von Matrizen. Dazu stellt es die Funktionen writedlm() und readdlm() zur Verfügung.

using DelimitedFiles

Wir erzeugen eine 200×3-Matrix von Zufallszahlen

A = rand(200,3)
200×3 Matrix{Float64}:
 0.672342   0.303105   0.930836
 0.385606   0.982857   0.297996
 0.982319   0.13608    0.963767
 0.700655   0.244917   0.745423
 0.892462   0.677228   0.495121
 0.909235   0.627747   0.831661
 0.371466   0.759464   0.88495
 0.62678    0.70573    0.404305
 0.504154   0.0573904  0.374065
 0.668662   0.161108   0.269653
 ⋮                     
 0.371035   0.582342   0.413769
 0.223072   0.983405   0.0632649
 0.781323   0.114408   0.0111916
 0.808109   0.541896   0.487848
 0.122619   0.710159   0.238551
 0.229855   0.236011   0.308402
 0.825045   0.486915   0.818736
 0.327997   0.691566   0.204639
 0.0339736  0.966453   0.163437

und speichern diese

f = open("data2.txt", "w")
writedlm(f, A)
close(f)

Die geschriebene Datei fängt so an:

;head data2.txt
0.6723418093633674  0.3031053013517715  0.9308361275030339
0.38560613566454505 0.9828565448875306  0.29799580632013134
0.9823188234171364  0.136079648886137   0.9637673224574909
0.7006547221425034  0.2449165218995646  0.7454234552064191
0.8924615626866147  0.6772279207528131  0.49512143423556876
0.9092346088022356  0.6277466070820665  0.8316605232323371
0.3714658487798871  0.7594644096269569  0.8849501793885042
0.626779832412877   0.7057301938882777  0.4043049315040659
0.5041543711407259  0.05739039511206501 0.37406500965366407
0.6686624219387581  0.1611078145265057  0.2696531319719828

Das Wiedereinlesen ist noch einfacher:

B = readdlm("data2.txt")
200×3 Matrix{Float64}:
 0.672342   0.303105   0.930836
 0.385606   0.982857   0.297996
 0.982319   0.13608    0.963767
 0.700655   0.244917   0.745423
 0.892462   0.677228   0.495121
 0.909235   0.627747   0.831661
 0.371466   0.759464   0.88495
 0.62678    0.70573    0.404305
 0.504154   0.0573904  0.374065
 0.668662   0.161108   0.269653
 ⋮                     
 0.371035   0.582342   0.413769
 0.223072   0.983405   0.0632649
 0.781323   0.114408   0.0111916
 0.808109   0.541896   0.487848
 0.122619   0.710159   0.238551
 0.229855   0.236011   0.308402
 0.825045   0.486915   0.818736
 0.327997   0.691566   0.204639
 0.0339736  0.966453   0.163437

Noch ein Punkt: Beim Umgang mit Dateien wird in Julia oft die do-Notation verwendet, s. Kapitel 10.6. Dazu nutzt man, dass open() auch Methoden hat, bei denen das 1. Argument eine function(iostream) ist. Diese wird dann auf den stream angewendet und dieser abschliessend automatisch geschlossen. Die do-Notation erlaubt es, diese Funktion anonym nach dem do zu definieren:

open("data2.txt", "w") do io
    writedlm(io, A)
end

15.4.2 CSV und DataFrames

  • Das CSV-Format wird oft benutzt, um Tabellen in einer nicht nur mit MS Excel lesbaren Form zur Verfügung zu stellen.
  • Ein Beispiel ist die Wetter- und Klimadatenbank Meteostat.
  • Das Paket DataFrames.jl stellt Funktionen zum bequemen Umgang mit tabellarischen Daten zur Verfügung.
using CSV, DataFrames, Downloads
# Wetterdaten von Westerland, s. https://dev.meteostat.net/bulk/hourly.html

url = "https://bulk.meteostat.net/v2/hourly/10018.csv.gz"
http_response = Downloads.download(url)
file = CSV.File(http_response, header=false);

Die Daten sehen so aus:

# https://dev.meteostat.net/bulk/hourly.html#endpoints
#
# Spalte 1  Datum
#        2  Uhrzeit (Stunde)
#        3  Temp
#        5  Luftfeuchtigkeit
#        6  Niederschlag
#        8 Windrichtung
#        9 Windstärke

df =  DataFrame(file)
185254×13 DataFrame
185229 rows omitted
Row Column1 Column2 Column3 Column4 Column5 Column6 Column7 Column8 Column9 Column10 Column11 Column12 Column13
Date Int64 Float64? Float64? Int64? Float64? Missing Int64? Float64? Float64? Float64? Missing Int64?
1 1989-03-18 7 6.0 missing missing missing missing 270 11.2 missing missing missing missing
2 1989-03-18 8 6.0 -1.9 57 missing missing 250 11.2 missing missing missing missing
3 1989-03-18 9 7.0 -3.0 49 missing missing 250 14.8 missing missing missing missing
4 1989-03-18 10 7.0 -1.9 53 missing missing 250 18.4 missing missing missing missing
5 1989-03-18 11 8.0 -1.0 53 missing missing 220 24.1 missing missing missing missing
6 1989-03-18 12 8.0 0.0 57 missing missing 240 27.7 missing missing missing missing
7 1989-03-18 13 8.0 -1.0 53 missing missing 240 25.9 missing missing missing missing
8 1989-03-18 14 8.0 0.0 57 missing missing 230 29.5 missing missing missing missing
9 1989-03-18 15 8.0 0.0 57 missing missing 230 31.7 missing missing missing missing
10 1989-03-23 9 7.0 -0.9 57 missing missing 280 37.1 missing missing missing missing
11 1989-03-23 10 7.0 -0.9 57 missing missing 270 64.8 missing missing missing missing
12 1989-03-23 11 7.0 -0.9 57 missing missing 280 33.5 missing missing missing missing
13 1989-03-27 7 6.0 4.0 87 missing missing 160 11.2 missing missing missing missing
185243 2025-03-08 23 4.1 2.8 91 missing missing 228 13.0 24.1 1014.3 missing 2
185244 2025-03-09 0 3.9 2.7 92 missing missing 229 13.0 27.8 1014.2 missing 2
185245 2025-03-09 1 3.7 2.7 93 missing missing 230 13.0 27.8 1014.0 missing 2
185246 2025-03-09 2 3.4 2.7 95 missing missing 235 13.0 27.8 1013.8 missing 2
185247 2025-03-09 3 3.3 2.6 95 missing missing 243 13.0 27.8 1013.7 missing 7
185248 2025-03-09 4 3.1 2.5 96 missing missing 244 13.0 25.9 1013.7 missing 7
185249 2025-03-09 5 2.4 2.0 97 missing missing 262 18.5 27.8 1015.8 missing 7
185250 2025-03-09 6 2.6 2.2 97 missing missing 258 18.5 29.6 1015.8 missing 7
185251 2025-03-09 7 3.2 2.3 94 missing missing 255 20.4 27.8 1016.0 missing 7
185252 2025-03-09 8 4.4 2.7 89 missing missing 255 20.4 29.6 1016.1 missing 7
185253 2025-03-09 9 5.4 3.2 86 missing missing 252 22.2 31.5 1016.2 missing 7
185254 2025-03-09 10 6.3 3.5 82 missing missing 259 24.1 33.3 1016.4 missing missing

Zum bequemen Plotten und zum Umgang mit den Datums- und Zeitformaten in der Wettertabelle laden wir noch 2 Helferlein:

using StatsPlots, Dates

Wir erzeugen eine neue Spalte, die Datum (aus Spalte 1) und Uhrzeit (aus Spalte 2) kombiniert:

# neue Spalte mit Sp.1 und 2  (date & time) kombiniert

df[!, :datetime] = DateTime.(df.Column1) .+ Hour.(df.Column2);

Und nun zum Plot:

@df df plot(:datetime, [:Column9, :Column6, :Column3], 
            xlims = (DateTime(2023,9,1), DateTime(2024,5,30)), 
            layout=(3,1), title=["Wind" "Regen" "Temp"], 
            legend=:none, size=(800,800))