Scripting Kit Error Codes Shell Exit Codes View Error Code Declarations Script # ------------------------------------------------ # EXIT CODES # ------------------------------------------------ EXIT_SUCCESS=0 EXIT_FAILURE=1 EXIT_USAGE=2 EXIT_DATAERR=65 EXIT_NOINPUT=66 EXIT_NOUSER=67 EXIT_NOHOST=68 EXIT_UNAVAILABLE=69 EXIT_SOFTWARE=70 EXIT_OSERR=71 EXIT_OSFILE=72 EXIT_CANTCREATE=73 EXIT_IOERR=74 EXIT_TEMPFAIL=75 EXIT_PROTOCOL=76 EXIT_NOPERM=77 EXIT_CONFIG=78 EXIT_QUIT=80 EXIT_KYC=81 EXIT_UPDATE=89 EXIT_CONFLICT=90 EXIT_UNLAWFUL=91 EXIT_PAYMENT_ISSUE=92 EXIT_QUOTA_ISSUE=93 EXIT_BUSY=100 EXIT_TIMEOUT=101 EXIT_LOCKOUT=102 EXIT_LOOP=103 EXIT_MOVED_PERMANENTLY=110 EXIT_MOVED_TEMPORARILY=111 EXIT_GONE=112 EXIT_FUTURE=119 EXIT_GIT_BISECT_SKIP=125 EXIT_COMMAND_FOUND_BUT_NOT_EXECUTABLE=126 EXIT_COMMAND_NOT_FOUND=127 EXIT_CODE_INVALID=128 Input Output Helpers out(): Prints output message to stdout using printf for consistency across systems. err(): Prints error messages to stderr, using printf for better control over formatting. die(): Prints an error message to stderr and exits with a specified error code. big(): Prints a large banner to stdout, formatted for clear visibility and human readability. log(): Logs a message with a datestamp, unique random ID, hostname, and process ID. zid(): Generates a 32-bit secure random lowercase hexadecimal identifier. ask(): Prompts the user for input and returns a trimmed string. View Input Output Helper Functions Script out() { printf %s\\n "$*" } err() { >&2 printf %s\\n "$*" } die() { n="$1" ; shift ; >&2 printf %s\\n "$*" ; exit "$n" } big() { printf \\n###\\n#\\n#\ %s\\n#\\n###\\n\\n "$*" } log() { printf '%s %s %s %s\n' "$( now )" "$( zid )" "$( hostname )" $$ "$*" } zid() { hexdump -n 16 -v -e '16/1 "%02x" "\n"' /dev/random } ask() { read x ; echo "$x" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' } Date-Time Helpers now(): Gets the current date and time in UTC in ISO format with nanosecond precision. now_date(): Retrieves the current date in UTC using ISO format YYYY-MM-DD. sec(): Returns the current time in POSIX seconds since the Unix epoch. age(): Calculates the age in seconds of a given timestamp from the current time. newer(): Checks if the age of a given timestamp is newer (less) than a specified number of seconds. older(): Determines if the age of a given timestamp is older (more) than a specified number of seconds. View Date-Time Helpers Script now() { date -u "+%Y-%m-%dT%H:%M:%S.%N+00:00" "$@" | sed 's/N/000000000/' } now_date() { date -u "+%Y-%m-%d" "$@" } sec() { date "+%s" "$@" } age() { printf %s\\n "$(( $(date "+%s") - $1 ))" } newer() { [ "$(( $(date "+%s") - $1 ))" -lt "$2" ] } older() { [ "$(( $(date "+%s") - $1 ))" -gt "$2" ] } Todo ## # Validation helpers ## # directory_exists: does a directory exist? # # Example: # ``` # directory_exists /usr # => true # # directory_exists /loremipsum # => false # ``` directory_exists() { test -d "$1" } # directory_exists_or_die: ensure a directory exists. # # Example: # ``` # directory_exists_or_die /usr # => true # # directory_exists_or_die /loremipsum # STDERR=> Directory needed: /loremipsum # => exit $EXIT_IOERR # ``` directory_exists_or_die() { directory_exists "$1" || die "$EXIT_IOERR" "Directory needed: $1" } # file_exists: does a file exist? # # Example: # ``` # file_exists foo.txt # => true # # file_exists loremipsum.txt # => false # ``` file_exists() { test -f "$1" } # file_exists_or_die: ensure a file exists. # # Example: # ``` # file_exists_or_die foo.txt # => true # # file_exists_or_die loremipsum.txt # STDERR=> File needed: loremipsum.txt # => exit $EXIT_IOERR # ``` file_exists_or_die() { file_exists "$1" || die "$EXIT_IOERR" "File needed: $1" } # symlink_exists: does a symlink exist? # # Example: # ``` # symlink_exists foo.txt # => true # # symlink_exists loremipsum.txt # => false # ``` symlink_exists() { test -h "$1" } # symlink_exists_or_die: ensure a symlink exists. # # Example: # ``` # symlink_exists_or_die foo.txt # => true # # symlink_exists_or_die loremipsum.txt # STDERR=> Symlink needed: loremipsum.txt # => exit $EXIT_IOERR # ``` symlink_exists_or_die() { symlink_exists "$1" || die "$EXIT_IOERR" "Symlink needed: $1" } # command_exists: does a command exist? # # Example: # ``` # command_exists grep # => true # # command_exists curl # => false # ``` command_exists() { command -v "$1" >/dev/null 2>&1 } # command_exists_or_die: ensure a command exists. # # Example: # ``` # command_exists_or_die grep # => true # # command_exists_or_die loremipsum # STDERR=> Command needed: loremipsum # => exit 1 # ``` command_exists_or_die() { command_exists "$1" || die "$EXIT_UNAVAILABLE" "Command needed: $1" } # command_version_exists_or_die: ensure a command version exists. # # Example: # ``` # command_version_exists_or_die grep 2.2 1.1 # => true # # version_or_die grep 2.2 3.3 # STDERR=> Command version needed: grep >= 3.x # => exit 1 # ``` command_version_exists_or_die() { command_exists "$1" && version "$2" "$3" || die "$EXIT_UNAVAILABLE" "Command version needed: $1 >= $2 (not ${3:-?})" } # var_exists: does a variable exist? # # Example: # ``` # var_exists HOME # => true # # var_exists FOO # => false # ``` var() { ! eval 'test -z ${'$1'+x}' } # var_exists_or_die: ensure a variable exists. # # Example: # ``` # var_exists_or_die HOME # => true # # var_exists_or_die FOO # STDERR=> Variable needed: FOO # => exit 1 # ``` var_exists_or_die() { var_exists "$1" || die "$EXIT_CONFIG" "Variable needed: $1" } # version: is a version sufficient? # # Example: # ``` # version 1.1 2.2 # => true # # version 3.3 2.2 # => false # ``` version() { [ "$(cmp_digits "$1" "$2")" -le 0 ] } # version_or_die: ensure a version is sufficient. # # Example: # ``` # version_or_die 1.1 2.2 # => true # # version_or_die 3.3 2.2 # STDERR=> Version needed: >= 3.3 (not 2.2) # ``` version_or_die() { version "$1" "$2" || die "$EXIT_CONFIG" "Version needed: >= $1 (not ${2:-?})" } ## # Number helpers ## # int: convert a number string to an integer number string. # # Example: # ``` # int 1.23 # => 1 # ``` int() { printf %s\\n "$1" | awk '{ print int($0); exit }' } # sum: print the sum of numbers. # # Example: # ``` # sum 1 2 3 # => 6 # ``` sum() { awk '{for(i=1; i<=NF; i++) sum+=$i; } END {print sum}' } ## # Comparison helpers ## # cmp_alnums: compare alnums as groups, such as for word version strings. # # Example: # # ``` # cmp_alnums "a.b.c" "a.b.c" # => 0 (zero means left == right) # # cmp_alnums "a.b.c" "a.b.d" # => -1 (negative one means left < right) # # cmp_alnums "a.b.d" "a.b.c" # => 1 (positive one means left > right) # ``` # cmp_alnums() { if [ "$1" = "$2" ]; then echo "0"; return 0 fi a=$(printf %s\\n "$1" | sed 's/^[^[:alnum:]]*//') b=$(printf %s\\n "$2" | sed 's/^[^[:alnum:]]*//') while true; do x=$(printf %s\\n "$a" | sed 's/[^[:alnum:]].*//') y=$(printf %s\\n "$b" | sed 's/[^[:alnum:]].*//') if [ "$x" = "" ] && [ "$y" = "" ]; then echo "0"; return 0 fi if [ "$x" = "" ] || [ "$(expr "$x" \< "$y")" = 1 ]; then echo "-1"; return 0 fi if [ "$y" = "" ] || [ "$(expr "$x" \> "$y")" = 1 ]; then echo "1"; return 0 fi a=$(printf %s\\n "$a" | sed 's/^[[:alnum:]]*[^[:alnum:]]*//') b=$(printf %s\\n "$b" | sed 's/^[[:alnum:]]*[^[:alnum:]]*//') done } # cmp_digits: compare digits as groups, such as for numeric version strings. # # Example: # # ``` # cmp_digits 1.2.3 1.2.3 # => 0 (zero means left == right) # # cmp_digits 1.2.3 1.2.4 # => -1 (negative one means left < right) # # cmp_digits 1.2.4 1.2.3 # => 1 (positive one means left > right) # ``` # cmp_digits() { if [ "$1" = "$2" ]; then echo "0"; return 0 fi a=$(printf %s\\n "$1" | sed 's/^[^[:digit:]]*//') b=$(printf %s\\n "$2" | sed 's/^[^[:digit:]]*//') while true; do x=$(printf %s\\n "$a" | sed 's/[^[:digit:]].*//') y=$(printf %s\\n "$b" | sed 's/[^[:digit:]].*//') if [ "$x" = "" ] && [ "$y" = "" ]; then echo "0"; return 0 fi if [ "$x" = "" ] || [ $x -lt $y ]; then echo "-1"; return 0 fi if [ "$y" = "" ] || [ $x -gt $y ]; then echo "1"; return 0 fi a=$(printf %s\\n "$a" | sed 's/^[[:digit:]]*[^[:digit:]]*//') b=$(printf %s\\n "$b" | sed 's/^[[:digit:]]*[^[:digit:]]*//') done } ## # Extensibility helpers ## # dot_all: source all the executable files in a given directory and subdirectories. # # Example: # ``` # dot_all ~/temp # => . ~/temp/a.sh # => . ~/temp/b.pl # => . ~/temp/c.js # ``` dot_all() { find "${1:-.}" -type f \( -perm -u=x -o -perm -g=x -o -perm -o=x \) -exec test -x {} \; -exec . {} \; } # run_all: run all the executable commands in a given directory and subdirectories. # # Example: # ``` # run_all ~/temp # => ~/temp/a.sh # => ~/temp/b.pl # => ~/temp/c.js # ``` run_all() { find "${1:-.}" -type f \( -perm -u=x -o -perm -g=x -o -perm -o=x \) -exec test -x {} \; -exec {} \; } # sh_all: shell all the executable commands in a given directory and subdirectories. # # Example: # ``` # sh_all ~/temp # => sh -c ~/temp/a.sh # => sh -c ~/temp/b.pl # => sh -c ~/temp/c.js # ``` sh_all() { find "${1:-.}" -type f \( -perm -u=x -o -perm -g=x -o -perm -o=x \) -exec test -x {} \; -print0 | xargs -0I{} -n1 sh -c "{}" } # rm_all: remove all files in a given directory and subdirectories-- use with caution. # # Example: # ``` # rm_all ~/temp # => rm ~/temp/a.sh # => rm ~/temp/b.pl # => rm ~/temp/c.js # ``` rm_all() { find "${1:-.}" -type f -exec rm {} \; } ## # Text helpers ## # trim: remove any space characters at the text's start or finish. # # Example: # ``` # trim " foo " # => foo #``` trim() { printf %s\\n "$*" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' } # slug: convert a string from any characters to solely lowercase and single internal dash characters. # # Example: # ``` # slug "**Foo** **Goo** **Hoo**" # => foo-goo-hoo #``` slug() { printf %s\\n "$*" | sed 's/[^[:alnum:]]/-/g; s/--*/-/g; s/^-*//; s/-*$//;' | tr '[[:upper:]]' '[[:lower:]]' } # slugs: convert a string from any characters to solely lowercase and single internal dash characters and slash characters. # # Example: # ``` # slugs "**Foo** / **Goo** / **Hoo**" # => foo/goo/hoo #``` slugs(){ printf %s\\n "$*" | sed 's/[^[:alnum:]\/]/-/g; s/--*/-/g; s/^-*//; s/-*$//; s/-*\/-*/\//g' | tr '[[:upper:]]' '[[:lower:]]' } # upper_format: convert text from any lowercase letters to uppercase letters. # # Example: # ``` # upper_format AbCdEf # => ABCDEF #``` upper_format() { printf %s\\n "$*" | tr '[[:lower:]]' '[[:upper:]]' } # lower_format: convert text from any uppercase letters to lowercase letters. # # Example: # ``` # lower_format AbCdEf # => abcdef #``` lower_format() { printf %s\\n "$*" | tr '[[:upper:]]' '[[:lower:]]' } # chain_format: convert a string from any characters to solely alphanumeric and single internal dash characters. # # Example: # ``` # chain_format "**Foo** **Goo** **Hoo**" # => Foo-Goo-Hoo #``` chain_format() { printf %s\\n "$*" | sed 's/[^[:alnum:]]\{1,\}/-/g; s/-\{2,\}/-/g; s/^-\{1,\}//; s/-\{1,\}$//;' } # snake_format: convert a string from any characters to solely alphanumeric and single internal underscore characters. # # Example: # ``` # snake_format "**Foo** **Goo** **Hoo**" # => Foo_Goo_Hoo #``` snake_format() { printf %s\\n "$*" | sed 's/[^[:alnum:]]\{1,\}/_/g; s/_\{2,\}/_/g; s/^_\{1,\}//; s/_\{1,\}$//;' } # space_format: convert a string from any characters to solely alphanumeric and single internal space characters. # # Example: # ``` # space_format "**Foo** **Goo** **Hoo**" # => Foo Goo Hoo #``` space_format() { printf %s\\n "$*" | sed 's/[^[:alnum:]]\{1,\}/ /g; s/ \{2,\}/ /g; s/^ \{1,\}//; s/ \{1,\}$//;' } # touch_format: convert a string from any characters to solely a command "touch -t" timestamp format. # # Example: # ``` # touch_format "Foo 2021-05-04 22:57:54 Goo" # => 202105042257.54 #``` touch_format() { printf %s\\n "$*" | sed 's/[^[:digit:]]//g; s/^\([[:digit:]]\{12\}\)\([[:digit:]]\{2\}\)/\1.\2/;' } # select_character_class: get a string's characters that match a class, with optional offset and length. # # Syntax: # ``` # select_character_class <string> <class> [offset [length]] # ``` # # Example with character class: # ``` # select_character_class foo123goo456 alpha # => foogoo # ``` # # Example with character class and substring offset: # ``` # select_character_class foo123goo456 alpha 3 # => goo # ``` # # Example with character class and substring offset and length: # ``` # select_character_class foo123goo456 alpha 3 1 # => g # ``` select_character_class() { string=${1//[^[:$2:]]/} offset=${3:-0} length=${4:-${#string}} printf %s\\n ${string:$offset:$length} } # reject_character_class: get a string's characters that don't match a class, with optional offset and length. # # Syntax: # ``` # reject_character_class <string> <class> [offset [length]] # ``` # # Example with character class: # ``` # reject_character_class foo123goo456 alpha # => -123--456 # ``` # # Example with character class and substring offset: # ``` # reject_character_class foo123goo456 alpha 6 # => 456 # ``` # # Example with character class and substring offset and length: # ``` # reject_character_class foo123goo456 alpha 6 1 # => 4 # ``` reject_character_class() { string=${1//[[:$2:]]/} offset=${3:-0} length=${4:-${#string}} printf %s\\n ${string:$offset:$length} } ## # Random character helpers ## # random_char # # Syntax: # ``` # random_char [characters [length]] # ``` # # Example: # ``` # random_char ABCDEF 8 # => CBACBFDD #``` # # Example hexadecimal digit uppercase: # ``` # random_char 0-9A-F 8 # => FC56A95C #``` # # Example character class for uppercase letters: # ``` # random_char '[:upper:]' 8 # => ZMGIQBJB #``` # # POSiX character classes for ASCII characters: # # ``` # Class Pattern Description # ---------- ------------- ----------- # [:upper:] [A-Z] uppercase letters # [:lower:] [a-z] lowercase letters # [:alpha:] [A-Za-z] uppercase letters and lowercase letters # [:alnum:] [A-Za-z0-9] uppercase letters and lowercase letters and digits # [:digit:] [0-9] digits # [:xdigit:] [0-9A-Fa-f] hexadecimal digits # [:punct:] punctuation (all graphic characters except letters and digits) # [:blank:] [ \t] space and TAB characters only # [:space:] [ \t\n\r\f\v] whitespace characters (space, tab, newline, return, feed, vtab) # [:cntrl:] control characters # [:graph:] [^ [:cntrl:]] graphic characters (all characters which have graphic representation) # [:print:] [[:graph:] ] graphic characters and space # ``` random_char() { chars=${1:-'[:graph:]'} len=${2-1} printf "%s\n" $(LC_ALL=C < /dev/urandom tr -dc "$chars" | head -c"$len") } # random_char_alnum: random characters using [:alnum:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_alnum 8 # => 1Yp7M7wc #``` random_char_alnum() { random_char '[:alnum:]' "$@" } # random_char_alpha: random characters using [:alpha:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_alpha 8 # => dDSmQlYD #``` random_char_alpha() { random_char '[:alpha:]' "$@" } # random_char_blank: random characters using [:blank:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_blank 8 # => " \t \t \t" #``` random_char_blank() { random_char '[:blank:]' "$@" } # random_char_cntrl: random characters using [:cntrl:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_cntrl 8 # => "^c^m^r^z^a^e^p^u" #``` random_char_cntrl() { random_char '[:cntrl:]' "$@" } # random_char_digit: random characters using [:digit:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_digit 8 # => 36415110 #``` random_char_digit() { random_char '[:digit:]' "$@" } # random_char_graph: random characters using [:graph:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_graph 8 # => e'2-3d+9 #``` random_char_graph() { random_char '[:graph:]' "$@" } # random_char_lower: random characters using [:lower:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_lower 8 # => pgfqrefo #``` random_char_lower() { random_char '[:lower:]' "$@" } # random_char_lower_digit: random characters using [:lower:][:digit] classes # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_lower_digit 8 # => 69m7o83i #``` random_char_lower_digit() { random_char '[:lower:][:digit:]' "$@" } # random_char_upper: random characters using [:upper:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_upper 8 # => EGXUHNIM #``` random_char_upper() { random_char '[:upper:]' "$@" } # random_char_upper_digit: random characters using [:upper:][:digit:] classes # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_upper_digit 8 # => L2PT37H6 #``` random_char_upper_digit() { random_char '[:upper:][:digit:]' "$@" } # random_char_print: random characters using [:print:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_print 8 # => ),zN87K; #``` random_char_print() { random_char '[:print:]' "$@" } # random_char_space: random characters using [:space:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_space 8 # => "\n \t\r \v \f" #``` random_char_space() { random_char '[:space:]' "$@" } # random_char_xdigit: random characters using [:xdigit:] class. # # Syntax: # ``` # random_char_alnum [length] # ``` # # Example: # ``` # random_char_xdigit 8 # => eC3Ce9eD #``` random_char_xdigit() { random_char '[:xdigit:]' "$@" } ## # Array helpers ## # array_n: get the array number of fields a.k.a. length a.k.a. size. # # Example: # ``` # set -- a b c d # array_n "$@" # => 4 # ``` array_n() { printf %s "$#" } # array_i: get the array item at index `i` which is 1-based. # # Example: # ``` # set -- a b c d # array_i "$@" 3 # => c # ``` # # POSIX syntax uses an array index that starts at 1. # # Bash syntax uses an array index that starts at 0. # # Bash syntax can have more power this way if you prefer it: # # ``` # [ $# == 3 ] && awk -F "$2" "{print \$$3}" <<< "$1" || awk "{print \$$2}" <<< "$1" # ``` array_i() { for __array_i_i in "$@"; do true; done if [ "$__array_i_i" -ge 1 -a "$__array_i_i" -lt $# ]; then __array_i_j=1 for __array_i_x in "$@"; do if [ "$__array_i_j" -eq "$__array_i_i" ]; then printf %s "$__array_i_x" return fi __array_i_j=$((__array_i_j+1)) done fi exit $EXIT_USAGE } # array_first: get the array's first item. # # Example: # ``` # set -- a b c d # array_first "$@" # => a # ``` array_first() { printf %s "$1" } # array_last: get the array's last item. # # Example: # ``` # set -- a b c d # array_last "$@" # => d # ``` array_last() { for __array_last_x in "$@"; do true; done printf %s "$__array_last_x" } # array_car: get the array's car item a.k.a. first item. # # Example: # ``` # set -- a b c d # array_car "$@" # => a # ``` array_car() { printf %s "$1" } # array_cdr: get the array's cdr items a.k.a. everything after the first item. # # Example: # ``` # set -- a b c # array_cdr "$@" # => b c d # ``` array_cdr() { shift printf %s "$*" } ## # Assert helpers ## # assert_test: assert a test utility command succeeds. # # Example: # ``` # assert_test -x program.sh # => success i.e. no output # # assert_test -x notes.txt # STDERR=> assert_test -x notes.txt # ``` assert_test() { test "$1" "$2" || err assert_test "$@" } # assert_empty: assert an item is empty. # # Example: # ``` # assert_empty "" # => success i.e. no output # # assert_empty foo # STDERR=> assert_empty foo # ``` assert_empty() { [ -z "$1" ] || err assert_empty "$@" } # assert_not_empty: assert an item is not empty. # # Example: # ``` # assert_not_empty foo # => success i.e. no output # # assert_not_empty "" # STDERR=> assert_not_empty # ``` assert_not_empty() { [ -z "$1" ] || err assert_not_empty "$@" } # assert_int_eq: assert an integer is equal to another integer. # # Example: # ``` # assert_int_eq 1 1 # => success i.e. no output # # assert_int_eq 1 2 # STDERR=> assert_int_eq 1 2 # ``` assert_int_eq() { [ "$1" -eq "$2" ] || err assert_int_eq "$@" } # assert_int_ne: assert an integer is not equal to another integer. # # Example: # ``` # assert_int_eq 1 2 # => success i.e. no output # # assert_int_eq 1 1 # STDERR=> assert_int_ne 1 1 # ``` assert_int_ne() { [ "$1" -ne "$2" ] || err assert_int_equal "$@" } # assert_int_ge: assert an integer is greater than or equal to another integer. # # Example: # ``` # assert_int_ge 2 1 # => success i.e. no output # # assert_int_ge 1 2 # STDERR=> assert_int_ge 1 2 # ``` assert_int_ge() { [ "$1" -ge "$2" ] || err assert_int_ge "$@" } # assert_int_gt: assert an integer is greater than another integer. # # Example: # ``` # assert_int_gt 2 1 # => success i.e. no output # # assert_int_gt 1 2 # STDERR=> assert_int_gt 1 2 # ``` assert_int_gt() { [ "$1" -gt "$2" ] || err assert_int_gt "$@" } # assert_int_le: assert an integer is less than or equal to another integer. # # Example: # ``` # assert_int_le 1 2 # => success i.e. no output # # assert_int_le 2 1 # STDERR=> assert_int_le 2 1 # ``` assert_int_le() { [ "$1" -le "$2" ] || err assert_int_le "$@" } # assert_int_lt: assert an integer is less than to another integer. # # Example: # ``` # assert_int_lt 1 2 # => success i.e. no output # # assert_int_lt 2 1 # STDERR=> assert_int_lt 2 1 # ``` assert_int_lt() { [ "$1" -lt "$2" ] || err assert_int_lt "$@" } # assert_str_eq: assert a string is equal to another string. # # Example: # ``` # assert_str_eq 1 1 # => success i.e. no output # # assert_str_eq 1 2 # STDERR=> assert_str_eq 1 2 # ``` assert_str_eq() { [ "$1" -eq "$2" ] || err assert_str_eq "$@" } # assert_str_ne: assert a string is not equal to another string. # # Example: # ``` # assert_str_eq 1 2 # => success i.e. no output # # assert_str_eq 1 1 # STDERR=> assert_str_ne 1 1 # ``` assert_str_ne() { [ "$1" -ne "$2" ] || err assert_str_equal "$@" } # assert_str_ge: assert a string is greater than or equal to another string. # # Example: # ``` # assert_str_ge 2 1 # => success i.e. no output # # assert_str_ge 1 2 # STDERR=> assert_str_ge 1 2 # ``` assert_str_ge() { [ "$1" -ge "$2" ] || err assert_str_ge "$@" } # assert_str_gt: assert a string is greater than another string. # # Example: # ``` # assert_str_gt 2 1 # => success i.e. no output # # assert_str_gt 1 2 # STDERR=> assert_str_gt 1 2 # ``` assert_str_gt() { [ "$1" -gt "$2" ] || err assert_str_gt "$@" } # assert_str_le: assert a string is less than or equal to another string. # # Example: # ``` # assert_str_le 1 2 # => success i.e. no output # # assert_str_le 2 1 # STDERR=> assert_str_le 2 1 # ``` assert_str_le() { [ "$1" -le "$2" ] || err assert_str_le "$@" } # assert_str_lt: assert a string is less than to another string. # # Example: # ``` # assert_str_lt 1 2 # => success i.e. no output # # assert_str_lt 2 1 # STDERR=> assert_str_lt 2 1 # ``` assert_str_lt() { [ "$1" -lt "$2" ] || err assert_str_lt "$@" } # assert_str_starts_with: assert a string starts with a substring. # # Example: # ``` # assert_str_starts_with foobar foo # => success i.e. no output # # assert_str_starts_with foobar xxx # STDERR=> assert_str_starts_with foobar xxx # ``` assert_str_starts_with() { [ "$1" != "${1#"$2"}" ] || err assert_str_starts_with "$@" } # assert_str_ends_with: assert a string ends with with a substring. # # Example: # ``` # assert_str_ends_with foobar bar # => success i.e. no output # # assert_str_ends_with foobar xxx # STDERR=> assert_str_ends_with foobar xxx # ``` assert_str_ends_with() { [ "$1" != "${1%"$2"}" ] || err assert_str_ends_with "$@" } ## # Make temp helpers ## # mktemp_dir: make a temporary directory path. # # Example: # ``` # mktemp_dir # => /var/folders/4f7b65122b0fb65b0fdad568a65dc97d # ``` mktemp_dir() { x=$(mktemp -d -t "${1:-$(zid)}") ; trap '{ rm -rf "$x"; }' EXIT ; out "$x" } # mktemp_file: make a temporary file path. # # Example: # ``` # mktemp_file # => /var/folders/4f7b65122b0fb65b0fdad568a65dc97d/1d9aafac5373be95d8b4c2dece0b1197 # ``` mktemp_file() { x=$(mktemp -t "${1:-$(zid)}") ; trap '{ rm -f "$x"; }' EXIT ; out "$x" } ## # Media helpers ## # file_media_type: get a file's media type a.k.a. mime type such as "text/plain". # # Example: # ``` # file_media_type notes.txt # => text/plain # ``` file_media_type() { file --brief --mime "$1" } # file_media_type_supertype: get a file's media type type a.k.a. mime type such as "text". # # Example: # ``` # file_media_type_supertype notes.txt # => text # ``` file_media_type_supertype() { file --brief --mime "$1" | sed 's#/.*##' } # file_media_type_subtype: get a file's media type subtype a.k.a. mime type such as "plain". # # Example: # ``` # file_media_type_subtype notes.txt # => plain # ``` file_media_type_subtype() { file --brief --mime "$1" | sed 's#^[^/]*/##; s#;.*##' } ## # Font helpers ## # font_name_exists: does a font name exist on this system? # # Example: # ``` # font_name_exists Arial # => true # # font_name_exists Foo # => false # ``` # font_name_exists() { fc-list | grep -q ": $1:" } # font_name_exists_or_die: ensure a font name exists. # # Example: # ``` # font_name_exists_or_die Arial # => true # # font_name_exists_or_die Foo # STDERR=> Font needed: Foo # => exit 1 # ``` # font_name_exists_or_die() { font_name_exists "$1" || die "$EXIT_UNAVAILABLE" "Font needed: $1" } ## # Content helpers ## # file_ends_with_newline: Does a file end with a newline? # # Example: # ``` # file_ends_with_newline notes.txt # => true # ``` file_ends_with_newline() { test $(tail -c1 "$1" | wc -l) -gt 0 } ## # Directory helpers ## # user_dir: get a user-specific directory via env var, or XDG setting, or HOME. # # Example: # ``` # user_dir foo => $FOO_DIR || $FOO_HOME || $XDG_FOO_DIR || $XDG_FOO_HOME || $HOME/foo # ``` # # Conventions: # # * `user_dir bin` => binary executable directory # * `user_dir cache` => cache directory # * `user_dir config` => configuration directory # * `user_dir data` => data directory # * `user_dir desktop` => desktop directory # * `user_dir documents` => documents directory # * `user_dir download` => download directory # * `user_dir log` => logging directory # * `user_dir music` => music directory # * `user_dir pictures` => pictures directory # * `user_dir publicshare` => public share directory # * `user_dir runtime` => runtime directory # * `user_dir state` => state directory # * `user_dir temp` => temporary directory # * `user_dir templates` => templates directory # * `user_dir videos` => videos directory # # Popular XDG conventions: # # * `XDG_DESKTOP_DIR` => user-specific desktop, such as frequent apps and files. # * `XDG_DOCUMENTS_DIR` => user-specific documents, such as typical working files. # * `XDG_DOWNLOAD_DIR` => user-specific downloads, such as internet file downloads. # * `XDG_MUSIC_DIR` => user-specific music files, such as songs. # * `XDG_PICTURES_DIR` => user-specific pictures, such as photos. # * `XDG_PUBLICSHARE_DIR` => user-specific public share, such as file sharing. # * `XDG_TEMPLATES_DIR` => user-specific templates. # * `XDG_VIDEOS_DIR` => user-specific videos, such as movies. # # POSIX XDG conventions: # # * `XDG_BIN_HOME` => user-specific binaries, analogous to system /usr/bin or $HOME/.local/bin. # * `XDG_LOG_HOME` => user-specific log files, analogous to system /var/log or $HOME/.local/log. # * `XDG_TEMP_HOME` => user-specific temporary files, analogous to system /temp or $HOME/.temp. # * `XDG_DATA_HOME` => user-specific data files, analogous to system /usr/share or $HOME/.local/share. # * `XDG_CACHE_HOME` => user-specific cache files, analogous to system /var/cache or $HOME/.cache. # * `XDG_STATE_HOME` => user-specific cache files, analogous to system /var/state or $HOME/.local/state. # * `XDG_CONFIG_HOME` => user-specific configuration files, analogous to system /etc or $HOME/.config. # * `XDG_RUNTIME_HOME` => user-specific runtime files such as sockets, named pipes, etc. or $HOME/.runtime. # # See also: # # * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html # # * https://wiki.archlinux.org/title/XDG_user_directories # user_dir(){ upper=$(printf %s\\n "$1" | tr '[:lower:]' '[:upper:]') x=$(set +u; eval printf "%s\\\\n" \$${upper}_DIR) if [ -n "$x" ]; then printf %s\\n "$x"; return; fi x=$(set +u; eval printf "%s\\\\n" \$${upper}_HOME) if [ -n "$x" ]; then printf %s\\n "$x"; return; fi x=$(set +u; eval printf "%s\\\\n" \$XDG_${upper}_DIR) if [ -n "$x" ]; then printf %s\\n "$x"; return; fi x=$(set +u; eval printf "%s\\\\n" \$XDG_${upper}_HOME) if [ -n "$x" ]; then printf %s\\n "$x"; return; fi lower=$(printf %s\\n "$1" | tr '[:upper:]' '[:lower:]') printf %s\\n "$HOME/$lower" }