substr

Unlike Bourne Again Shell (bash), POSIX shell does not have a native substring function. Below is an implementation written in native POSIX shell that is:

  • Faster than piping to awk, perl, python, ruby, or other langauge

  • Compatible with bash and all other POSIX-compliant shells

  • Optimized to use ${foo:start[:length]} when bash is detected

  1 #!/bin/sh
  2 if [ "$BASH_VERSION" ]; then
  3     substr() # $var_to_get $start $length $var_to_set
  4     {
  5         eval $4=\"\${$1:\$2${3:+:\$3}}\"
  6     }
  7 else
  8     substr() # $var_to_get $start $length $var_to_set
  9     {
 10         eval local __tmp=\"\$$1\" || return
 11         local __start="${2:-0}" __len="$3"
 12         local __tbuf __tbuf_len __trim __trimq
 13 
 14         if [ ! "$__tmp" ]; then
 15             eval $4=
 16             return 0
 17         fi
 18         if [ "$__start" -lt 0 ] 2> /dev/null; then
 19             __start=$(( ${#__tmp} + $__start ))
 20             if [ $__start -lt 0 ]; then
 21                 eval $4=
 22                 return 1
 23             fi
 24         elif [ "$__start" -ge 0 ] 2> /dev/null; then
 25             : valid number
 26         else
 27             __start=0
 28         fi
 29         if [ ! "$__len" ]; then
 30             if [ $__start -eq 0 ]; then
 31                 eval $4=\"\$__tmp\"
 32                 return 0
 33             fi
 34             __len=$(( ${#__tmp} - $__start ))
 35         elif [ "$__len" -lt 0 ] 2> /dev/null; then
 36             __len=$(( ${#__tmp} - $__start + $__len ))
 37         elif [ "$__len" -ge 1 ] 2> /dev/null; then
 38             : valid number
 39         else
 40             eval $4=
 41             return 1
 42         fi
 43 
 44         __trim=$__start
 45         while [ $__trim -gt 0 ]; do
 46             __tbuf="?"
 47             __tbuf_len=1
 48             while [ $__tbuf_len -lt $(( $__trim / $__tbuf_len )) ]
 49             do
 50                 __tbuf="$__tbuf?"
 51                 __tbuf_len=$(( $__tbuf_len + 1 ))
 52             done
 53             __trimq=$(( $__trim / $__tbuf_len ))
 54             __trim=$(( $__trim - $__tbuf_len * $__trimq ))
 55             while [ $__trimq -gt 0 ]; do
 56                 __tmp="${__tmp#$__tbuf}"
 57                 __trimq=$(( $__trimq - 1 ))
 58             done
 59         done
 60 
 61         local __tmp_len=${#__tmp}
 62         local __mask __mask_len
 63         __trim=$(( $__tmp_len - ${__len:-$__tmp_len} ))
 64         while [ $__trim -gt 0 ]; do
 65             __tbuf="?"
 66             __tbuf_len=1
 67             if [ $__trim -le $__len ]; then
 68                 while [ $__tbuf_len -lt $((
 69                     $__trim / $__tbuf_len
 70                 )) ]; do
 71                     __tbuf="$__tbuf?"
 72                     __tbuf_len=$(( $__tbuf_len + 1 ))
 73                 done
 74                 __trimq=$(( $__trim / $__tbuf_len ))
 75                 __trim=$(( $__trim - $__tbuf_len * $__trimq ))
 76                 while [ $__trimq -gt 0 ]; do
 77                     __tmp="${__tmp%$__tbuf}"
 78                     __trimq=$(( $__trimq - 1 ))
 79                 done
 80             else
 81                 __mask="$__tmp"
 82                 while [ $__tbuf_len -lt $((
 83                     $__len / $__tbuf_len
 84                 )) ]; do
 85                     __tbuf="$__tbuf?"
 86                     __tbuf_len=$(( $__tbuf_len + 1 ))
 87                 done
 88                 __trimq=$(( $__len / $__tbuf_len ))
 89                 if [ $__len -ne $(( $__trimq * $__tbuf_len )) ]
 90                 then
 91                     __tbuf="$__tbuf?"
 92                     __tbuf_len=$(( $__tbuf_len + 1 ))
 93                 fi
 94                 __mask_len=$((
 95                     $__tmp_len - $__tbuf_len * $__trimq
 96                 ))
 97                 __trim=$(( $__tmp_len - $__mask_len - $__len ))
 98                 while [ $__trimq -gt 0 ]; do
 99                     __mask="${__mask#$__tbuf}"
100                     __trimq=$(( $__trimq - 1 ))
101                 done
102                 __tmp="${__tmp%"$__mask"}"
103             fi
104         done
105 
106         eval $4=\"\$__tmp\"
107     }
108 fi
109 substr "$@"
110 eval echo \"$4=[\$$4]\"

Last updated