Skip to content

Instantly share code, notes, and snippets.

@alganet
Created August 19, 2022 16:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alganet/70cf8b20a560adeae63ce867bfb2b2a9 to your computer and use it in GitHub Desktop.
Save alganet/70cf8b20a560adeae63ce867bfb2b2a9 to your computer and use it in GitHub Desktop.
Repeating strings and iterating over chars in portable shell
set -euf
PATH=
IFS=' '
# Shells like dash and posh don't have the substring parameter expansion
# commonly found in bash and zsh.
#
# To make iterating over each char fast, instead of breaking each
# char individually (which forces the shell to deal with the entire
# string twice, due to the nature of parameter substitution), we
# do consecutive breaks in half.
#
# The str_repeat function here is used to generate the specific
# pattern that splits an arbitrary string into its half.
#
# You should use this only if you want to support these kind
# of operations on these lofi shells.
#
! command -v emulate >/dev/null 2>&1 || emulate ksh >/dev/null 2>&1
command -v local >/dev/null 2>&1 || alias local=typeset
_str_repeat () {
local str="$1" length_plus1=$(($2 + 1)) pow_of_two=1 next_pow= repeated=
# these cases (0-4) are not worth the cost of the while below
case $length_plus1 in
1) REPLY=""; return;;
2) REPLY="$1"; return;;
3) REPLY="$1$1"; return;;
4) REPLY="$1$1$1"; return;;
5) REPLY="$1$1$1$1"; return;;
esac
# doubles $repeated by powers of two
while next_pow=$((pow_of_two * 2))
do
if test $next_pow -lt $length_plus1
then
pow_of_two=$next_pow
str="$str$str"
else
length_plus1=$((length_plus1 - pow_of_two))
if test $length_plus1 -lt 1
then break
fi
repeated="$repeated$str"
str="$1"
pow_of_two=1
fi
done
REPLY="$repeated"
}
_str_scan_chars () {
local result= left= right= result= any_char=
# changes the orientation of string cutting
if test "${1:-}" = 1
then begin='#' end='%'
else begin='%' end='#'
fi
if test $# -gt 1
then shift
fi
# recursively splits string in half, left side first
while test "$#" -gt 0
do
if test -z "$1"
then shift && continue
fi
_str_repeat '?' $((${#1} / 2))
any_char="$REPLY"
eval "right=\"\${1$begin$any_char}\""
eval "left=\"\${1$end$right}\""
shift
if test 1 = ${#left}
then
result="${result} ${left}"
left=''
fi
if test 1 = ${#right}
then
result="${result} ${right}"
right=''
fi
set -- "$left" "$right" "$@"
done
REPLY="$result"
}
_str_repeat '_' 1000; echo "${#REPLY}"
_str_scan_chars 1 "$REPLY";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment