---
engine: julia
---

# Ein- und Ausgabe


```{julia}
#| error: false
#| echo: false
#| output: false

# https://github.com/JuliaLang/julia/blob/master/base/show.jl#L516-L520
# https://github.com/JuliaLang/julia/blob/master/base/show.jl#L3073-L3077


using InteractiveUtils
import QuartoNotebookWorker
Base.stdout = QuartoNotebookWorker.with_context(stdout)
myactive_module() = Main.Notebook
Base.active_module() = myactive_module()
```



# Ein- und Ausgabe


## 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()`


### Eingaben

Die Sprache _Python_ stellt eine Funktion `input()`  zur Verfügung: 
```{.python}
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:

```{julia}
function input(prompt = "Eingabe:")
    println(prompt)
    flush(stdout)
    return chomp(readline())
end
```

**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. 

```{julia}
#| eval: false
a = input("Bitte 2 Zahlen eingeben!")
```
```{julia}
#| echo: false
a = "34 56"
```


### Verarbeitung der Eingabe

> `split(str)` zerlegt einen String in "Wörter" und liefert einen _(array of strings)_:

```{julia}
av = split(a)
```


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


```{julia}
v = parse.(Int, av)
```

`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. 



### Einzelne Tastenanschläge einlesen

- `readline()` u.ä. warten auf den Abschluss der Eingabe durch Drücken der `Enter`-Taste. 
- Techniken zum Einlesen einzelner _keystrokes_ findet man hier:
  
  - [https://stackoverflow.com/questions/56888266/how-to-read-keyboard-inputs-at-every-keystroke-in-julia](https://stackoverflow.com/questions/56888266/how-to-read-keyboard-inputs-at-every-keystroke-in-julia)
  - [https://stackoverflow.com/questions/60954235/how-can-i-test-whether-stdin-has-input-available-in-julia](https://stackoverflow.com/questions/60954235/how-can-i-test-whether-stdin-has-input-available-in-julia)
  



## 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. 

```{julia}
using Printf

x = 123.7876355638734

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


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`|

: {.striped .hover}


**Flags**

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

: {.striped .hover}


**Width**

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


### Beispiele:


```{julia}
using Printf    # Paket laden nicht vergessen!
```


```{julia}
@printf("|%s|", "Hallo")     # string mit Platzhalter für String
```
Die senkrechten Striche sind nicht Teil des Platzhalters. Sie sollen die Begrenzung des Ausgabefeldes anzeigen.


```{julia}
@printf("|%10s|", "Hallo")   # Minimallänge, rechtsbündig    
```


```{julia}
@printf("|%-10s|", "Hallo")     # linksbündig
```


```{julia}
@printf("|%3s|", "Hallo")     # Längenangabe kann überschritten werden 
                              # besser eine 'kaputt formatierte' Tabelle als falsche Werte!
```


```{julia}
j = 123
k = 90019001
l = 3342678

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

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


```{julia}
@printf("%i %i", 22, j)
```

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


```{julia}
@printf "%i %i" 22 j
```

`@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


```{julia}
@printf(stderr, "Erstes Resultat: %i %s\nZweites Resultat %i", 
                                   j, "(geschätzt)"       ,k)
```

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


```{julia}
str = @sprintf("x = %10.6f", π );
```


```{julia}
str
```

### Formatierung der Gleitkommazahlen:

Bedeutung des _Precision_-Wertes:

- `%f` und  `%e`-Format:  maximale Anzahl der Nachkommastellen
- `%g`-Format:  maximale Anzahl von ausgegebenen Ziffern (Vor- + Nachkommastellen) 


```{julia}
x = 123456.7890123456

@printf("%20.4f   %20.4e", x, x)     # 4 Nachkommastellen
```


```{julia}
@printf("%20.7f %20.7e", x, x)     # 7 Nachkommastellen
```


```{julia}
@printf("%20.7g %20.4g", x, x)    # insgesamt 7 bzw. 4 Stellen
```

## 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
 
 ```{.julia}
 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:

```{julia}
file = open("datei.txt", "w")
```


```{julia}
@printf(file, "%10i\n", k)
```


```{julia}
println(file, " zweite Zeile")
```


```{julia}
close(file)
```

Schauen wir uns die Datei  an:

```{julia}
;cat datei.txt
```

...und jetzt öffnen wir sie wieder zum Einlesen:

```{julia}
stream = open("datei.txt", "r")
```

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

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


```{julia}
n = 0
for line in eachline(stream)    # Lese zeilenweise
    n += 1
    println(n, line)            # Drucke mit Zeilennummer
end
close(stream)
```

## Pakete für Dateiformate

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

- [PrettyTables.jl](https://ronisbr.github.io/PrettyTables.jl/stable/) Ausgabe von formatierten Tabellen
- [DelimitedFiles.jl](https://docs.julialang.org/en/v1/stdlib/DelimitedFiles/) Ein- und Ausgabe von Matrizen u.ä.
- [CSV.jl](https://csv.juliadata.org/stable/) Ein- und Ausgabe von Dateien mit "comma-separated values" u.ä.
- [XLSX.jl](https://felipenoris.github.io/XLSX.jl/stable/tutorial/) Ein- und Ausgabe von Excel-Dateien

und viele andere mehr...

### DelimitedFiles.jl

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

```{julia}
using DelimitedFiles
```


Wir erzeugen eine 200×3-Matrix von Zufallszahlen
```{julia}
A = rand(200,3)
```

und speichern diese
```{julia}
f = open("data2.txt", "w")
writedlm(f, A)
close(f)
```


Die geschriebene Datei fängt so an:

```{julia}
;head data2.txt
```


Das Wiedereinlesen ist noch einfacher:
```{julia}
B = readdlm("data2.txt")
```


Noch ein Punkt: Beim Umgang mit Dateien wird in Julia oft die `do`-Notation verwendet, s. @sec-do.
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:

```{julia}
open("data2.txt", "w") do io
    writedlm(io, A)
end
```

### 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](https://dataframes.juliadata.org/stable/) stellt Funktionen zum bequemen Umgang mit tabellarischen Daten 
zur Verfügung. 



```{julia}
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:


```{julia}
# 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)
```



```{julia}
#| error: false
#| echo: false
#| output: false
#| eval: false
describe(df)
```



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

```{julia}
using StatsPlots, Dates
```


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

```{julia}
# neue Spalte mit Sp.1 und 2  (date & time) kombiniert

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




```{julia}
#| error: false
#| echo: false
#| output: false
#| eval: false

@df df plot(:datetime, :Column3)
```

Und nun zum Plot:

```{julia}
@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))
```



