Bash: String Rulers
k n o w t h y s e l f
1 2 3 4 5 6 7 8 9 0 1 2
0 0 0 0 0 0 0 0 0 1 1 1
In this project I try visualize strings as in the above.
IBAN Numbers
Counting the number of characters in a string is straight forward with wc
echo $iban_number | wc -c
Suppose we want to visualize the 24-character strings. We could define a bash function as follows
iban_ruler(){
ruler=$(seq 24 | sed 's/$/%10/' | bc | xargs | tr -d ' ')
echo $1
echo $ruler
}
So now
$ iban_ruler SA0000000000000000000000
SA0000000000000000000000
123456789012345678901234
This is nice but is limited to 24 characters. Let’s make it more flexible:
string_ruler(){
num_chars=$(echo $1 | wc -c)
ruler=$(seq $num_chars | sed 's/$/%10/' | bc | xargs | tr -d ' ')
echo $1
echo $ruler
}
sed
here is replacing the end of the sentence with %10
and piping the result to the calculator bc
, the remainder will result from this, which is then piped to xargs
to put the result on one line then the spaces are removed with tr
. Now let’s try it
$ string_ruler 'aziz codes is the best blog in the universe'
aziz codes is the best blog in the universe
12345678901234567890123456789012345678901234
This is nice but it’s not treating spaces correctly. Also, I want to show the another ruler that has the tens.
string_ruler4(){
# print the input
echo $1
# insert spaces between characters in the input string
# i.e. aziz becomes a z i z
old_IFS=$IFS
IFS=''
data=$(echo -n "$1" | sed 's/\(.\)/\1 /g' ) # or sed 's/\B/ /g' )
echo $data
IFS=$old_IFS
# calculate number of characters in the input string
num_chars=$(echo -n "$1" | wc -c)
# make a sequence of characters equal in length to the input string
# pad single digits with zeros -> 01; 02; 03; etc
# insert spaces between these 2-digit numbers -> 0 1; 0 2; 0 3 etc
# the first field is the tens, the second field is ones (recognizable by awk)
ones=$(seq $num_chars | sed 's/^\(.\)$/0\1/' | sed 's/\(.\)\(.\)/\1 \2/' | awk '{print $2}')
tens=$(seq $num_chars | sed 's/^\(.\)$/0\1/' | sed 's/\(.\)\(.\)/\1 \2/' | awk '{print $1}')
echo $ones
echo $tens
}
You can see the result of this below.
$ string_ruler4 'know thyself'
know thyself
k n o w t h y s e l f
1 2 3 4 5 6 7 8 9 0 1 2
0 0 0 0 0 0 0 0 0 1 1 1
With Bash Arrays
We can use arrays
string_ruler5(){
# print the input
echo $1
# make it space separated
sep=$(echo $1 | sed 's/./& /g')
# make an array
arr=($sep)
# make
for k in ${arr[@]}; do
echo $k
done | nl | awk '{print int($1/10), int($1%10), $2}'
}
The result of the above code is below
$ string_ruler5 knowthyself
knowthyself
0 1 k
0 2 n
0 3 o
0 4 w
0 5 t
0 6 h
0 7 y
0 8 s
0 9 e
1 0 l
1 1 f
improved to handles spaces below
string_ruler6(){
input=$1
letters=$(echo $input | sed 's/ /_/g' | sed 's/./& /g')
arr=($letters)
for a in "${arr[@]}"; do echo $a; done | xargs | sed 's/_/ /g'
i=0
for a in "${arr[@]}"; do
i=$(($i+1))
k=$(($i%10))
echo $k
done | xargs
i=0
for a in "${arr[@]}"; do
i=$(($i+1))
j=$(($i/10))
echo $j
done | xargs
}
Bonus: Other Attempts
Python is excellent at looping. A solution is below
import sys
arg=sys.argv[1]
print(arg)
numbered=[k for k in enumerate(arg)]
for n,a in numbered:
print(a,end=' ')
print('')
for n,a in numbered:
print(n%10,end=' ')
print('')
for n,a in numbered:
print(n//10,end=' ')
print('')
Saved into numbered.py
, then
$ python numbered.py 'know thyself'
know thyself
k n o w t h y s e l f
0 1 2 3 4 5 6 7 8 9 0 1
0 0 0 0 0 0 0 0 0 0 1 1
These attempts didn’t work but they were worth trying.
This one doesn’t work because brace expansion precedes command substitution.
string_ruler2(){
num_chars=$(echo $1 | wc -c)
tens=$(echo $num_chars/10 | bc)
ruler1=$(for k in {0..$tens}{1..9}; do echo ${k:1:1}; done | xargs | tr -d ' ')
ruler2=$(for k in {0..$tens}{1..9}; do echo ${k:0:1}; done | xargs | tr -d ' ')
echo $1
echo $ruler1
echo $ruler2
}
This one works but its lengthy and doesn’t treat spaces correctly.
string_ruler3(){
num_chars=$(echo $1 | wc -c)
tens=$(echo $num_chars/10 | bc)
ruler1=$(
for i in $(seq 0 $tens); do
for j in $(seq 10 | sed 's/.*\(.$\)/\1/g'); do
k=$i$j
echo ${k:1:1}
done
done | xargs | tr -d ' ')
ruler2=$(
for i in $(seq 0 $tens); do
for j in $(seq 10 | sed 's/.*\(.$\)/\1/g'); do
k=$i$j
echo ${k:0:1}
done
done | xargs | tr -d ' ')
echo $1
echo $ruler1
echo $ruler2
}