encrypt-file
Dissection of the encrypt-file wrapper script — bash patterns for RHCSA study: strict mode, file tests, argument handling, arithmetic edge cases, regex matching, and secure deletion.
What encrypt-file Does
A wrapper around age -e that handles the common workflow: encrypt one or more files, append .age, optionally shred originals. Located at ~/.secrets/bin/encrypt-file, found via PATH.
encrypt-file secret.txt # → secret.txt.age
encrypt-file file1.txt file2.txt file3.txt # Batch encrypt
Script Dissection
Each section below maps to a bash concept worth learning.
Strict Mode
#!/bin/bash
set -euo pipefail
| Flag | Meaning |
|---|---|
|
Exit immediately on any command failure (non-zero exit) |
|
Treat unset variables as errors (prevents silent bugs from typos) |
|
Pipe fails if ANY stage fails, not just the last |
set -e has edge cases. Arithmetic count))` returns 1 when count is 0, triggering exit. The script handles this with `((encrypted_count \|\| true.
|
Argument Validation
if [ $# -lt 1 ]; then
echo "Usage: encrypt-file <file1> [file2] [file3] ..."
exit 1
fi
| Element | Concept |
|---|---|
|
Number of positional parameters passed to the script |
|
All positional parameters as separate words (used later in |
|
Individual positional parameters |
|
Non-zero exit = failure (convention: 1 = general error, 2 = usage error) |
File Test Operators
if [ ! -f "$PUBKEY" ]; then
echo "✗ Public key not found: $PUBKEY"
exit 1
fi
| Test | Meaning |
|---|---|
|
True if file exists AND is a regular file |
|
True if directory exists |
|
True if path exists (any type) |
|
True if readable |
|
True if writable |
|
True if executable |
|
Negation — true if file does NOT exist |
For RHCSA: always quote "$variable" inside [ ] to prevent word splitting on paths with spaces.
|
Looping Over Arguments
for INPUT in "$@"; do
# Skip if file doesn't exist
if [ ! -f "$INPUT" ]; then
echo "✗ Skipping: $INPUT (file not found)"
continue
fi
# Skip if already an .age file
if [[ "$INPUT" == *.age ]]; then
echo "✗ Skipping: $INPUT (already encrypted)"
continue
fi
...
done
| Element | Concept |
|---|---|
|
All arguments, individually quoted (preserves spaces in filenames) |
|
All arguments as ONE string (rarely what you want) |
|
Skip to next iteration (like Python’s |
|
Glob pattern match — |
|
|
String Manipulation
OUTPUT="$INPUT.age"
Simple concatenation. More complex patterns use parameter expansion:
"${INPUT%.txt}.age" # Strip .txt suffix, append .age
"${INPUT##*/}" # Strip path prefix (basename)
"${INPUT%/*}" # Strip filename (dirname)
The Core Encryption
age --encrypt -r "$(cat "$PUBKEY")" -o "$OUTPUT" "$INPUT"
| Element | Concept |
|---|---|
|
Command substitution — reads the public key file and inlines its content as an argument |
|
Recipient — the public key string (not a file path) |
|
Recipients file — reads keys from a file (alternative approach) |
|
Output file path |
|
Input file to encrypt (last argument) |
The script uses -r "$(cat …)" to pass the key as a string. The age codex entry shows -R file which reads the file directly — equivalent but cleaner.
|
Arithmetic with set -e
((encrypted_count++)) || true
Arithmetic expansion returns exit code 1 when the result is 0. With set -e active, count++ when count=0 would kill the script. The || true absorbs the false exit.
encrypted_count=$((encrypted_count + 1)) # Assignment always succeeds
let "encrypted_count+=1" || true # Same issue as (())
: $((encrypted_count++)) # : is no-op, absorbs exit code
Interactive Confirmation
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
...
fi
| Element | Concept |
|---|---|
|
Read one line from stdin. |
|
Regex match operator (only inside |
|
Regex: exactly one character, either Y or y |
|
|
Secure Deletion
shred -u "$INPUT"
| Flag | Meaning |
|---|---|
|
Overwrite file content with random data before unlinking |
|
Unlink (delete) the file after overwriting |
|
Number of overwrite passes (default: 3). The |
shred is less effective on journaling filesystems (ext4, btrfs) and SSDs with wear leveling. For SSD/NVMe, the data may persist in spare cells. For true security on SSDs, use LUKS full-disk encryption so plaintext never hits raw flash.
|
How encrypt-file Gets Into PATH
The script lives at ~/.secrets/bin/encrypt-file. The shell finds it because ~/.secrets/bin is in $PATH (set in .zshrc or .bashrc).
# Verify
type encrypt-file
# → encrypt-file is /home/evanusmodestus/.secrets/bin/encrypt-file
echo "$PATH" | tr ':' '\n' | grep secrets
# → /home/evanusmodestus/.secrets/bin
See PATH Resolution for the full breakdown of which, type, aliases, and when PATH resolution breaks.