A menu script is a useful way to practice input, branching and loops in Bash. It is also an easy place to make a destructive mistake if a filename is treated as shell syntax or a path is allowed to escape the exercise folder.
This version creates its own private temporary directory and operates only on simple filenames inside it. Run it as an ordinary user, not with sudo. It is a learning exercise, not a general file manager or a security boundary against another process running as the same user.
The menu script
Save the code as menu.sh with Unix line endings and run bash menu.sh. Use Bash, not sh: the filename validation uses Bash conditional syntax.
#!/usr/bin/env bash
umask 077
workdir=$(mktemp -d) || exit 1
printf 'Exercise directory: %s\n' "$workdir"
trap 'printf "Files retained in: %s\n" "$workdir"' EXIT
while true; do
printf '\n1. Create file\n2. Delete file\n3. Exit\nChoice: '
IFS= read -r choice || break
case "$choice" in
1|2)
printf 'Filename (letters, digits, spaces, dot, underscore, hyphen): '
IFS= read -r name || break
if [[ ! $name =~ ^[a-zA-Z0-9][a-zA-Z0-9._\ -]*$ ]]; then
printf 'Invalid filename. Paths and leading punctuation are not allowed.\n'
continue
fi
target="$workdir/$name"
if [[ $choice == 1 ]]; then
if [[ -e $target || -L $target ]]; then
printf 'Already exists; nothing overwritten.\n'
elif (set -o noclobber; : > "$target"); then
printf 'Created: %s\n' "$name"
else
printf 'Creation failed.\n' >&2
fi
else
if [[ ! -f $target || -L $target ]]; then
printf 'Not an ordinary exercise file.\n'
continue
fi
printf 'Delete "%s"? Type DELETE: ' "$name"
IFS= read -r answer || break
if [[ $answer != DELETE ]]; then
printf 'Cancelled.\n'
elif rm -- "$target"; then
printf 'Deleted: %s\n' "$name"
else
printf 'Deletion failed.\n' >&2
fi
fi
;;
3) break ;;
*) printf 'Choose 1, 2 or 3.\n' ;;
esac
done
Why the input handling is deliberate
IFS= read -r reads the line without trimming its whitespace or treating backslashes as escapes. The filename rule then deliberately accepts a smaller set of characters. A slash, an empty name, ../notes and a leading hyphen are rejected. A name such as draft 1.txt is accepted and stays one argument because every filesystem path is quoted.
The case statement chooses an action, while the loop displays the menu again after that action. End-of-input exits rather than spinning forever. The GNU Bash manual documents read, conditional expressions, quoting and case.
Creation and deletion need different safeguards
Creating an existing name must not truncate a file. The script checks for existing entries and uses a no-clobber redirection when creating the empty file. Deletion requires a regular file, rejects symbolic links and asks for the exact confirmation word. It never recursively removes directories.
Even with these checks, a shell script is not designed to defeat hostile concurrent manipulation by the same account. Keep this exercise separate from a shared upload directory or production data. Do not turn the temporary path into a user-supplied arbitrary directory without redesigning the constraints.
Exercise the failure paths too
Create draft 1.txt, try creating it again, cancel a deletion, then confirm the deletion. Try an invalid menu choice and an invalid path. Each response should tell you whether anything changed. The script intentionally keeps its temporary folder on exit and prints its location; inspect it and remove only that specific exercise folder when finished.