3

I want to use the tr command to map chars to new chars, for example:

echo "hello" | tr '[a-z]' '[b-za-b]' Will output: ifmmp

(where each letter in the lower-case alphabet is shifted over one to the right)

See below the mapping to new chars for '[b-za-b]':

[a b c d e f g h i j k l m n o p q r s t u v w x y z] will map to:

[b c d e f g h i j k l m n o p q r s t u v w x y z a]


However, when I want it to rotate multiple times, how can I use a variable to control the rotate-value for the tr command?

Eg: for a shift of 1:

echo "hello" | tr '[a-z]' '[b-za-b]' without variables and:

echo "hello" | tr '[a-z]' '[(a+$var)-za-(a+$var)]' where $var=1

here I have: (a+$var)-z representing the same as b-z and

....................a-(a+$var) representing the same as a-b

I have tried converting the ascii value to a char to use within the tr command but I don't think that is allowed.

My problem is that bash is not interpreting:

(a+$var) as the char b when $var=1

(a+$var) as the char c when $var=2

... etc.

How can I tell bash to interpret these equations as chars for the tr command

EDIT

I tried doing it with an array but it's not working:

chars=( {a..z} )

var=2

echo "hello" | tr '[a-z]' '[(${chars[var]}-z)(a-${chars[var]})]'

I used: (${chars[var]}-z) to represent b-z where var=1

Because ${chars[1]} is b but this is not working. Am I missing something?

Community
  • 1
  • 1
user9506231
  • 113
  • 2
  • 7
  • 1
    Use a language with `ord()` and `chr()` functions, Python, Perl, C, etc.... – cdarke Mar 17 '18 at 07:40
  • 2
    See: [Difference between single and double quotes in bash](http://stackoverflow.com/q/6697753/3776858) – Cyrus Mar 17 '18 at 07:40

3 Answers3

5

What you are trying to do cannot be done using tr which does not handle your requirement. Moreover when you meant to modify and use variables to add to glob patterns in bash which is something you cannot possibly do.

There is a neat little trick you can do with bash arrays!. The tr command can take expanded array sequence over the plain glob patterns also. First define a source array as

source=()

Now add its contents as a list of character ranges from a-z using brace expansion as

source=({a..z})

and now for the transliterating array, from the source array, construct it as follows by using the indices to print the array elements

trans=()

Using a trick to get the array elements from the last with syntax ${array[@]: (-num)} will get you the total length - num of the elements. So building the array first as

var=2
trans+=( "${source[@]:(-(26-$var))}" )

and now to build the second part of the array, use another trick ${array[@]:0:num} to get the first num number of elemtents.

trans+=( "${source[@]:0:$(( $var + 1 ))}" )

So what we have done now is for a given value of var=2, we built the trans array as

echo "${trans[@]}"
c d e f g h i j k l m n o p q r s t u v w x y z a b c

Now you can just use it easily in the tr command

echo "hello" | tr "${source[*]}" "${trans[*]}"
jgnnq

You can just put it all in function and print its value as

transChar() {
    local source
    local trans
    local result
    source=({a..z})
    trans=()
    var="$2"
    input="$1"
    trans+=( "${source[@]:(-(26-$var))}" )
    trans+=( "${source[@]:0:$(( $var + 1 ))}" )
    result=$( echo "$input" | tr "${source[*]}" "${trans[*]}" )
    echo "$result"
}

Some of the tests

transChar "hello" 1
ifmmp

transChar "hello" 2
jgnnq

transChar "hello" 3
khoor
Inian
  • 80,270
  • 14
  • 142
  • 161
2

rot-random:

# generate alphabet as arr:
arr=( {1..26} ) 
i=$(($RANDOM%24+1))
# left and right
l=$(echo ${arr[$i]})
r=$(echo ${arr[$i+1]})
# reusing arr for testing:
echo ${arr[@]} | tr "a-z" "$r-za-$l"

echo "secret:"  | tr "a-z" "$r-za-$l" ; echo $l $r $i
amkzmb:
h i 7
Inian
  • 80,270
  • 14
  • 142
  • 161
user unknown
  • 35,537
  • 11
  • 75
  • 121
1

You could use octal \XXX character codes for characters to do what you intend. Using the octal codes you could do any arithmetic manipulations to numbers and then convert them to character codes

# rotr x
#
# if 0 <= x <= 25 rotr x outputs a set specification
# that could be used as an argument to tr command
# otherwise it outputs 'a-z'

function rotr(){
   i = $(( 97 + $1 ))
   if [ $i -lt 97 ] ; then
        translation='a-z'
   elif [ $i -eq 97 ] ; then
        translation='\141-\172'  # 141 is the octal code for "a"
                                 # 172 is the octal code for "z" 
   elif [ $i -eq 98 ] ; then
        translation='\142-\172\141'
   elif [ $i -lt 122 ] ; then       # $i is between 99 and 121 ("c" and "y") 
        ii=$(echo "obase=8 ; $i" | bc)
        jj=$(echo "obase=8 ; $(( $i - 1 ))" | bc)
        translation=\\$ii'-\172\141-'\\$jj
   elif [ $i -eq 122 ] ; then
        translation='\172\141-\171'
   else                              # $i > 122
        tranlation='a-z'
   fi
   echo $translation
} 

Now you could use this as follows

echo hello | tr 'a-z' $(rotr 7)

prints

olssv
Dima Chubarov
  • 16,199
  • 6
  • 40
  • 76