cli-tutor 0.4.1

Interactive terminal app for learning Unix command-line tools
Documentation
[module]
name = "cat"
description = "Concatenate and display files — the simplest way to start a pipeline"
version = 1

[intro]
text = """
## Why cat matters

`cat` stands for **concatenate**. Its original purpose was stitching multiple files together, but its most common use today is feeding a file into a pipeline:

```
cat access.log | grep '500' | wc -l
```

This pattern — `cat file | command` — is the fundamental building block of shell pipelines. Every tool you learn (grep, awk, sed, sort) becomes more powerful once you can point it at any file with `cat`.

Beyond pipelines, `cat` earns its place in several everyday workflows:

- **Quick file inspection.** For small files, `cat` is faster than opening an editor — you see the full content immediately.
- **Combining files.** `cat header.txt body.txt footer.txt > report.txt` assembles a document from parts. Build configs, SQL migrations, and templated emails all benefit from this.
- **Numbered lines for debugging.** `cat -n script.sh` shows line numbers alongside content, which makes error messages like "line 14: syntax error" instantly navigable.
- **Exposing invisible characters.** `cat -A file` reveals hidden characters: tabs appear as `^I` and line endings appear as `$`. This is how you diagnose "my CSV parses fine on Linux but breaks on macOS" — you look for `^M` (Windows-style `\r\n` line endings).

## Key flags

- `-n` — number every output line, starting at 1. Indispensable when correlating error messages with source files.
- `-A` — show all non-printing characters. Tabs render as `^I`, line endings as `$`, and Windows carriage returns as `^M`. Use this whenever a file "looks fine" in a text editor but behaves unexpectedly in a pipeline — the culprit is almost always hidden characters.
- `-s` — squeeze multiple consecutive blank lines into a single blank line. Useful when concatenating files that each have trailing newlines, to avoid walls of blank space in the output.

## Real-world workflows

**Assembling a config from parts:**
```
cat header.conf app.conf > final.conf
```
Combine a shared preamble with an app-specific block into a single deployable config file.

**Debugging invisible characters in a CSV:**
```
cat -A data.csv | head -5
```
If you see `^M$` at the end of each line, the file has Windows line endings (`\r\n`) — a common cause of mysterious parse failures in Unix tools.
"""

[[examples]]
title = "Display file contents"
description = "Print the entire contents of a file to the terminal"
command = "cat message.txt"
output = "Hello, World!\nWelcome to the shell.\nEnjoy your stay.\n"

[[examples]]
title = "Concatenate two files"
description = "Print both files in sequence — contents flow together without a separator"
command = "cat header.txt config.txt"
output = "# Server Configuration\n# Generated by deploy.sh\nhost=db.internal\nport=5432\npool=10\n"

[[exercises]]
id = "cat.1"
difficulty = "beginner"
question = """Display the full contents of `message.txt`."""
expected_output = "Hello, World!\nWelcome to the shell.\nEnjoy your stay.\n"
hints = [
  "cat followed by a filename prints that file's contents",
  "Try: cat message.txt",
]
solution = "cat message.txt"
match_mode = "exact"

[[exercises.fixtures]]
filename = "message.txt"
content = "Hello, World!\nWelcome to the shell.\nEnjoy your stay.\n"

[[exercises]]
id = "cat.2"
difficulty = "beginner"
question = """When debugging a script, line numbers help you correlate error messages with source code. Display `script.sh` with each line numbered."""
expected_output = "     1\t#!/bin/bash\n     2\tset -e\n     3\techo starting\n     4\tnpm install\n     5\tnpm test\n"
hints = [
  "The -n flag adds line numbers to every output line",
  "cat -n pads line numbers with spaces and separates them from content with a tab",
  "Try: cat -n script.sh",
]
solution = "cat -n script.sh"
match_mode = "exact"

[[exercises.fixtures]]
filename = "script.sh"
content = "#!/bin/bash\nset -e\necho starting\nnpm install\nnpm test\n"

[[exercises]]
id = "cat.3"
difficulty = "intermediate"
question = """You're building a config file from parts. Print the contents of `header.txt` followed immediately by `config.txt`."""
expected_output = "# Server Configuration\n# Generated by deploy.sh\nhost=db.internal\nport=5432\npool=10\n"
hints = [
  "cat accepts multiple filenames — it prints them in order",
  "Try: cat header.txt config.txt",
]
solution = "cat header.txt config.txt"
match_mode = "exact"

[[exercises.fixtures]]
filename = "header.txt"
content = "# Server Configuration\n# Generated by deploy.sh\n"

[[exercises.fixtures]]
filename = "config.txt"
content = "host=db.internal\nport=5432\npool=10\n"

[[exercises]]
id = "cat.4"
difficulty = "intermediate"
question = """Two microservices write to separate log files. Count the total number of log lines across both `app1.log` and `app2.log`."""
expected_output = "5\n"
hints = [
  "cat can merge multiple files into one stream before piping",
  "Pipe the combined output to wc -l to count lines",
  "Strip leading spaces from wc output with tr -d ' '",
  "Try: cat app1.log app2.log | wc -l | tr -d ' '",
]
solution = "cat app1.log app2.log | wc -l | tr -d ' '"
match_mode = "normalized"

[[exercises.fixtures]]
filename = "app1.log"
content = "request\nrequest\nrequest\n"

[[exercises.fixtures]]
filename = "app2.log"
content = "request\nrequest\n"

[[exercises]]
id = "cat.5"
difficulty = "advanced"
question = """Two service logs both contain errors. Combine them, keep only error lines, remove duplicates, and print unique errors sorted alphabetically."""
expected_output = "ERROR disk full\nERROR network timeout\n"
hints = [
  "cat combines both logs into a single stream",
  "grep 'ERROR' filters to only error lines",
  "sort -u sorts and deduplicates in one step",
  "Try: cat service1.log service2.log | grep 'ERROR' | sort -u",
]
solution = "cat service1.log service2.log | grep 'ERROR' | sort -u"
match_mode = "exact"

[[exercises.fixtures]]
filename = "service1.log"
content = "INFO started\nERROR disk full\nINFO request\nERROR disk full\nINFO done\n"

[[exercises.fixtures]]
filename = "service2.log"
content = "INFO started\nERROR network timeout\nINFO request\nERROR disk full\nINFO done\n"