Session 7 · Computer Systems

Practice tasks: from input to output file

Six tasks in the style of the practical test, from reading a number to finding the most frequent visitor in a CSV-like file, each with a hint and an expected result.

8 min readA routine for every taskread vs argumentsTrailing spaces and CRLFTwo outputs from one loopMaximum of a field

This session is a practice set in the style of the practical test: six tasks, from a two-line warm-up to a small data-processing script. Nothing new is introduced – everything here was in the previous three sessions. The point is to get fast and reliable at the routine: argument checks, the safe loop, a pattern, an output.

Work through them in order and time yourself. Open a hint only after you have a first version that runs; every task has an expected result so you can check yourself.

Read the statement twice. "Input value, not a command-line argument" and "the file name comes as an argument" are different scripts. Most lost points in a practical test come from solving a slightly different task.

The routine

1 · square a numberread, $(( n * n ))2 · copy lines with a wordglob *ZH*, then > and >>3 · lines ending in a digittr -d '[:space:]', [0-9]$4 · count numeric linesdecimal regex, a counter5 · split odd and evenn % 2, a sum and an output file6 · most frequent visitorsplit at commas, a maximumFrom a warm-up to a full file-processing task – every one from 2 on reads a file with the universal loop.
Fig. 1 · Six tasks, one main tool each. Recognising the tool is half the solution.

For every task from 2 on, the same steps, in this order:

  1. Check the number of arguments; print a usage line to standard error and exit 1.
  2. Check that the input file exists (-f); if not, an error message and exit 1.
  3. If there is an output file, empty it once before the loop.
  4. Paste the universal loop: IFS= read -r line || [ -n "$line" ], strip \r, skip empty lines.
  5. Write only the processing step – the part that differs between tasks.
  6. Run it, compare with the expected result, look at the input with cat -A if they differ.

steps 1–2

if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <input_file> <output_file>" >&2
    exit 1
fi
if [ ! -f "$1" ]; then
    echo "Error: file '$1' does not exist" >&2
    exit 1
fi

The tasks

Task 1

Square of a number

Read a number as input – with read, not as a command-line argument – and print its square. You may assume the input is valid.

Number: 2
2^2 = 2 × 2 = 4
Approach

read -p prints the prompt and reads in one step. The arithmetic is a single $(( … )); build the output line around it.

read -r -p "Number: " n      # from the keyboard – not $1

Task 2

Copy the lines that contain a word

Copy every line containing ZH from one file to another. Both file names are command-line arguments: the source, then the file to create. If the source doesn't exist, tell the user. Input: 2.txt; expected: 2 lines in the new file.

Approach

A glob test is enough – no regex needed for "contains". Empty the output before the loop, append inside it. One-line alternative: grep ZH "$1" > "$2".

> "$output_file"
...
    if [[ $line == *ZH* ]]; then
        echo "$line" >> "$output_file"
    fi

Task 3 looks like a one-liner, but the line's real last character is not always the one you see. Depending on where the file was made, there can be spaces or a \r after the last digit (Fig. 2).

as read…n:sp5sp\r[0-9]$ → no matchlast characterclean=$(echo -e "$line" | tr -d '[:space:]')cleaned copy…n:5[0-9]$ → match
Fig. 2 · Test a cleaned copy; print the original. sp = a space.

Task 3

Lines that end in a number

Print the lines of a file (argument) that end in a digit. Check that the file exists. Input: 3.txt; expected: 2 lines.

Approach

Delete every whitespace character from a copy of the line and test that copy with an end-anchored pattern. tr -d '[:space:]' also removes the spaces between words – harmless here, since only the last character matters, but a reason not to print the copy.

clean=$(echo -e "$line" | tr -d '[:space:]')
if [[ $clean =~ [0-9]$ ]]; then
    echo "$line"                 # print the original, not the cleaned copy
fi

Task 4

Count the numeric lines

Count the lines of a file (argument) that contain only a number – think of real numbers too: sign, decimal point. Print the count. Input: 4.txt; expected: 5.

Approach

The decimal pattern from the text processing session, anchored on both ends. 420, 1222, 123, 12.2 and -12.3 count; asd2, dsa3 and a sentence that merely contains 23 do not.

if [[ $line =~ ^[+-]?[0-9]*\.?[0-9]+$ ]]; then
    count=$(( count + 1 ))
fi

Task 5 sends each number one of two ways and produces two results (Fig. 3).

5.txt2345769n % 20Sum of even numbers: 12terminal – 2 + 4 + 61OddNumbers.txt3579Sum of copied odd numbers: 24
Fig. 3 · One loop, one decision, two outputs: a number on the terminal and a file.

Task 5

Even sum, odd numbers to a file

From a file (argument), add up the even numbers and print the sum (expected: 12). Copy the odd numbers to a new file, OddNumbers.txt, add them up too, and write the sum as the last line: Sum of copied odd numbers: 24. The 24 must come from a variable, not be typed in. Input: 5.txt.

Approach

Two sums, one if on n % 2. The odd-sum line is appended after the loop, when the sum is final.

> OddNumbers.txt
...
    if (( n % 2 == 0 )); then
        even_sum=$(( even_sum + n ))
    else
        echo "$n" >> OddNumbers.txt
        odd_sum=$(( odd_sum + n ))
    fi
...
echo "Sum of copied odd numbers: $odd_sum" >> OddNumbers.txt

Task 6

The most frequent visitor

A GP practice keeps its patient register as plain text: Name, visits, date of last visit. Print the name of the person who visited most often, and how many times. Check that the file exists. Input: 6.txt; expected: Kiss Lajos – 12 visits.

Approach

Split each line at the commas with a here string. The number has a space in front of it – clean it before comparing, or -gt complains. Keep the best name and count so far.

IFS=',' read -r name visits date <<< "$line"
visits=$(echo "$visits" | tr -d '[:space:]')
if [ "$visits" -gt "$max" ]; then
    max=$visits
    best=$name
fi

Check yourself

TaskInputExpected
1keyboard: 22^2 = 2 × 2 = 4
22.txt2 lines in the new file
33.txt2 lines printed
44.txt5
55.txt12 on the terminal; OddNumbers.txt: 3, 5, 7, 9 and the sum 24
66.txtKiss Lajos – 12

Summary

  • Input (read) and argument ($1) are different – the statement says which.
  • Argument count, file exists, empty the output – before any processing.
  • Test a cleaned copy of the line; print the original.
  • One loop can feed several results: a sum to the terminal and lines to a file.
  • Check every script on a bad input too, not only on the sample file.