Bash Scripting: Build and Test a First Script - Yenra

Count newlines in text files while learning Bash arguments, quoting, loops and failure checks.

Conceptual terminal and file tiles passing through a checkpoint toward an output tray.
Conceptual illustration: a script turns a defined set of inputs into an inspectable result.

Build a small Bash script that counts newline characters in a folder of text files, then use its output and exit status to check what happened. The example handles spaces in filenames and gives you a safe place to practice arguments, quoting, loops and error handling.

You need Bash and the wc utility, available together in many Unix environments and Git Bash on Windows. Run the commands in Bash; PowerShell uses different syntax. The download was checked with Bash 5.2.37. It reads the sample files and prints a report without changing them.

Start with a working folder

Download the Bash practice kit, extract it into a new folder, and open a Bash terminal in the extracted sample directory. Run:

bash count-lines.sh notes

The supplied fictional files contain two newline characters in alpha.txt and one in two words.txt. With the ordinary locale ordering, the output is:

file           newlines
alpha.txt      2
two\ words.txt 1

The actual separator is a tab. Bash's %q formatting displays a space as \ so the filename remains unambiguous; the backslash is presentation, not part of that filename. A final line without a terminating newline contributes zero to wc -l. Decide whether your task needs newline counts or logical records before adapting this example.

Read the script from input to result

#!/usr/bin/env bash
# Yenra educational example. Counts newline characters in visible *.txt files.
# Usage: bash count-lines.sh DIRECTORY
# https://yenra.com/bash-shell-script-programming/
if (( $# != 1 )); then
  printf 'Usage: bash count-lines.sh DIRECTORY\n' >&2
  exit 2
fi
dir=$1
if [[ ! -d $dir || ! -r $dir || ! -x $dir ]]; then
  printf 'Directory must exist and be readable/searchable: %s\n' "$dir" >&2
  exit 2
fi
shopt -s nullglob
status=0
printf 'file\tnewlines\n'
for file in "$dir"/*.txt; do
  [[ -f $file && ! -L $file ]] || continue
  if count=$(wc -l < "$file"); then
    count=${count//[[:space:]]/}
    printf '%q\t%s\n' "${file##*/}" "$count"
  else
    printf 'Could not read: %s\n' "$file" >&2
    status=1
  fi
done
exit "$status"

The first guard requires exactly one argument. The second checks that it identifies a readable, searchable directory. Both use status 2 to describe incorrect invocation. That convention belongs to this script; other tools can assign different meanings to their nonzero statuses.

"$dir"/*.txt protects the directory argument while allowing the wildcard to expand. Quoting the entire expression as "$dir/*.txt" would make the asterisk literal. nullglob makes a pattern with no matches expand to an empty list, so an empty directory prints only the header. The loop skips symbolic links and entries that are not regular files. Hidden names and nested directories are outside this example's scope.

The redirection in wc -l < "$file" supplies file contents on standard input. It also avoids passing a filename beginning with a dash as a wc option. The assignment appears in an if, making its success or failure an explicit branch. A read failure sets the final status to 1 while allowing the loop to examine later files.

Bash-specific features here include [[ ... ]], arithmetic (( ... )), shopt, the substitution that removes whitespace from the count, and %q. Invoke this file with Bash. The GNU Bash manual documents these features; the installed commands help printf and help shopt give local reference material. For the underlying quote rules, see the POSIX shell language specification.

Check failure as well as success

Run each command separately and print its status immediately afterward:

bash count-lines.sh missing-folder
printf 'exit=%s\n' "$?"

The expected result is a directory diagnostic on standard error and exit=2. Any command placed between those two lines would replace the status you meant to inspect.

Check failure as well as success
Exercise Expected result What it checks
Supplied notes folder Counts 2 and 1; exit 0 Normal path and a filename containing a space
Existing empty folder Header only; exit 0 Wildcard with no matches
Missing argument Usage message; exit 2 Argument validation
A text file with no final newline Count includes only actual newlines Definition of the measurement

Before running a changed script, use bash -n count-lines.sh to check syntax. To follow execution, use bash -x count-lines.sh notes with these harmless fixtures. Tracing prints expanded values, so use fabricated data when investigating scripts that normally handle credentials or private records. A syntax pass establishes that Bash can parse the file; the exercises establish selected behaviors.

Adapt one behavior at a time

Choose a concrete next change, such as a different extension or a total row, and write down the expected output first. Preserve the same tests for empty input, spaces and errors. Adding recursion requires a new traversal design; the current glob deliberately visits one directory only.

For portable command patterns, continue with the Unix shell reference. For a report that needs grouping and numeric validation, the shell and SQLite exercise moves those responsibilities into SQL.