File and directory manipulation is at the core of system automation. Every backup script, deployment pipeline, and log management tool works with files. Knowing how to do it safely — with proper error checking, no data loss, and correct permissions — is essential production knowledge.
1
Create, read, write, append
BASH
# ── Create files ──────────────────────────────────────────
touch newfile.txt # create empty or update timestamp
touch file{1,2,3}.txt # create file1.txt file2.txt file3.txt
echo "content" > file.txt # create with content
cat > file.txt << 'EOF'
multi-line
content here
EOF
# ── Read files ────────────────────────────────────────────
cat file.txt # print entire file
head -5 file.txt # first 5 lines
tail -5 file.txt # last 5 lines
content=$(cat file.txt) # read into variable
first_line=$(head -1 file.txt) # first line into variable
# ── Write / append ────────────────────────────────────────
echo "line" > file.txt # overwrite
echo "line" >> file.txt # append
printf "%s\n" "line" >> file.txt # printf append (more control)
# ── Create directories ────────────────────────────────────
mkdir mydir # single directory
mkdir -p /deep/nested/path # create all parents (no error if exists)
mkdir -p logs/{2026,2025}/{jan,feb,mar} # brace expansion structure
2
Copy, move, delete safely
BASH
# ── Copy ──────────────────────────────────────────────────
cp file.txt backup.txt # copy file
cp -r srcdir/ destdir/ # copy directory recursively
cp -p file.txt backup.txt # preserve permissions + timestamps
cp -a srcdir/ destdir/ # archive: recursive + preserve all
cp -u src dst # only copy if src is newer
cp -v file.txt backup.txt # verbose — show what's happening
cp -i file.txt backup.txt # interactive — prompt before overwrite
# ── Move / rename ─────────────────────────────────────────
mv old.txt new.txt # rename
mv file.txt /backup/ # move to directory
mv -v file.txt /backup/ # verbose
mv -n file.txt /backup/ # no-clobber: don't overwrite
# ── Delete safely ─────────────────────────────────────────
rm file.txt # delete file
rm -f file.txt # force (no error if missing)
rm -r mydir/ # delete directory recursively
rm -rf mydir/ # force recursive (DANGEROUS — no undo!)
rmdir emptydir/ # remove empty directory only
# ── Safe delete pattern — always check path is not empty ──
safe_rm() {
local path="${1:?safe_rm: path required}"
[[ -z "${path}" ]] && { echo "ERROR: empty path" >&2; return 1; }
[[ "${path}" == "/" ]] && { echo "ERROR: refusing to delete /" >&2; return 1; }
rm -rf "${path}"
}
# Never do: rm -rf "${var}/" if $var could be empty!
# That becomes: rm -rf /
⚠ Never
rm -rf "${var}/" without guarding — if $var is empty, this becomes rm -rf / and destroys the system. Always validate the variable is non-empty before any destructive operation.
3
find — locate files with actions
BASH
# ── Basic find ────────────────────────────────────────────
find /var/log -name "*.log" # by name pattern
find /var/log -iname "*.LOG" # case-insensitive
find /app -type f # files only
find /app -type d # directories only
find /app -type l # symlinks only
# ── By age ────────────────────────────────────────────────
find /tmp -mtime +7 # modified > 7 days ago
find /tmp -mtime -1 # modified within 1 day
find /var/log -mmin +60 # modified > 60 minutes ago
find /backups -newer reference.txt # newer than reference file
# ── By size ───────────────────────────────────────────────
find /var -size +100M # > 100 MB
find /tmp -size 0 # empty files
find /logs -size +1G -size -5G # between 1G and 5G
# ── With actions ──────────────────────────────────────────
find /tmp -name "*.tmp" -delete # delete in one step
find /logs -mtime +30 -exec gzip {} \; # compress old logs
find /app -name "*.sh" -exec chmod +x {} \; # make scripts executable
find /var/log -mtime +7 -print0 | xargs -0 rm -f # safe with spaces
# ── Combine conditions ────────────────────────────────────
find /var/log -name "*.log" -size +10M -mtime +3
find /app \( -name "*.pyc" -o -name "__pycache__" \) -delete
4
Permissions, stat, and mktemp
BASH
# ── Permissions ───────────────────────────────────────────
chmod 755 script.sh # rwxr-xr-x
chmod 640 config.env # rw-r----- (owner rw, group r)
chmod +x script.sh # add execute for all
chmod go-w file.txt # remove write from group+others
chmod -R 755 /app/ # recursive
chown vriddh:www-data file.txt # change owner:group
chown -R app:app /opt/myapp/ # recursive ownership
# ── stat — file metadata ──────────────────────────────────
stat file.txt # full metadata
stat -c "%s" file.txt # file size in bytes
stat -c "%U:%G" file.txt # owner:group
stat -c "%a" file.txt # octal permissions
stat -c "%y" file.txt # last modified time
stat -c "%i" file.txt # inode number
# ── mktemp — safe temporary files ────────────────────────
tmpfile=$(mktemp) # /tmp/tmp.XXXXXXXXXX
tmpdir=$(mktemp -d) # temporary directory
tmpfile=$(mktemp /tmp/myapp.XXXXXX) # custom prefix/suffix
# Always clean up temp files on exit
cleanup() { rm -f "${tmpfile}"; rm -rf "${tmpdir}"; }
trap cleanup EXIT INT TERM
5
rsync — reliable file transfer and sync
BASH
# ── rsync basics ──────────────────────────────────────────
rsync -av src/ dest/ # archive + verbose
rsync -av --delete src/ dest/ # exact mirror (delete extras)
rsync -av --dry-run src/ dest/ # preview without copying
rsync -avz src/ remote:/dest/ # compress over network
rsync -avz --progress src/ remote:/dest/ # with progress bar
# ── Exclude patterns ──────────────────────────────────────
rsync -av --exclude='*.log' --exclude='.git' src/ dest/
rsync -av --exclude-from='.rsync-exclude' src/ dest/
# ── Backup script using rsync ─────────────────────────────
BACKUP_SRC="/app/data"
BACKUP_DST="backup@bkp-01:/backups/$(date +%Y/%m/%d)"
rsync -avz \
--delete \
--exclude='*.tmp' \
--exclude='cache/' \
--log-file=/var/log/rsync.log \
"${BACKUP_SRC}/" \
"${BACKUP_DST}/"
echo "Backup exit code: $?"
Terminal output
vriddh@prod-01:~/scripts$find /var/log -name "*.log" -mtime +7 -size +1M
/var/log/nginx/access.log.8
/var/log/myapp/app.log.12
vriddh@prod-01:~/scripts$stat -c "%s %U:%G %a %n" /etc/passwd
2741 root:root 644 /etc/passwd
vriddh@prod-01:~/scripts$tmpf=$(mktemp); echo "data" > "$tmpf"; echo $tmpf
/tmp/tmp.aX4kR92bPq
█
✔ File operation safety rules — Always use
mktemp for temp files, never /tmp/myfile.$$. Always trap cleanup EXIT to remove temp files. Never rm -rf without validating the path variable is non-empty. Use cp -a or rsync -av for backups to preserve metadata. Use find ... -print0 | xargs -0 for filenames with spaces.