How to Create a Bash File Sorting Tool

How to Create a Bash File Sorting Tool

How to Create a Bash File Sorting Tool

Sorting seems like a solved problem — everyone knows sort exists — until the actual requirement turns out to be “sort this CSV by the third column, numerically, but keep the header at the top” or “sort these files by modification time” or “sort a list of version numbers correctly instead of alphabetically.” The sort command handles all of this, but its many flags aren’t always intuitive, and a wrapper tool that exposes the common sorting needs clearly is worth having.

This article builds a Bash file sorting tool covering line sorting, field-based sorting, numeric and version-aware sorting, and file-level sorting by metadata.

Step 1: Basic Alphabetical Sorting

sort file.txt

By default, sort orders lines alphabetically (technically, based on the current locale’s collation order). Reversing:

sort -r file.txt

Step 2: Numeric Sorting

Plain alphabetical sort treats numbers as strings, so 10 sorts before 2 (since "1" < "2" character-by-character). Numeric sort fixes this:

sort -n numbers.txt
2
10
100

-n tells sort to interpret each line as a number for comparison purposes, rather than comparing character-by-character.

Step 3: Sorting by a Specific Field/Column

sort -t',' -k3,3n data.csv

This is one of the most commonly needed patterns and one of the least obvious from sort --help alone, so it’s worth building directly into a wrapper script.

Step 4: A Wrapper for Common Sort Patterns

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

usage() {
    cat <<EOF
Usage: $0 <file> [-f field_num] [-d delimiter] [-n (numeric)] [-r (reverse)] [-u (unique)] [-H (keep header)]
EOF
    exit 1
}

FILE="${1:-}"
[[ -z "$FILE" ]] && usage
shift

FIELD=""
DELIM=","
NUMERIC=false
REVERSE=false
UNIQUE=false
KEEP_HEADER=false

while getopts ":f:d:nruH" opt; do
    case "$opt" in
        f) FIELD="$OPTARG" ;;
        d) DELIM="$OPTARG" ;;
        n) NUMERIC=true ;;
        r) REVERSE=true ;;
        u) UNIQUE=true ;;
        H) KEEP_HEADER=true ;;
        *) usage ;;
    esac
done

[[ -f "$FILE" ]] || { echo "File not found: $FILE"; exit 1; }

SORT_ARGS=()
[[ -n "$FIELD" ]] && SORT_ARGS+=(-t"$DELIM" -k"${FIELD},${FIELD}")
[[ "$NUMERIC" == true ]] && SORT_ARGS+=(-n)
[[ "$REVERSE" == true ]] && SORT_ARGS+=(-r)
[[ "$UNIQUE" == true ]] && SORT_ARGS+=(-u)

if [[ "$KEEP_HEADER" == true ]]; then
    HEADER=$(head -n1 "$FILE")
    echo "$HEADER"
    tail -n +2 "$FILE" | sort "${SORT_ARGS[@]}"
else
    sort "${SORT_ARGS[@]}" "$FILE"
fi

Example:

./sort-file.sh sales.csv -f 4 -d ',' -n -r -H

This sorts sales.csv by the fourth comma-separated field, numerically, in reverse (largest first), while keeping the header row pinned at the top untouched.

How the Header-Preservation Logic Works

Step 5: Version-Aware Sorting

Sorting version strings like 1.2.10 and 1.2.9 alphabetically gives the wrong order (1.2.10 sorts before 1.2.9, since "1" < "9" character-by-character at that position). GNU sort has a dedicated flag for this:

sort -V versions.txt
1.2.9
1.2.10
1.2.11
2.0.0

-V (version sort) correctly interprets numeric components within strings, which is exactly what’s needed for changelogs, release tags, and file names containing version numbers.

Step 6: Sorting Files by Metadata (Not Content)

Sometimes the goal isn’t sorting lines within a file, but sorting a list of files themselves — by size, modification time, or name:

# By modification time, newest first
ls -t

# By size, largest first
ls -S

# Using find + sort for more control
find . -type f -printf '%T@ %p\n' | sort -rn | cut -d' ' -f2-

The find -printf '%T@ %p\n' pattern prints each file’s modification timestamp (as a Unix epoch number, %T@) followed by its path (%p), one per line. Piping through sort -rn sorts numerically by that leading timestamp, in reverse (newest first), and cut -d' ' -f2- strips the timestamp back off, leaving just the sorted file list. This is more portable and scriptable than relying on ls‘s sort flags, which can behave inconsistently across systems and aren’t meant to be parsed programmatically in the first place.

Step 7: Combining File-Level and Content-Level Sorting

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

DIR="$1"
CRITERION="${2:-mtime}"

case "$CRITERION" in
    mtime)
        find "$DIR" -type f -printf '%T@ %p\n' | sort -rn | cut -d' ' -f2-
        ;;
    size)
        find "$DIR" -type f -printf '%s %p\n' | sort -rn | cut -d' ' -f2-
        ;;
    name)
        find "$DIR" -type f -printf '%f %p\n' | sort -k1,1 | cut -d' ' -f2-
        ;;
    *)
        echo "Unknown criterion: $CRITERION (use mtime|size|name)"
        exit 1
        ;;
esac
./sort-files.sh ./downloads size

This lists every file under ./downloads, sorted largest-first by size, using the file’s byte count (%s) as the sort key.

Real-World Use Cases

Automation Example: Weekly Largest-Files Report

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

DIR="/var/data"
REPORT="/tmp/largest-files-$(date +%F).txt"

find "$DIR" -type f -printf '%s %p\n' | sort -rn | head -20 | \
    awk '{printf "%.2f MB\t%s\n", $1/1024/1024, $2}' > "$REPORT"

echo "Top 20 largest files written to $REPORT"

This finds every file, sorts by size descending, takes the top 20, then reformats the raw byte count into megabytes for readability using awk‘s printf-style formatting.

Best Practices

Security Considerations

Optimization Tips

Troubleshooting

Common Mistakes

FAQs

How do I sort in a locale-independent, byte-exact way? Prefix the command with LC_ALL=C: LC_ALL=C sort file.txt. This guarantees consistent, portable ordering regardless of the environment’s locale settings.

Can sort handle multiple sort keys (primary, then secondary)? Yes — repeat -k for each key in priority order: sort -k2,2 -k1,1n file.txt sorts primarily by field 2 (as a string), then by field 1 numerically as a tiebreaker.

Is sort -V the same as sort -n? No — -n treats the entire line as one number. -V specifically understands version-style strings with multiple numeric components separated by dots, correctly ordering things like 2.9 before 2.10.

Summary

Sorting is deceptively simple on the surface and full of small correctness traps underneath — string versus numeric comparison, header rows, version strings, and file-metadata sorting all need slightly different handling. A wrapper script that exposes these needs directly (rather than expecting every user to memorize sort‘s flag combinations) turns a frequently-misused command into a reliable, self-documenting tool.

References

Exit mobile version