Linux Basic — File Existence & Permissions Practice Exercises

4 minute read

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:

  • missing
  • regular
  • directory
  • symlink-broken
  • symlink → TARGET
  • device
  • permission-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 /tmp or /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:

  1. Checks whether it is running as root
  2. If not, prints “Need root — re-running with sudo” and re-execs itself
  3. Once running as root, writes /etc/myapp-marker (owner root, mode 0644) and prints OK
  4. 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

Updated: