How to Create a Bash File Unarchiving Tool

How to Create a Bash File Unarchiving Tool

How to Create a Bash File Unarchiving Tool

Anyone who’s spent time at a terminal has run into the same small annoyance: a .tar.gz here, a .zip there, a .7z from a colleague, and each one demands a slightly different command with slightly different flags. Remembering tar -xzf versus unzip versus 7z x isn’t hard individually, but it adds friction. The fix is a single Bash script that detects the archive type and extracts it correctly — a universal unarchiving tool.

This article builds that tool step by step, explains the mechanics of archive detection, and covers the practical considerations that turn a toy script into something reliable enough for daily use.

Why a Universal Unarchiving Script Is Worth Building

Most people reach for whatever tool they remember first, then look up the syntax when it doesn’t work. A wrapper script solves this permanently:

Step 1: Detecting Archive Type

The naive approach is to check the file extension with a case statement. It’s simple and works for the overwhelming majority of real-world files:

#!/usr/bin/env bash
set -euo pipefail

FILE="$1"

if [[ ! -f "$FILE" ]]; then
    echo "Error: '$FILE' not found."
    exit 1
fi

case "$FILE" in
    *.tar.gz|*.tgz)   tar -xzf "$FILE" ;;
    *.tar.bz2|*.tbz2) tar -xjf "$FILE" ;;
    *.tar.xz)         tar -xJf "$FILE" ;;
    *.tar)            tar -xf  "$FILE" ;;
    *.zip)            unzip "$FILE" ;;
    *.rar)            unrar x "$FILE" ;;
    *.7z)             7z x "$FILE" ;;
    *.gz)             gunzip "$FILE" ;;
    *.bz2)            bunzip2 "$FILE" ;;
    *)
        echo "Error: unsupported archive format for '$FILE'"
        exit 1
        ;;
esac

echo "Extracted: $FILE"

How This Works

Save this as unpack.sh, make it executable, and test it:

chmod +x unpack.sh
./unpack.sh project-backup.tar.gz

Output:

Extracted: project-backup.tar.gz

Step 2: Detecting by Content, Not Just Extension

Extensions can lie — a renamed file, a download with no extension, or a mislabeled upload. The file command inspects the actual byte signature (magic numbers) to identify format regardless of name:

detect_type() {
    file --brief --mime-type "$1"
}

TYPE=$(detect_type "$FILE")

case "$TYPE" in
    application/gzip)        tar -xzf "$FILE" 2>/dev/null || gunzip -k "$FILE" ;;
    application/x-bzip2)     tar -xjf "$FILE" 2>/dev/null || bunzip2 -k "$FILE" ;;
    application/x-xz)        tar -xJf "$FILE" 2>/dev/null || unxz -k "$FILE" ;;
    application/x-tar)       tar -xf "$FILE" ;;
    application/zip)         unzip "$FILE" ;;
    application/x-rar)       unrar x "$FILE" ;;
    application/x-7z-compressed) 7z x "$FILE" ;;
    *)
        echo "Unknown archive type: $TYPE"
        exit 1
        ;;
esac

Here, tar -xzf is attempted first for gzip files because a .gz-compressed file could either be a plain gzipped file or a gzipped tarball; falling back to plain gunzip handles the non-tar case. This dual-detection approach (extension first, MIME type as a fallback or verification) is considerably more robust.

Step 3: Combining Extension and MIME Detection

The most reliable script uses extension matching as the fast path, and falls back to file-based detection when the extension is missing or ambiguous:

#!/usr/bin/env bash
set -euo pipefail

FILE="$1"
DEST="${2:-.}"

[[ ! -f "$FILE" ]] && { echo "File not found: $FILE"; exit 1; }
mkdir -p "$DEST"

extract_by_extension() {
    case "$1" in
        *.tar.gz|*.tgz)   tar -xzf "$1" -C "$2" ;;
        *.tar.bz2|*.tbz2) tar -xjf "$1" -C "$2" ;;
        *.tar.xz)         tar -xJf "$1" -C "$2" ;;
        *.tar)            tar -xf  "$1" -C "$2" ;;
        *.zip)            unzip -q "$1" -d "$2" ;;
        *.rar)            unrar x -inul "$1" "$2" ;;
        *.7z)             7z x -o"$2" "$1" > /dev/null ;;
        *.gz)             gunzip -k "$1" ;;
        *.bz2)            bunzip2 -k "$1" ;;
        *) return 1 ;;
    esac
}

if extract_by_extension "$FILE" "$DEST"; then
    echo "Extracted '$FILE' to '$DEST' (by extension)"
    exit 0
fi

TYPE=$(file --brief --mime-type "$FILE")
echo "Extension unrecognized, detected MIME type: $TYPE"

case "$TYPE" in
    application/zip)             unzip -q "$FILE" -d "$DEST" ;;
    application/x-tar)           tar -xf "$FILE" -C "$DEST" ;;
    application/gzip)            tar -xzf "$FILE" -C "$DEST" 2>/dev/null || gunzip -k "$FILE" ;;
    application/x-7z-compressed) 7z x -o"$DEST" "$FILE" > /dev/null ;;
    *)
        echo "Unsupported or unknown archive type: $TYPE"
        exit 1
        ;;
esac

echo "Extracted '$FILE' to '$DEST'"

The -C "$DEST" flag tells tar to change to the destination directory before extracting, avoiding a separate cd and keeping the current working directory untouched — an important detail for scripts run in automation pipelines where the caller’s working directory shouldn’t shift unexpectedly.

Step 4: Handling Nested and Multi-Part Archives

Some archives are compressed twice (.tar.gz is really “tar, then gzip”) and some are split into multiple parts (.tar.gz.001, .rar multi-volume sets). A more advanced version can loop:

extract_recursive() {
    local file="$1"
    local dest="$2"
    extract_by_extension "$file" "$dest"

    # If exactly one file was extracted and it's still an archive, recurse
    local contents
    contents=$(find "$dest" -maxdepth 1 -type f)
    local count
    count=$(echo "$contents" | wc -l)

    if [[ "$count" -eq 1 ]] && file --brief "$contents" | grep -qiE 'archive|compressed'; then
        echo "Nested archive detected: $contents"
        extract_recursive "$contents" "$dest"
    fi
}

This checks whether the extraction left behind a single file that itself looks like an archive, and if so, extracts again — useful for double-compressed downloads.

Real-World Use Cases

Automation Example: Batch Unpacking a Directory

#!/usr/bin/env bash
set -euo pipefail

for archive in ./downloads/*; do
    [[ -f "$archive" ]] || continue
    ./unpack.sh "$archive" "./extracted/$(basename "$archive" | sed 's/\.[^.]*$//')"
done

This loops through every file in ./downloads, extracting each into its own subdirectory under ./extracted, named after the archive (extension stripped).

Best Practices

Security Considerations

Optimization Tips

Troubleshooting

Common Mistakes

FAQs

Can this handle password-protected archives? Yes, with modification — pass -P password to unzip or prompt interactively; avoid hardcoding passwords in scripts.

What about .tar.zst (Zstandard)? Add a case for *.tar.zst using tar --zstd -xf, assuming zstd is installed.

Does this work on macOS? Mostly yes; tar, gzip, and unzip ship by default, though 7z and unrar need to be installed separately via Homebrew.

Summary

A universal unarchiving tool eliminates one of the most repetitive small tasks at the command line. By combining extension-based fast detection with MIME-type fallback via the file command, the script handles the vast majority of real-world archives reliably, while staying simple enough to extend for new formats as they come up.

References

Exit mobile version