File conversion is one of those tasks that seems trivial until it happens fifty times a week — images that need resizing and reformatting, documents bouncing between markdown and PDF, CSVs that need to become JSON. Every individual conversion has a well-known command behind it, but remembering all of them, and typing them correctly each time, is where a wrapper script pays for itself immediately.
This article builds a Bash file conversion tool that detects input and target formats, dispatches to the right underlying utility, and handles the practical edge cases that come up in daily use.
Why a Unified Conversion Script?
Different file types need entirely different tools: convert/magick for images, pandoc for documents, ffmpeg for audio/video, jq for JSON transforms. A single entry point script:
- Provides one consistent command (
convert-file input.md output.pdf) regardless of what’s actually running underneath. - Reduces the cognitive load of remembering tool-specific flags for occasional conversions.
- Makes it trivial to add new format pairs as needed.
- Fits naturally into batch-processing and automation pipelines.
Step 1: Detecting Source and Target Format
The simplest and most reliable approach is to infer format from file extensions on both sides:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$1"
OUTPUT="$2"
[[ -f "$INPUT" ]] || { echo "Input file not found: $INPUT"; exit 1; }
SRC_EXT="${INPUT##*.}"
DST_EXT="${OUTPUT##*.}"
echo "Converting .$SRC_EXT -> .$DST_EXT"
"${INPUT##*.}" is Bash parameter expansion that strips everything up to and including the last dot, leaving just the extension. This is a fast, dependency-free way to identify format without invoking an external tool.
Step 2: Image Conversion
ImageMagick’s convert (or magick on newer versions) handles most image format conversions:
convert_image() {
local src="$1" dst="$2"
convert "$src" "$dst"
}
Example:
./convert-file.sh photo.png photo.jpg
Internally this runs convert photo.png photo.jpg, and ImageMagick auto-detects both formats from their headers, re-encoding accordingly. Quality and resizing options can be layered in:
convert "$src" -resize 50% -quality 85 "$dst"
-resize 50%scales the image down by half.-quality 85sets JPEG compression quality (0–100 scale), trading file size against visual fidelity.
Step 3: Document Conversion with Pandoc
Pandoc is the standard tool for converting between markup and document formats — markdown, HTML, DOCX, PDF, and more:
convert_document() {
local src="$1" dst="$2"
pandoc "$src" -o "$dst"
}
./convert-file.sh report.md report.pdf
Pandoc infers both formats from file extensions by default, but explicit format flags avoid ambiguity in less common cases:
pandoc -f markdown -t pdf "$src" -o "$dst"
-f markdownsets the input format explicitly.-t pdfsets the output format explicitly.- PDF output specifically requires a working LaTeX installation (e.g.,
texlive) behind the scenes, since Pandoc renders PDFs via LaTeX by default.
Step 4: Audio/Video Conversion with FFmpeg
convert_media() {
local src="$1" dst="$2"
ffmpeg -i "$src" "$dst" -hide_banner -loglevel error -y
}
./convert-file.sh podcast.wav podcast.mp3
-i "$src"specifies the input file.-hide_banner -loglevel errorsuppresses FFmpeg’s normally verbose startup banner and progress logs, printing only actual errors — useful for clean script output.-yoverwrites the output file without an interactive confirmation prompt, which matters for non-interactive/automated use.
Step 5: Dispatching by Category
Putting it together with a category dispatcher based on extension:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$1"
OUTPUT="$2"
[[ -f "$INPUT" ]] || { echo "Input file not found: $INPUT"; exit 1; }
SRC_EXT="${INPUT##*.}"
DST_EXT="${OUTPUT##*.}"
IMAGE_EXTS="png jpg jpeg gif bmp tiff webp"
DOC_EXTS="md markdown html docx pdf odt rtf"
MEDIA_EXTS="mp3 wav flac ogg mp4 mkv avi mov"
is_in() {
local item="$1"; shift
for x in "$@"; do [[ "$x" == "$item" ]] && return 0; done
return 1
}
if is_in "$SRC_EXT" $IMAGE_EXTS && is_in "$DST_EXT" $IMAGE_EXTS; then
convert "$INPUT" "$OUTPUT"
elif is_in "$SRC_EXT" $DOC_EXTS && is_in "$DST_EXT" $DOC_EXTS; then
pandoc "$INPUT" -o "$OUTPUT"
elif is_in "$SRC_EXT" $MEDIA_EXTS && is_in "$DST_EXT" $MEDIA_EXTS; then
ffmpeg -i "$INPUT" "$OUTPUT" -hide_banner -loglevel error -y
else
echo "Error: unsupported or mismatched conversion: .$SRC_EXT -> .$DST_EXT"
exit 1
fi
echo "Converted: $INPUT -> $OUTPUT"
The is_in helper function checks whether a value exists in a space-separated list, used here to classify both the source and destination extensions into the same category before picking a tool — this prevents nonsensical calls like trying to feed an image into ffmpeg.
Step 6: Adding CSV/JSON Data Conversion
Data format conversion deserves its own branch, since tools like jq and csvkit (or a quick Python one-liner) don’t overlap with the categories above:
elif [[ "$SRC_EXT" == "csv" && "$DST_EXT" == "json" ]]; then
python3 -c "
import csv, json, sys
with open('$INPUT') as f:
reader = csv.DictReader(f)
data = list(reader)
with open('$OUTPUT', 'w') as f:
json.dump(data, f, indent=2)
"
elif [[ "$SRC_EXT" == "json" && "$DST_EXT" == "csv" ]]; then
python3 -c "
import csv, json
with open('$INPUT') as f:
data = json.load(f)
if data:
with open('$OUTPUT', 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
"
This uses python3 -c as a lightweight embedded scripting escape hatch — Bash itself has no native structured data handling, so leaning on Python for this specific pair is more reliable than attempting a pure awk/sed transformation, which gets fragile fast with quoted CSV fields.
Real-World Use Cases
- Batch image optimization: Convert an entire folder of PNG screenshots to compressed WebP for a website.
- Document pipeline: Convert markdown release notes to both PDF (for archival) and HTML (for a changelog page) in one pass.
- Podcast processing: Convert raw WAV recordings to MP3 for distribution, with consistent bitrate settings.
- Data interchange: Convert exported CSV reports to JSON for ingestion into a web application.
Automation Example: Batch Convert a Directory
#!/usr/bin/env bash
set -euo pipefail
SRC_DIR="$1"
SRC_EXT="$2"
DST_EXT="$3"
for file in "$SRC_DIR"/*."$SRC_EXT"; do
[[ -f "$file" ]] || continue
OUT="${file%.*}.$DST_EXT"
./convert-file.sh "$file" "$OUT"
done
Usage: ./batch-convert.sh ./photos png webp converts every PNG in the folder to WebP, preserving the base filename.
Best Practices
- Always validate that the input file exists and is readable before invoking a conversion tool, to fail fast with a clear message rather than a cryptic tool-specific error.
- Use
-y/--overwrite-equivalent flags carefully — auto-overwriting is convenient in scripts but dangerous if run against the wrong target by mistake; consider a--forceflag on the wrapper itself to make overwriting an explicit choice. - Preserve original files by default; only delete or overwrite sources when the user explicitly asks for it.
- Pin down encoding/quality settings for lossy conversions rather than leaving every default to chance, so repeated runs produce consistent, predictable output.
Security Considerations
- Untrusted input files: Media and document converters like FFmpeg, Pandoc, and ImageMagick have all had parser vulnerabilities historically. Avoid running conversions on completely untrusted files as a privileged user, and keep these tools updated.
- Command injection via filenames: Always quote variables (
"$INPUT","$OUTPUT") — an attacker-controlled filename containing shell metacharacters could otherwise inject commands if interpolated unquoted. - ImageMagick policy restrictions: Modern ImageMagick installations ship with a
policy.xmlthat disables certain risky format conversions (like PDF rendering) by default due to past vulnerabilities (e.g., “ImageTragick”); don’t casually loosen this policy without understanding why it’s there.
Optimization Tips
- For batch image conversions,
mogrify(ImageMagick’s in-place batch variant) can be faster than loopingconvertcalls individually, though it modifies files in place — use with a copy of the originals. - FFmpeg supports hardware-accelerated encoding (
-hwaccel) on supported systems, dramatically speeding up video conversions. - Parallelize batch conversions with
xargs -Por GNUparallelwhen converting many independent files:find . -name '*.png' | xargs -P4 -I{} convert {} {}.webp.
Troubleshooting
- “convert: not authorized” from ImageMagick: Usually a
policy.xmlrestriction blocking a specific format (often PDF); check/etc/ImageMagick-6/policy.xmlor the equivalent path for the installed version. - Pandoc PDF conversion fails with LaTeX errors: A LaTeX distribution (e.g.,
texlive-latex-base) is likely missing or incomplete; install the full recommended package set for the target OS. - FFmpeg “Unknown encoder” errors: The installed FFmpeg build may lack a specific codec; check
ffmpeg -encodersto confirm availability, or install a build with more codecs enabled.
Common Mistakes
- Assuming every format pair is supported by every tool — not every conversion path exists, and the wrapper should fail clearly rather than attempt a nonsensical call.
- Forgetting
-y/non-interactive flags in automated contexts, causing a script to hang waiting for a confirmation prompt that will never come. - Not accounting for lossy re-encoding — repeatedly converting between lossy formats (e.g., JPEG to JPEG through several steps) degrades quality cumulatively.
FAQs
Can this handle converting an entire batch with mixed formats? Yes, by looping over files and letting the extension-based dispatcher route each one to the correct tool individually.
Why use Pandoc instead of a direct DOCX-to-PDF library? Pandoc already handles a huge range of document format pairs consistently and is actively maintained, avoiding the need to individually wire up separate libraries for each format combination.
Is a GUI-free environment (server, container) suitable for this? Mostly yes — convert, ffmpeg, and pandoc are all fully usable headless; only some obscure format edge cases assume a display server, which is uncommon for the formats covered here.
Summary
A unified file conversion script turns a scattered set of tool-specific commands into one predictable interface. By classifying formats into categories (image, document, media, data) and dispatching to the right underlying tool — ImageMagick, Pandoc, FFmpeg, or a small Python snippet for structured data — the script scales cleanly from one-off conversions to full batch-processing pipelines.