#!/bin/bash # convert-size - convert file sizes between SI (1000) and binary (1024) systems # # Usage: # convert-size <-t|--to system> [-u|--unit unit] # convert-size -h _convert_size() ( local SCRIPT_NAME; SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" case "${BASH_SOURCE[0]}" in /dev/*|/proc/*) SCRIPT_NAME="" ;; esac case "$SCRIPT_NAME" in ""|bash|sh|zsh|dash) SCRIPT_NAME="convert-size" ;; esac _error() { echo "[ERR][$SCRIPT_NAME] $*" >&2; } _show_help() { local s; [ -t 1 ] && s=$'\033[4m' local r; [ -t 1 ] && r=$'\033[24m' echo "NAME" echo " $SCRIPT_NAME - convert file sizes between SI (1000) and binary (1024) systems" echo "SYNOPSIS" echo " $SCRIPT_NAME -t ${s}system${r} [-u ${s}unit${r}] ${s}size${r}" echo " $SCRIPT_NAME -h" echo "OPTIONS" echo " -t, --to ${s}system${r} Target number system. Values:" echo " SI: mac, macos, si, unix, nix" echo " Binary: win, windows, bin, binary" echo " -u, --unit ${s}unit${r} Override the unit suffix on ${s}size${r} (B, K, M, G, T)" echo " -h, --help Show this help message" echo "EXAMPLES" echo " $SCRIPT_NAME -t binary 500G # 500 GB -> binary GiB" echo " $SCRIPT_NAME -t binary 500 GB # same, space-separated unit" echo " $SCRIPT_NAME -t si 256M # 256 MiB -> SI MB" echo " $SCRIPT_NAME -t win -u G 1000 # 1000 (forced to G) -> binary GiB" echo "CAVEATS" echo " ${s}size${r} must be a positive integer (4700M is OK; 4.7G is not -- use 4700M instead)." echo "EXIT STATUS" echo " 0 Success" echo " 2 Usage error (bad/missing arg, duplicate flag, multiple sizes)" echo " 3 Dependency error (bc missing)" echo "DEPENDENCIES" echo " bc" } local unit local to local size _si_unit() { case "$1" in B) echo 1 ;; K) echo 1000 ;; M) echo 1000000 ;; G) echo 1000000000 ;; T) echo 1000000000000 ;; esac } _binary_unit() { case "$1" in B) echo 1 ;; K) echo 1024 ;; M) echo 1048576 ;; G) echo 1073741824 ;; T) echo 1099511627776 ;; esac } _expand_short_opts() { # $1 = string of short-opt letters that take a value (e.g. "nXHd"); "" for flag-only scripts # $2..$N = "$@" # Populates _EXPANDED; caller does: set -- "${_EXPANDED[@]}"; unset _EXPANDED local value_opts="$1"; shift _EXPANDED=() local passthru="" local arg local rest local c for arg in "$@"; do if [ -n "$passthru" ]; then _EXPANDED+=("$arg"); continue; fi case "$arg" in --) passthru=1; _EXPANDED+=("$arg") ;; --*|-|"") _EXPANDED+=("$arg") ;; -[a-zA-Z]?*) rest="${arg#-}" while [ -n "$rest" ]; do c="${rest%"${rest#?}"}"; rest="${rest#?}" _EXPANDED+=("-$c") case "$value_opts" in *"$c"*) [ -n "$rest" ] && _EXPANDED+=("$rest") rest="" ;; esac done ;; *) _EXPANDED+=("$arg") ;; esac done } # -h handled before the dep guard so help works without bc installed case "$1" in -h|--help) _show_help; return 0 ;; esac if ! command -v bc >/dev/null 2>&1; then _error "bc is required" return 3 fi _expand_short_opts "ut" "$@" set -- "${_EXPANDED[@]}"; unset _EXPANDED while [ $# -gt 0 ]; do case "$1" in -h|--help) _show_help return 0 ;; -u|--unit) [ "$unit" ] && { _error "The -u|--unit option is specified more than once. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shift unit="$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')" ;; --unit=*) [ "$unit" ] && { _error "The -u|--unit option is specified more than once. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } unit="${1#*=}" [ -n "$unit" ] || { _error "-u|--unit requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } unit="$(printf '%s' "$unit" | tr '[:lower:]' '[:upper:]')" ;; -t|--to) [ "$to" ] && { _error "The -t|--to option is specified more than once. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shift to="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" ;; --to=*) [ "$to" ] && { _error "The -t|--to option is specified more than once. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } to="${1#*=}" [ -n "$to" ] || { _error "-t|--to requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } to="$(printf '%s' "$to" | tr '[:upper:]' '[:lower:]')" ;; *) if [ "$size" ]; then # A second positional arg was supplied. Accept it only when the # first positional was purely numeric and the second is a valid # unit letter -- treat `convert-size 500 GB` as `500G` local second_upper; second_upper="$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')" local second_letter="${second_upper%B}" # allow GB -> G, KB -> K, etc [ -z "$second_letter" ] && second_letter="$second_upper" case "$size" in ''|*[!0-9]*) _error "Multiple size inputs provided. Run \`$SCRIPT_NAME -h\` for usage" return 2 ;; esac case "$second_letter" in B|K|M|G|T) size="${size}${second_letter}" ;; *) _error "Multiple size inputs provided. Run \`$SCRIPT_NAME -h\` for usage" return 2 ;; esac else size="$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')" fi ;; esac shift done if [ -z "$size" ] || [ -z "$to" ]; then _error "Must provide a target system and size. Run \`$SCRIPT_NAME -h\` for usage" return 2 fi # Separate size from unit local input_size="${size%[BKMGT]*}" local input_unit="${size:${#input_size}}" # -u flag overrides suffix; default to B if neither input_unit="${unit:-${input_unit:-B}}" case "$input_size" in ''|*[!0-9]*) _error "Invalid size: $input_size. Must be a positive integer"; return 2 ;; esac if [ -z "$(_si_unit "$input_unit")" ]; then _error "Invalid unit: $input_unit. Supported: B, K, M, G, T" return 2 fi local size_in_bytes local target_multiplier case "$to" in win|windows|bin|binary) size_in_bytes=$(bc <<< "scale=0; $input_size * $(_si_unit "$input_unit")") target_multiplier=$(_binary_unit "$input_unit") ;; mac|macos|si|unix|nix) size_in_bytes=$(bc <<< "scale=0; $input_size * $(_binary_unit "$input_unit")") target_multiplier=$(_si_unit "$input_unit") ;; *) _error "Invalid -t|--to '$to' (valid: mac, macos, si, unix, nix, win, windows, bin, binary). Run \`$SCRIPT_NAME -h\` for usage" return 2 ;; esac local converted_size; converted_size="$(bc <<< "scale=2; $size_in_bytes / $target_multiplier" \ | sed -E 's/(\.[0-9]*[1-9])0+$/\1/; s/\.0+$//; s/^\./0./')" echo "${converted_size}${input_unit}" ) _convert_size "$@" __convert_size_rc=$? unset -f _convert_size if [ -n "${BASH_SOURCE[0]}" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then eval "unset __convert_size_rc; return $__convert_size_rc" fi eval "unset __convert_size_rc; exit $__convert_size_rc"