A Weird Imagination

Reverse sequence for tr

The problem

If you take the word wizard, reverse the order of the letters and reverse the alphabet:

From: abcdefghijklmnopqrstuvwxyz
To:   ZYXWVUTSRQPONMLKJIHGFEDCBA

then you get the word wizard back, an observation made at least as early as 1972.

Now let's write a shell script to verify this so we can find other words with similar interesting properties. The obvious shell script to verify this

echo wizard | tr a-z z-a | rev

unfortunately fails with the error

tr: range-endpoints of 'z-a' are in reverse collating sequence order

The error is by design: it's not clear what a sequence in reverse order should mean, so POSIX actually requires that it not work.

Reverse-ordered sequence the hard way

The arguments to tr can be strings interpreted as lists of characters instead of character ranges. We could just precompute (or type by hand) the alphabet in order and in reverse order and write

echo wizard | tr abcdefghijklmnopqrstuvwxyz zyxwvutsrqponmlkjihgfedcba | rev

But what if we want to automate generating the alphabet? (Perhaps there's some other character sequence we might care about in another situation.)

seq will generate sequences of numbers in reverse order if given a negative increment:

$ seq 3 -1 1
3
2
1

But seq doesn't generate characters. Luckily, someone has already figured out how to convert from numbers to characters in shell, so we can use the chr() function in our script:

chr() {
    printf \\"$(printf '%03o' "$1")"
}

ShellCheck is not happy with this function because it's abusing printf by generating a string for printf to take as its first argument. The way it works is writing the number it is given as input in octal, prefixing it with a backslash and then using the fact that \NNN where NNN is an octal number is one of the special sequences printf accepts.

As a convenience, we will also take ord() which uses an obscure shell feature to convert a character to a number:

ord() {
    printf '%d' "'$1"
}

Now we can generate the sequence with seq:

$ za="$(for charNum in $(seq "$(ord z)" -1 "$(ord a)")
do
    chr "$charNum"
done)"
$ echo $za
zyxwvutsrqponmlkjihgfedcba

Now we can use $za as an argument to tr:

$ echo wizard | tr a-z "$za" | rev
wizard

Reverse-ordered sequence the easy way

If we are willing to accept a dependency on bash, there is a very useful bashism that simplifies the above:

za="$(echo {z..a} | tr -d ' ')"

This post has some examples of bash's {..} syntax.

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

It's intended to be iterated over, which is why it has spaces, but the tr -d ' ' removes the spaces.

Iterate over the dictionary

The following script uses look to find all words which get back to the same word when reversed and the alphabet is reversed:

#!/bin/bash

az="$(echo {a..z} | tr -d ' ')"
za="$(echo {z..a} | tr -d ' ')"

for word in $(look "$1" | tr -d "'" | tr "[:upper:]" "[:lower:]")
do
    if [[ "$(echo "$word" | tr "$az" "$za" | rev)" = "$word" ]]
    then
        echo "$word"
    fi
done

It takes a few minutes to run on my computer. I won't spoil the results, but one of the words with the same property as wizard is sh.

Comments

Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.

There are no comments yet.