How to Create a Bash File Conversion Tool

How to Create a Bash File Conversion Tool

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 85 sets 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 markdown sets the input format explicitly.
  • -t pdf sets 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 error suppresses FFmpeg’s normally verbose startup banner and progress logs, printing only actual errors — useful for clean script output.
  • -y overwrites 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 --force flag 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.xml that 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 looping convert calls 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 -P or GNU parallel when converting many independent files: find . -name '*.png' | xargs -P4 -I{} convert {} {}.webp.

Troubleshooting

  • “convert: not authorized” from ImageMagick: Usually a policy.xml restriction blocking a specific format (often PDF); check /etc/ImageMagick-6/policy.xml or 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 -encoders to 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.

References

Total
1
Shares

Leave a Reply

Previous Post
How to Create a Bash File Formatting Tool

How to Create a Bash File Formatting Tool

Next Post
How to Create a Bash File Encryption Utility

How to Create a Bash File Encryption Utility

Related Posts