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

Source: https://laszlonemes.com/university/computer-systems/practice-tasks · Author: László Nemes

Session 7 · Computer Systems

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 read `A routine for every task` `read vs arguments` `Trailing spaces and CRLF` `Two outputs from one loop` `Maximum 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.

After this session you can

- go from a task statement to a working script with a fixed routine
- read user input with `read` and arguments with `$1`, `$2` – and know which the task wants
- make a line test robust against trailing spaces and Windows line endings
- write two kinds of results at once: a sum to the terminal, lines to a file
- find the maximum of a field in a comma-separated file

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

> **Fig. 1** · Six tasks, one main tool each. Recognising the tool is half the solution.
>
> *Diagram:* The six practice tasks and the main tool each one needs: reading input and arithmetic, a glob test with output redirection, whitespace cleaning with an anchored regex, a decimal regex with a counter, a modulo split into a sum and a file, and splitting fields to find a maximum.

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

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

In your assignment

**Same routine, bigger script**

Your shell assignment is checked the same way as these tasks: wrong arguments, a missing file and an awkward input are among the first things tried. Make steps 1–2 a habit, and the edge cases cost you nothing.

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

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

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

```shell
> "$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).

> **Fig. 2** · Test a cleaned copy; print the original. `sp` = a space.
>
> *Diagram:* A line that visibly ends in 5 may really end in a space or a carriage return, so the pattern digit at end of line does not match. After deleting all whitespace from a copy of the line, the last character is 5 and the pattern matches.

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.

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

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

> **Fig. 3** · One loop, one decision, two outputs: a number on the terminal and a file.
>
> *Diagram:* Each number goes one of two ways depending on n modulo 2. Even numbers are only added up, and the sum, 12, is printed to the terminal. Odd numbers are copied to OddNumbers.txt and added up separately; the sum, 24, is written as the last line of that 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.

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

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

Good to know

**Going further with task 6**

In this file every name appears once. If a name could appear in several lines and the visits had to be added up, you would sum per name in an associative array first and find the maximum after the loop – exactly the last "More practice" task of the text processing session.

## Check yourself

| Task | Input | Expected |
| --- | --- | --- |
| 1 | keyboard: `2` | `2^2 = 2 × 2 = 4` |
| 2 | `2.txt` | 2 lines in the new file |
| 3 | `3.txt` | 2 lines printed |
| 4 | `4.txt` | 5 |
| 5 | `5.txt` | 12 on the terminal; `OddNumbers.txt`: 3, 5, 7, 9 and the sum 24 |
| 6 | `6.txt` | Kiss Lajos – 12 |

Common mistake

**Right answer, wrong script**

Matching the expected output on the given file isn't the same as a correct script. Run each solution once more with a missing file name, a non-existent file, and the input converted to CRLF (`sed 's/$/\r/' 5.txt > 5_crlf.txt`). If any of these breaks it, go back to the routine.

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