If there is anything the shell is very good at it is mangling arguments. And this is as it should be - the shell's primary purpose is to interpret and pass those arguments along.
What follows is composed almost entirely of shell builtins - excepting tr and possibly printf - and is very nearly POSIX portable code with the one exception being my use of local which is not a POSIX specified utility but which is pretty much ubiquitous. To emulate local we might do one more one-line function but I doubt if it's necessary.
TWO SHELL FUNCTIONS:
#let printf handle the printing
_hashes() { printf %0$((${1}))d\\n | tr 0 \# ; }
_hdr_inc() { local _hinc=${1##*-} _hashc=${2##*[!0-9]}
: ${_hinc:=$(set -- $3 ; printf %s_cnt\\n "${1-_hdr}")}
${1+shift} ${2+2}
_hashes ${_hashc:=40}
printf "%s #$((${_hinc}=${_hinc}+1)):${1+%b}" \
${1+"$*"} ${1+\\c}Default
echo && _hashes $_hashc
}
Both functions will accept any number of command-line arguments gracefully (within $ARGMAX limits).
_hashes
..for example, will print you only a single #hash then a \newline. But adding any arguments after the first is futile - _hashes() will ignore them.
_hdr_inc - - and a bunch of extras
...on the other hand will at once either increment by one from its current value or from zero a shell variable named $and_cnt and print to its stdout the 3 line string result of doing approximately:
_hashes 40
echo and a bunch of extras \#$((and_cnt=and_cnt+1)):
_hashes 40
Just ...
_hdr_inc
... does similar, but with all default values:
_hashes 40
echo Default \#$((_hdr_cnt=_hdr_cnt+1)):
_hashes 40
But setting explicit values for all args:
_hdr_inc counter 31 Some string
...does...
_hashes 31
echo Some string \#$((counter=counter+1)):
_hashes 31
Or just the first:
_hdr_inc counter
...does...
_hashes 40
echo Default \#$((counter=counter+1))
_hashes 40
DEMO
( set -- Header Paragraph
for h do {
_hdr_inc ; echo
_hdr_inc - - $h ; echo
_hdr_inc - 20 $h And More\! ; echo
_hdr_inc - 30 ; echo
_hdr_inc $h - ; echo
} ; done
set -- $1 $1 $1 $1 $2 $2 $2 $2
printf 'echo "Custom increment \\$%s_cnt = $%s_cnt"
echo "Explicit increment \\$%s = $%s"\n' "$@" |
. /dev/stdin
echo 'Default increment $_hdr_cnt =' $_hdr_cnt
)
OUTPUT:
########################################
Default #1:
########################################
########################################
Header #1:
########################################
####################
Header And More! #2:
####################
##############################
Default #2:
##############################
########################################
Default #1:
########################################
########################################
Default #3:
########################################
########################################
Paragraph #1:
########################################
####################
Paragraph And More! #2:
####################
##############################
Default #4:
##############################
########################################
Default #1:
########################################
Custom increment $Header_cnt = 2
Explicit increment $Header = 1
Custom increment $Paragraph_cnt = 2
Explicit increment $Paragraph = 1
Default increment $_hdr_cnt = 4