Skip to content

Instantly share code, notes, and snippets.

@td-shi
Last active May 7, 2024 17:55
Show Gist options
  • Save td-shi/d7580d39265ddfd7f2f44358e53b21b6 to your computer and use it in GitHub Desktop.
Save td-shi/d7580d39265ddfd7f2f44358e53b21b6 to your computer and use it in GitHub Desktop.
ULID on shell script. Universally Unique Lexicographically Sortable Identifier. [ULID](https://github.com/ulid/spec)
#!/bin/sh
# -*- coding:utf-8 posix -*-
# === Initialize shell environment =============================================
#set -u # Just stop undefined values.
#set -e # Just stop error.
#set -x # Debug running command.
umask 0022
export LC_ALL=C
export LANG=C
PATH="$(command -p getconf PATH 2>/dev/null)${PATH+:}${PATH-}"
case $PATH in (:*) PATH=${PATH#?};; esac
export PATH # or PATH="<add dir>${PATH+:}${PATH-}"
export UNIX_STD=2003 # to make HP-UX conform to POSIX
# === Define the functions for printing usage and error message ================
usage_and_exit(){
cat <<-"USAGE" 1>&2
# About
The ulid.sh is a shell script implimentation of
"Universally Unique Lexicographically Sortable Identifier".
[ULID](https://github.com/ulid/spec).
Bad, this script does not conform to the "Monotonicity" specification.
Therefore, the 80-bit random region is always random.
Also, this script does not get the exact time (milliseconds). This is because
there is no stable way to obtain this with shell commands. However,
you can optionally specify milliseconds.
# Usage
## Command
ulid.sh [Options] [--msec <ms>]
```
$> ulid.sh
01DNPS05T77C5WY933034FYZYP
```
```
$> i=0; while [ 5 -gt $i ] ; do i=$(( i + 1 )); ./ulid.sh --msec 158598740350$i; done
01E523EFQDPTDVW5V4AZ6AY1NW
01E523EFQEY8PDZ1CS95A1QH18
01E523EFQF7HCPSXJVQ8K15TCV
01E523EFQG0XBA99T9VVHTW3RB
01E523EFQH84V9KTYRQN24FHRJ
```
## Options
+ -h |--help |--version
- help.
+ --msec <ms>
- Generating at <ms>.
# Version
2022-12-20T23:10:00 0.09 [Latest](https://gist.github.com/search?q=user%3Atd-shi+filename%3A.sh+ulid)
# LICENSE
[CC0(Public domain)](https://creativecommons.org/publicdomain/zero/1.0/legalcode)
# Author
2020 TD
USAGE
exit 1
}
error_exit() {
${2+:} false && echo "${0##*/}: $2" 1>&2
exit "$1"
}
# === Initialize parameters ====================================================
TIME=0
# === Confirm that the required commands exist =================================
if type hexdump >/dev/null 2>&1 ; then
genRandom () {
hexdump -n 16 -e '8/2 " %04x" "\n"' -v /dev/urandom | tr -d " "
}
elif type od >/dev/null 2>&1 ; then
genRandom () {
od -A n -t x2 -N 16 -v /dev/urandom | tr -d " "
}
else
error_exit 1 'require command "hexdump" or "od"'
fi
type od >/dev/null 2>&1 || error_exit 1 'require command od'
# === Print usage and exit if one of the help options is set ===================
case "$# ${1:-}" in
('1 -h'|'1 --help'|'1 --version') usage_and_exit;;
esac
# === Read options =============================================================
while :; do
case "${1:-}" in
(--|-)
break
;;
(--msec)
TIME=$(printf '%s' "${2:-}" | tr -Cd "0123456789")
shift 2
;;
(--*|-*)
error_exit 1 'Invalid option'
;;
(*)
break
;;
esac
done
# === Require parameters check =================================================
# === Last parameter ===========================================================
# === Define funcitons =========================================================
unixTimeSec () {
set -- "$(date -u "+%Y-%m-%d %H:%M:%S")" "$1"
set -- "${1%% *}" "${1##* }" "$2"
set -- "${1%%-*}" "${1#*-}" "${2%%:*}" "${2#*:}" "$3"
set -- "$1" "${2%%-*}" "${2#*-}" "$3" "${4%%:*}" "${4#*:}" "$5"
set -- "${1}" "${2#0}" "${3#0}" "${4#0}" "${5#0}" "${6#0}" "$7"
[ "$2" -lt 3 ] && set -- $(($1 - 1)) $(($2 + 12)) "$3" "$4" "$5" "$6" "$7"
set -- $(( (365 * $1) + ($1 / 4) - ($1 / 100) + ($1 / 400))) $(( (306 * (1 + $2) / 10) - 428 )) "$3" "$4" "$5" "$6" "$7"
set -- $(( ($1 + $2 + $3 - 719163) * 86400 + $4 * 3600 + $5 * 60 + $6 )) "$7"
printf "%s%s" "$1" "$2"
}
decodeHex5BaseV4() {
set -- "${1%???}" "${1#?}" "${1#??}" "${1#???}"
set -- "0x${1}" "0x${2%??}" "0x${3%?}" "0x${4}"
set -- "\\1$(($1>>6 & 0x3))$(($1>>3 & 0x7))" "\\1$(($2>>5 & 0x3))$(($2>>2 & 0x7))" "\\1$(($3>>4 & 0x3))$(($3>>1 & 0x7))" "\\1$(($4>>3 & 0x3))$(($4 & 0x7))"
printf "$1$2$3$4"
}
decode2Hex () {
set -- "$1" "0" ""
while [ "$1" -gt 0 ] ; do
set -- "$(( $1 / 16))" "$(( $1 % 16 ))" "$3"
set -- "$1" "$2" "\\0$(( ($2>>3) + 6))$(($2 & 0x7))$3"
done
printf "$3" | tr ":;<=>?" "abcdef"
}
# === Main routine =============================================================
if [ "_" != "_${TIME}" ] && [ "${TIME}" -gt 0 ]; then
:
else
TIME=$(unixTimeSec "000")
fi
set -- "000000000000000$(decode2Hex "${TIME}")" "0000$(genRandom)"
set -- "${1#${1%???????????????}}" "${2#${2%????????????????????}}" # 15, 20
set -- "${1%??????????}" "${1#?????}" "${2%??????????}" "${2#??????????}"
set -- "$1" "${2%?????}" "${2#?????}" "${3%?????}" "${3#?????}" "${4%?????}" "${4#?????}"
ULID=$(while [ $# -gt 0 ] ; do decodeHex5BaseV4 "$1"; shift; done | tr "@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]^_\`" "0123456789ABCDEFGHJKMNPQRSTVWXYZ")
printf "%s\n" "${ULID#??}"
# === End shell script =========================================================
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment