Linux Basic — File Existence & Permissions Practice Exercises
File Existence & Permissions — Practice Exercises
These exercises stitch together everything in the chapter: test operators, defensive guards, chmod/chown, root checks, and safe directory work.
Exercise 1 — Classify Any Path
Goal: Write a function classify PATH that prints one of:
missingregulardirectorysymlink-brokensymlink → TARGETdevicepermission-denied
Setup:
mkdir -p /tmp/cls
cd /tmp/cls
touch reg
mkdir dir
ln -s reg link-good
ln -s nope link-bad
chmod 000 perm-denied
sudo cp /dev/zero ./dev-zero 2>/dev/null || true
💡 Show Answer
classify() {
local p=$1
if [[ -L "$p" && ! -e "$p" ]]; then echo "symlink-broken"
elif [[ -L "$p" ]]; then echo "symlink → $(readlink "$p")"
elif [[ ! -e "$p" ]]; then echo "missing"
elif [[ -d "$p" ]]; then echo "directory"
elif [[ -b "$p" || -c "$p" ]]; then echo "device"
elif [[ -f "$p" && ! -r "$p" ]]; then echo "permission-denied"
elif [[ -f "$p" ]]; then echo "regular"
else echo "other"
fi
}
for f in reg dir link-good link-bad missing perm-denied dev-zero; do
printf '%-15s %s\n' "$f" "$(classify "$f")"
done
# reg regular
# dir directory
# link-good symlink → reg
# link-bad symlink-broken
# missing missing
# perm-denied permission-denied
# dev-zero device (if you had perms to copy /dev/zero)
Exercise 2 — Defensive Cleanup
Goal: Write a safe_purge BASE SUBDIR function that deletes $BASE/$SUBDIR if and only if:
- Both vars are non-empty
- The combined path exists and is a directory
- The combined path is under
/tmpor/var/cache - The user confirms with
y
Print a clear error otherwise. Never call rm -rf if any guard fails.
💡 Show Answer
safe_purge() {
local base=$1 sub=$2
[[ -n "$base" && -n "$sub" ]] || { echo "BASE and SUBDIR required" >&2; return 2; }
local target="$base/$sub"
[[ -d "$target" ]] || { echo "not a directory: $target" >&2; return 2; }
[[ "$target" =~ ^/(tmp|var/cache)/ ]] \
|| { echo "$target outside allowed roots" >&2; return 2; }
read -p "Really delete $target? [y/N] " yn
[[ "${yn,,}" == y* ]] || { echo "aborted"; return 0; }
rm -rf -- "$target"
echo "purged $target"
}
safe_purge "" foo # BASE and SUBDIR required
safe_purge /tmp no-such # not a directory
safe_purge /home/me stuff # outside allowed roots
safe_purge /tmp scratch # prompts; if y, deletes
Exercise 3 — Tighten Permissions Recursively
Goal: On a directory tree, set:
- Every directory to mode
0750 - Every regular file to mode
0640 - Owner = current user, Group =
developers
…without touching symlinks.
💡 Show Answer
tighten() {
local root=$1
[[ -d "$root" ]] || { echo "not a dir: $root" >&2; return 2; }
find "$root" -type d -exec chmod 0750 {} \;
find "$root" -type f -exec chmod 0640 {} \;
chown -R "$(id -un):developers" "$root"
}
# Or with capital X — same idea, fewer commands
find "$root" \( -type f -o -type d \) -exec chmod u=rwX,g=rX,o= {} \;
The capital-X form sets execute only on dirs (and files that already had any execute bit), so a single pass works.
Exercise 4 — Self-Sudo Re-Exec
Goal: Write a script that:
- Checks whether it is running as root
- If not, prints “Need root — re-running with sudo” and re-execs itself
- Once running as root, writes
/etc/myapp-marker(owner root, mode 0644) and printsOK - Cleans up the marker file at the end with
trap
💡 Show Answer
#!/usr/bin/env bash
set -euo pipefail
if [[ $EUID -ne 0 ]]; then
echo "Need root — re-running with sudo"
exec sudo --preserve-env=PATH -- "$0" "$@"
fi
marker=/etc/myapp-marker
install -m 0644 /dev/null "$marker"
trap 'rm -f -- "$marker"' EXIT
echo "OK — wrote $marker"
ls -l "$marker"
# -rw-r--r-- 1 root root 0 ... /etc/myapp-marker
install -m 0644 /dev/null "$marker" creates an empty file with exact permissions in one atomic step.
Exercise 5 — Count Files Safely
Goal: Count regular files in /var/log (recursively). Then create three files with newlines or spaces in their names in /tmp/weird/, count those too, and prove your method survives weird filenames.
💡 Show Answer
# Robust count (any filename, including newlines)
find /var/log -type f -printf 'x' | wc -c
# 245
mkdir -p /tmp/weird
touch /tmp/weird/'normal'
touch /tmp/weird/'has space.txt'
touch /tmp/weird/$'has\nnewline.txt'
# ❌ wrong — counts 4 because the newline file shows as 2 "lines"
ls /tmp/weird | wc -l
# 4
# ✅ right — printf 'x' per file, count chars
find /tmp/weird -type f -printf 'x' | wc -c
# 3
# Or with an array (pure Bash, no find)
shopt -s nullglob dotglob
files=( /tmp/weird/* )
echo "${#files[@]}"
# 3
The -printf 'x' trick emits exactly one byte per matched file, immune to any filename content.
What’s Next?
Files and permissions — locked down. The next chapter pivots from byte streams to strings: parameter expansion, slicing, case conversion, pattern removal, and substitution — all without ever spawning sed or awk.
Continue → Working with Strings