12

I would like to reverse the operation performed by the following bash command:

$ echo $((62#a39qrT))
9207903953

i.e. convert decimal 9207903953 to base 62, keeping bash standard of {0..9},{a..z},{A..Z}.

I know I can do this by using bc, but I will have to manually convert each character then. For example, I do this currently:

BASE62=($(echo {0..9} {a..z} {A..Z}))
for i in $(echo "obase=62; 9207903953" | bc)
do
    echo -n ${BASE62[$i]} #Doesn't work if bc's output contains leading zeroes
done

There must be a way to do this in a less 'hackier' way. Do you know of a way to do this more efficiently?

EDIT: changed bc input.

agc
  • 7,973
  • 2
  • 29
  • 50
Ram
  • 1,161
  • 1
  • 11
  • 34
  • See also [Binary to hexadecimal and decimal in a shell script](https://unix.stackexchange.com/questions/65280/binary-to-hexadecimal-and-decimal-in-a-shell-script), and [BASH base conversion from decimal to hex](https://unix.stackexchange.com/questions/191205/bash-base-conversion-from-decimal-to-hex). Both have answers that are more general than those titles suggest. – agc May 12 '17 at 15:58

4 Answers4

12

I do really appreciate the solution you came up with, and I guess there's no way around it straight with bash. Here's the little point you've missed:

BASE62=($(echo {0..9} {a..z} {A..Z}))
for i in $(bc <<< "obase=62; 9207903953"); do
    echo -n ${BASE62[$(( 10#$i ))]}
done && echo

Output:

a39qrT
Rubens
  • 14,478
  • 11
  • 63
  • 92
  • 2
    The key point is to eliminate the leading zeroes and force base 10, using `$$((10#$i))`. The here-string `$(bc <<< "...")` is fine but not mandatory. – Edouard Thiel Oct 14 '14 at 07:40
  • 2
    Here's the symmetrical base62 decode function: `function base62_decode() { echo $((62#$1)) }` – jsears Apr 08 '16 at 14:11
  • @jsears this doesnt work if the input is too long, you have two's compliment which returns a negative number rather than the correct decimal number. Do you have an alternative to this approach to covert from base-62 to decimal? – qodeninja Sep 26 '19 at 05:24
4
function base62encode() {
  bc<<<"obase=62;$1" | awk '{for (i=1; i<=NF; i++) printf "%c", $i+(($i<10)?48:(($i<36)?87:29))}'
}
  • bc<<<"obase=62;$1" converts to a sequence of space prefixed decimal numbers from 00 to 61
  • then offset each digit into ASCII table and convert to character with awk's printf

Or without the for loop:

function base62encode() {
  bc<<<"obase=62;$1" | awk 'BEGIN{RS=" +"}/./{printf "%c", $1+(($1<10)?48:(($1<36)?87:29))}';
}
Bob
  • 31
  • 2
Bob
  • 41
  • 1
  • If the editor Bob and the original author Bob are the same: you should use the same account, then you can just edit your answer. If not: the edit should have been a comment, in my opinion. – Benjamin W. Sep 30 '16 at 16:52
2

Or without bc and with arbitrary base:

function baseXencode() {
  awk 'BEGIN{b=split(ARGV[1],D,"");n=ARGV[2];do{d=int(n/b);i=D[n-b*d+1];r=i r;n=d}while(n!=0);print r}' "$1" "$2"
}
function base62encode() {
  baseXencode 0123465789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "$1"
}
Bob
  • 31
  • 2
  • 1st example doesn't work correctly. e.g. `baseXencode 10 8` returns `0111` – arielf Jul 22 '18 at 01:59
  • You specified an inverted base-2 number, it actually works as expected. – astorga Oct 20 '20 at 11:48
  • hey man perfectcode ALMOST :) 01234 **65** 789 better reverse this if anyone wants to use this to test if it works `echo $((62#$(base62encode 1624429385)))` should output the same as the input ;D – trycatch Jun 23 '21 at 06:47
1

Arbitrary base10 to baseX conversion function using gforth, and tr, (tr is needed since gforth and bash use different chars to print bases):

n2b() { gforth -e "$1 $2 base ! . cr bye" | tr '[0-9A-z]' '[0-9a-zA-Z]' ; }
n2b 9207903953 62
n2b 9207903953 61   # Also works with other bases.
n2b 9207903953 3

Output:

a39qrT 
aT1PM8
212202201021222121202
agc
  • 7,973
  • 2
  • 29
  • 50