Zsh Shell Tutorial #3: File Operations - touch, mkdir, cp, mv, rm & Wildcards (macOS)
Video: Zsh Shell Tutorial #3: File Operations - touch, mkdir, cp, mv, rm & Wildcards (macOS) by Taught by Celeste AI - AI Coding Coach
Zsh Lesson 3: File Operations — touch, mkdir, cp, mv, rm, Wildcards
touch filecreates empty (or updates mtime).mkdir -p pathcreates nested dirs.cp src dst,mv src dst.rmdeletes — no Trash, no undo. Wildcards:*any chars,?one char,[abc]set.
The five commands you'll use to create, copy, rename, and delete files. Sandbox first:
mkdir -p ~/zshshell-practice && cd ~/zshshell-practice
So nothing important gets touched.
touch: create or update
touch myfile.txt
ls -la myfile.txt
# -rw-r--r-- 1 alice staff 0 May 8 10:00 myfile.txt
touch path creates an empty file at path if it doesn't exist. If it does exist, it updates the modification time.
Multiple files at once:
touch file1.txt file2.txt file3.txt
ls -la *.txt
mkdir: create directories
mkdir myproject
ls -la
Creates a single directory.
For nested paths, -p creates parents as needed:
mkdir -p projects/webapp/src/components
Without -p, that errors if projects/ doesn't exist. With -p, also no error if the directory already exists. Always use -p — it's idempotent.
cp: copy
# Copy file to a new name
cp myfile.txt backup.txt
# Copy file INTO a directory (trailing slash makes it explicit)
cp myfile.txt myproject/
# Copy a directory recursively
cp -r myproject myproject_backup
-r (or -R) recursively copies a directory and all its contents. Without -r, copying a directory errors.
For preserving permissions, timestamps, and links:
cp -a src dst # archive mode — like -r but preserves more metadata
mv: move and rename
# Rename
mv backup.txt renamed.txt
# Move file into a directory
mv renamed.txt myproject/
# Move and rename simultaneously
mv old.txt myproject/new.txt
mv doesn't have a separate "rename" command — moving to the same directory with a different name is renaming.
Same syntax for directories — no -r needed for mv:
mv old_dir new_dir
rm: delete (DESTRUCTIVE)
rm file1.txt
rm file2.txt file3.txt # multiple files
There is no Trash. Files deleted via rm are gone.
For directories:
rm -r myproject_backup # recursive
rm -rf myproject_backup # recursive + force (no prompts, no errors on missing)
rm -rf is the most dangerous command in shell. Aimed at the wrong directory — say with a typo, or with $VAR that's accidentally empty making rm -rf $VAR/* become rm -rf /* — it can wipe your system.
Safer habits:
# 1. Always preview with ls first
ls files_to_delete*
# 2. Use -i for confirmation
rm -i file.txt
rm -ri directory/
# 3. Quote variables
rm -rf "$DIR/sub"
For irreplaceable files: don't use rm. Use Finder's "Move to Trash" via mv file ~/.Trash/ — gives you a chance to undo.
Wildcards (globbing)
Wildcards expand to matching filenames before the command runs.
# Setup
mkdir -p demo && cd demo
touch a.txt b.txt log1.txt log2.txt log3.txt
* — any number of characters
ls *.txt
# a.txt b.txt log1.txt log2.txt log3.txt
ls log*
# log1.txt log2.txt log3.txt
? — exactly one character
ls log?.txt
# log1.txt log2.txt log3.txt
ls log??.txt
# (nothing — needs exactly two chars after "log")
[abc] — one character from a set
ls log[12].txt
# log1.txt log2.txt
ls log[1-3].txt
# log1.txt log2.txt log3.txt
ls log[!2].txt # negation in zsh — NOT 2
# log1.txt log3.txt
** — recursive (Zsh)
ls **/*.txt # all .txt files in current dir AND subdirs
This is a Zsh-specific extension. In Bash, you'd need find.
Combining wildcards with commands
# Copy all .txt files into a backup dir
mkdir -p ../backup_txt
cp *.txt ../backup_txt/
# Move all log files into a logs dir
mkdir -p logs
mv log*.txt logs/
# Delete all temp files
rm temp*.tmp
Wildcards work with cp, mv, rm, ls, cat, anything that takes filenames.
Brace expansion: not a wildcard, but related
mkdir -p project/{src,test,docs}
# Creates project/src, project/test, project/docs
touch report_{2024,2025,2026}.txt
# Creates three files
Braces expand BEFORE the command runs — but they don't match existing files like wildcards do. They generate strings.
Globs that match nothing
ls *.foo
# zsh: no matches found: *.foo
By default, Zsh errors if a glob matches nothing. Bash silently passes the literal *.foo to the command. To get Bash-like behavior:
setopt nullglob # match-nothing → empty list
# or
setopt nomatch # the default — error
Most users set nullglob once in .zshrc.
Hidden files and globs
ls * # NOT including .dotfiles
ls .* # only dotfiles (and . and ..)
ls .?* # dotfiles, excluding . and ..
By default, * doesn't match leading .. To include them:
setopt globdots
Permissions: chmod
A quick aside — created files default to rw-r--r-- (644). To make a script executable:
chmod +x script.sh
Lesson 6 covers this in detail.
A complete workflow
# Set up a project skeleton
mkdir -p myproject/{src,tests,docs}
cd myproject
# Create starter files
touch src/main.py src/utils.py
touch tests/test_main.py
touch docs/README.md
# Verify
ls -laR
# Quick edit (any editor works)
nano src/main.py
# Backup before risky edit
cp -a src src_backup
# After confirming the new src works, remove backup
rm -r src_backup
# Or move it somewhere
mv src_backup ../archive/
Standard create-edit-backup-delete cycle. Use it constantly.
Common stumbles
rm with no recovery. Triple-check before. There's no Trash — rm releases the disk blocks immediately.
Wildcard expanded to too much. rm *.txt in /etc/? You'd be sad. Always ls first to see what'll match.
Forgot -r on cp. cp src_dir dest_dir errors. Add -r.
Trailing slash matters for cp/mv. cp file.txt mydir — if mydir exists, copies into it; if not, creates a copy named mydir. cp file.txt mydir/ always treats mydir as a directory (and errors if it doesn't exist).
Spaces in filenames. mv old name new name fails. Quote: mv "old name" "new name". Better: avoid spaces.
mkdir without -p. mkdir a/b/c errors if a/ doesn't exist. Always mkdir -p.
rm -rf $VAR/* with empty $VAR. Becomes rm -rf /*. Always quote and validate variables before destructive operations:
[[ -n "$DIR" ]] || exit 1
rm -rf "$DIR"/*
What's next
Lesson 4: viewing files. cat, less, head, tail, nano.
Recap
touch file creates or updates timestamp. mkdir -p path creates nested dirs (idempotent). cp src dst copies (-r for directories). mv src dst moves and renames. rm deletes — no Trash, no undo; use -i for confirmation. Wildcards: * any, ? one, [abc] set, **/ recursive (Zsh). Brace expansion {a,b,c} generates strings. Quote paths with spaces; never rm -rf unquoted variables.
Next lesson: viewing and editing files.