15  Input and Output

15.1 Console

The operating system typically provides three channels (streams) for a program:

  • Standard input (stdin)
  • Standard output (stdout)
  • Standard error (stderr)

When executed in a terminal, console, or shell, the program reads keyboard input through stdin and outputs to the terminal via stdout and stderr.

  • Writing to stdout: print(), println(), printstyled()
  • Writing to stderr: print(stderr,...), println(stderr,...), printstyled(stderr,...)
  • Reading from stdin: readline()

15.1.1 Input

The Python language provides an input() function:

ans = input("Please enter a positive number!")

It prints the prompt, waits for input, and returns a string.

In Julia, you can implement this function as follows:

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

Comments

  • Write operations are buffered by modern operating systems. flush(stdout) empties the buffer and forces the write operation to complete immediately.
  • readline() returns a string ending with a newline (\n). The function chomp() removes a trailing line break from the string.
a = input("Please enter two numbers!")
"34 56"

15.1.2 Processing the Input

split(str) splits a string into “words”, returning a string array:

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

parse(T, str) tries to convert str to type T:

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

parse() throws an error if the string cannot be parsed as type T. You can catch the error with try/catch, or use tryparse(T, str), which returns nothing in such cases. Test the result with isnothing().

15.2 Formatted Output with the Printf Macro

You often need to output numbers or strings with strict formatting: total length, decimal places, alignment, etc.

For this purpose, the Printf package defines the macros @sprintf and @printf, which work similarly to the corresponding C functions.

using Printf

x = 123.7876355638734

@printf("Output right-aligned with max. 10 character width and 3 decimal places: x = %10.3f", x)
Output right-aligned with max. 10 character width and 3 decimal places: x =    123.788

The first argument is a string containing placeholders (here: %10.3f) for the variables, followed by the variables themselves.

Placeholders have the form:

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

where entries in square brackets are optional.

Type specifications in placeholders

%s string
%i integer
%o integer, octal (base 8)
%x, %X integer, hexadecimal (base 16), digits 0-9a-f or 0-9A-F
%f floating point
%e floating point, scientific notation
%g floating point, %f or %e as appropriate

Flags

Plus sign right-aligned (default)
Minus sign left-aligned
Zero adds leading zeros

Width

Minimum number of characters used (more will be taken if necessary)

15.2.1 Examples

using Printf    # Load the package first
@printf("|%s|", "Hello")     # string with placeholder for string
|Hello|

The vertical bars are not part of the placeholder; they indicate the output field boundaries.

@printf("|%10s|", "Hello")   # Minimum length, right-aligned
|     Hello|
@printf("|%-10s|", "Hello")     # left-aligned
|Hello     |
@printf("|%3s|", "Hello")     # Length specification can be exceeded
                              # Better a badly formatted table than wrong values!
|Hello|
j = 123
k = 90019001
l = 3342678

@printf("j = %012i, k = %-12i, l = %12i", j, k, l)   # 0-flag for leading zeros
j = 000000000123, k = 90019001    , l =      3342678

@printf and @sprintf can be called like functions:

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

or as macros, i.e., without parentheses or commas:

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

@printf can take a stream as its first argument; otherwise, the argument list consists of:

  • format string with placeholders
  • variables matching the placeholders in number and type
@printf(stderr, "First result: %i %s\nSecond result %i", 
                                   j, "(estimated)", k)
First result: 123 (estimated)
Second result 90019001

The macro @sprintf does not print; it returns the formatted string:

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

15.2.2 Formatting Floating-Point Numbers

The precision value specifies:

  • %f and %e formats: maximum decimal places
  • %g format: maximum total digits (integer part + decimal places)
x = 123456.7890123456

@printf("%20.4f   %20.4e", x, x)     # 4 decimal places
         123456.7890             1.2346e+05
@printf("%20.7f %20.7e", x, x)     # 7 decimal places
      123456.7890123        1.2345679e+05
@printf("%20.7g %20.4g", x, x)    # 7 and 4 digits total, respectively
            123456.8            1.235e+05

15.3 File Operations

Files are handled by:

  • Opening \(\Longrightarrow\) creation of a new stream object (in addition to stdin, stdout, stderr)
  • Reading from and writing to this stream
  • Closing \(\Longrightarrow\) detachment of the stream object from the file
stream = open(path, mode)
  • path: filename or path

  • mode:

    "r"    read, opens at file beginning
    "w"    write, opens at file beginning (file is created or overwritten)
    "a"    append, opens to continue writing at file end

Let’s write a file:

file = open("myfile.txt", "w")
IOStream()
@printf(file, "%10i\n", k)
println(file, " second line")
close(file)

Let’s look at the file:

;cat myfile.txt
  90019001
 second line

…and now we open it again for reading:

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

readlines(stream) returns all lines of a text file as a string vector.

eachline(stream) returns an iterator over the file lines.

n = 0
for line in eachline(stream)    # Read line by line
    n += 1
    println(n, line)            # Print with line number
end
close(stream)
1  90019001
2 second line

15.4 Packages for File Formats

Julia packages for various file formats include:

and many more…

15.4.1 DelimitedFiles.jl

This package offers convenient functions for saving and reading matrices using writedlm() and readdlm().

using DelimitedFiles

Generate a 200×3 matrix of random numbers:

A = rand(200,3)
200×3 Matrix{Float64}:
 0.242052  0.139977   0.0213735
 0.860452  0.921534   0.135449
 0.780965  0.6372     0.416209
 0.284369  0.76668    0.0325718
 0.901622  0.0636347  0.833439
 0.796214  0.984548   0.0332568
 0.81308   0.568994   0.414981
 0.188305  0.865349   0.908197
 0.166486  0.65975    0.901737
 0.204107  0.141201   0.0720358
 ⋮                    
 0.31036   0.40433    0.513737
 0.762837  0.412597   0.0890357
 0.148856  0.314174   0.843368
 0.563406  0.537396   0.694132
 0.929565  0.264838   0.265931
 0.232175  0.742833   0.409047
 0.816274  0.642768   0.23883
 0.586098  0.973815   0.240567
 0.302521  0.683097   0.368841

and save it:

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

The written file starts like this:

;head data2.txt
0.24205199766594254 0.1399771615290385  0.021373493695757695
0.860451688227497   0.9215337499514452  0.13544883014638742
0.780964976014078   0.6371995170230044  0.4162091214722937
0.28436852057305706 0.7666802395700884  0.03257182903393552
0.901622476189251   0.06363468493606483 0.8334389353153023
0.7962141348779413  0.9845483637051188  0.03325683070660668
0.8130801601016981  0.5689943195747339  0.41498081083566574
0.1883049395764308  0.8653492832736521  0.9081965329945879
0.16648615248459198 0.6597504707341652  0.9017373388815852
0.20410724461059393 0.14120130273969866 0.07203584538037122

Reading it back is simple:

B = readdlm("data2.txt")
200×3 Matrix{Float64}:
 0.242052  0.139977   0.0213735
 0.860452  0.921534   0.135449
 0.780965  0.6372     0.416209
 0.284369  0.76668    0.0325718
 0.901622  0.0636347  0.833439
 0.796214  0.984548   0.0332568
 0.81308   0.568994   0.414981
 0.188305  0.865349   0.908197
 0.166486  0.65975    0.901737
 0.204107  0.141201   0.0720358
 ⋮                    
 0.31036   0.40433    0.513737
 0.762837  0.412597   0.0890357
 0.148856  0.314174   0.843368
 0.563406  0.537396   0.694132
 0.929565  0.264838   0.265931
 0.232175  0.742833   0.409047
 0.816274  0.642768   0.23883
 0.586098  0.973815   0.240567
 0.302521  0.683097   0.368841

In Julia, the do notation is frequently utilized for file handling (see Section 10.6). The open() function includes methods where the first argument is a function(iostream). This function is applied to the stream, which is automatically closed afterward. The do notation allows to define this function anonymously:

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

15.4.2 CSV and DataFrames

  • The CSV format provides tables readable by MS Excel and other applications.
  • Example: the weather and climate database Meteostat.
  • The DataFrames.jl package handles tabular data conveniently.
using CSV, DataFrames, Downloads
# Weather data from Westerland (see 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);

The data looks like this:

# https://dev.meteostat.net/bulk/hourly.html#endpoints
#
# Column 1  Date
#        2  Time (hour)
#        3  Temperature
#        5  Humidity
#        6  Precipitation
#        8 Wind direction
#        9 Wind speed

df =  DataFrame(file)
189280×13 DataFrame
189255 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
189269 2025-08-23 17 19.8 14.2 70 missing missing 332 22.2 31.5 1011.7 missing 2
189270 2025-08-23 18 19.2 14.2 73 missing missing 342 22.2 31.5 1011.7 missing 2
189271 2025-08-23 19 18.6 14.3 76 missing missing 343 20.4 29.6 1011.9 missing 2
189272 2025-08-23 20 18.7 14.0 74 missing missing 351 20.4 27.8 1013.2 missing 2
189273 2025-08-23 21 18.3 14.0 76 missing missing 354 20.4 27.8 1013.4 missing 2
189274 2025-08-23 22 18.0 13.9 77 missing missing 2 20.4 27.8 1013.5 missing 2
189275 2025-08-23 23 17.9 13.6 76 missing missing 3 20.4 29.6 1012.4 missing 2
189276 2025-08-24 0 17.9 13.6 76 missing missing 7 20.4 31.5 1012.4 missing 2
189277 2025-08-24 1 17.7 13.6 77 missing missing 13 18.5 31.5 1012.2 missing 2
189278 2025-08-24 2 17.0 13.7 81 missing missing 14 18.5 31.5 1012.1 missing 2
189279 2025-08-24 3 16.9 13.8 82 missing missing 18 18.5 31.5 1012.2 missing 2
189280 2025-08-24 4 17.0 13.7 81 missing missing 9 20.4 31.5 1012.1 missing missing

For convenient plotting and date/time handling, we load two packages:

using StatsPlots, Dates

We create a new column that combines date (from column 1) and time (from column 2):

# new column combining col. 1 and 2 (date & time)

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

The resulting plot:

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