Skip to content

Instantly share code, notes, and snippets.

@ifthenelse
Last active December 22, 2025 10:47
Show Gist options
  • Select an option

  • Save ifthenelse/4fdd962f92258c8c5c1c7e559c5ee40c to your computer and use it in GitHub Desktop.

Select an option

Save ifthenelse/4fdd962f92258c8c5c1c7e559c5ee40c to your computer and use it in GitHub Desktop.
ZSH script to unlock all PDF files in the current directory using a single password
#!/usr/bin/env zsh
set -u
setopt extendedglob nullglob
SCRIPT_NAME=${0:t}
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [options] [input_dir]
Unlock all PDF files in input_dir (or current directory if omitted).
Unlocked files are written to: input_dir/unlocked/
Options:
-i, --input DIR Input directory (optional; can also be given positionally)
-h, --help Show this help message and exit
Requirements:
- zsh
- qpdf
Notes:
- Matching is case-insensitive (*.pdf, *.PDF, ...)
- Only regular files are processed
- Already-unlocked PDFs are skipped
- Existing output files are not overwritten
EOF
}
# --- Parse args ---
indir="."
while (( $# > 0 )); do
case "$1" in
-h|--help)
usage
exit 0
;;
-i|--input)
shift
if [[ -z "${1:-}" ]]; then
echo "Error: --input requires a directory path."
exit 1
fi
indir="$1"
shift
;;
--) # end of options
shift
break
;;
-*)
echo "Unknown option: $1"
echo
usage
exit 1
;;
*)
# positional input dir (only one supported)
indir="$1"
shift
;;
esac
done
# --- Validate input directory ---
if [[ ! -d "$indir" ]]; then
echo "Error: input directory not found: $indir"
exit 1
fi
# --- Dependency check ---
if ! command -v qpdf >/dev/null 2>&1; then
echo "Error: qpdf is not installed or not in PATH."
echo "Install it with:"
echo " macOS (brew): brew install qpdf"
echo " Debian/Ubuntu: sudo apt install qpdf"
exit 1
fi
# --- Output directory is unlocked subdir of input dir ---
outdir="$indir/unlocked"
mkdir -p -- "$outdir"
# --- Collect PDFs from input dir (case-insensitive, regular files only) ---
pdfs=( "$indir"/*.(#i)pdf(.N) )
if (( ${#pdfs} == 0 )); then
echo "No PDF file found in: $indir"
exit 0
fi
echo "Input directory: $indir"
echo "Output directory: $outdir"
echo "Found ${#pdfs} PDF file(s):"
for f in "${pdfs[@]}"; do
print -- " ${f:t}"
done
echo
# --- Prompt for password ---
read -rs "PW?Password: "
echo
skipped=0
unlocked=0
failed=0
# --- Process each PDF ---
for f in "${pdfs[@]}"; do
out="$outdir/${f:t:r}_unlocked.pdf"
encinfo="$(qpdf --show-encryption "$f" 2>/dev/null || true)"
if [[ "$encinfo" == *"File is not encrypted"* ]]; then
echo "Skipping (already unlocked): ${f:t}"
((skipped++))
continue
fi
if [[ -f "$out" ]]; then
echo "Skipping (output already exists): ${f:t} -> ${out:t}"
((skipped++))
continue
fi
echo "Unlocking: ${f:t} -> ${out:t}"
if qpdf --password="$PW" --decrypt "$f" "$out" 2>/dev/null; then
((unlocked++))
else
echo "Failed to unlock: ${f:t}"
rm -f -- "$out" 2>/dev/null || true
((failed++))
fi
done
echo
echo "Outcome:"
echo " Unlocked: $unlocked"
echo " Skipped : $skipped"
echo " Failed : $failed"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment