Linux Tutorial / Write Your First Bash Script with Variables, Conditions, and Loops
A shell script stores commands and control flow in a reusable program. In Bash, quote variable expansions, validate inputs, and check exit status while building and testing a small script.
This lesson creates a first program stored in a file. Review shell execution flow for variables and operators, and Bash errors and safe file operations for failure, path, and concurrency limits.
Choose Bash explicitly and prepare a workspace
This lesson targets Bash on Ubuntu and Rocky Linux. Features such as [[ ... ]] and arrays are not portable to every sh, so do not run a Bash script with sh script.sh. Check bash --version, work in a new directory, and save shell files with LF line endings.
lab=$(mktemp -d "$HOME/linux-script-lab.XXXXXX")
cd "$lab"
mkdir "sample files"
printf "one\n" > "sample files/alpha.txt"
printf "two\n" > "sample files/beta.txt"
Create the complete runnable script
The following block creates list-files.sh in the current directory. The script validates one directory argument and prints its regular-file names. It does not modify the files, making it suitable for experimenting with variables, branches, and loops.
cat > list-files.sh <<'EOF'
#!/usr/bin/env bash
if (( $# != 1 )); then
printf "Usage: %s DIRECTORY\n" "$0" >&2
exit 2
fi
directory=$1
if [[ ! -d "$directory" ]]; then
printf "Not a directory: %s\n" "$directory" >&2
exit 1
fi
count=0
for path in "$directory"/*; do
[[ -f "$path" ]] || continue
printf "%s\n" "${path##*/}"
count=$((count + 1))
done
printf "files=%s\n" "$count"
EOF
bash -n list-files.sh
bash list-files.sh "sample files"
Expect alpha.txt, beta.txt, and files=2; name ordering can depend on locale. bash -n checks syntax only, not runtime paths or permissions. The initial #! line is a shebang that selects an interpreter, not a shell prompt.
Understand variables and quoting
Assignment uses no spaces around the equals sign: directory=$1. Quoting "$directory" passes a path containing spaces as one argument. Single quotes generally suppress expansion; double quotes allow parameter expansion while preventing word splitting and pathname expansion. There is no need to reinterpret user input using eval.
In "$directory"/*, the directory portion is protected while the asterisk performs pathname expansion. Under default Bash settings, an unmatched pattern may remain literal, so the -f test skips it. The pattern excludes dotfiles and does not recurse. A symbolic link to a regular file can satisfy -f.
Read arguments, conditions, loops, and status
| Syntax | Meaning |
|---|---|
| $# / $1 | Argument count / first argument |
| [[ -d ... ]] | Test whether the target is a directory |
| for ...; do ...; done | Apply a body to each expanded item |
| ${path##*/} | Remove the prefix through the last slash |
| exit 0 / 1 / 2 | Success / invalid target / usage error in this example |
If the final printf succeeds, the script finishes with status 0. The meanings of 1 and 2 are conventions chosen by this script, not a universal classification for every command. $? holds the immediately previous command’s status, so inspect it before running another command.
bash list-files.sh
printf "exit=%s\n" "$?"
bash list-files.sh missing-directory
printf "exit=%s\n" "$?"
chmod u+x list-files.sh
./list-files.sh "sample files"
Change one condition at a time
- Run the script against the original directory and confirm a count of 2.
- Add gamma.txt and confirm the count changes to 3.
- Remove only the added file and confirm the count returns to 2.
- Test an empty directory for files=0 and a missing path for an error and status 1.
If errors mention bash\r or ^M after editing on Windows, inspect line endings. bash FILE reads the script directly; ./FILE needs executable permission and a valid interpreter line. Check your current directory before cleaning up the lab.
If a Windows editor shows CRLF, change it to LF and save. In VS Code, choose CRLF → LF in the status bar. With GNU sed, the following also makes a backup and converts this exercise’s list-files.sh.
cp -i -- list-files.sh list-files.sh.crlf-backup
sed -i 's/\r$//' list-files.sh
bash -n list-files.sh
bash list-files.sh "sample files"
A syntax check should exit 0 without output; with only the original two example files, expect files=2. To undo the conversion, inspect the backup and restore it with cp -i -- list-files.sh.crlf-backup list-files.sh.




