0

I'm trying to sort out a bash command to convert a number from base 10 to an arbitrary base, using a specified set of characters (for example, to base 26 using letters a-z, although that's not actually my use-case).

I have solved this problem before, but not in bash (which I have limited experience in), and not in quite some time.

Any ideas?

Hate9
  • 21
  • 5
  • 2
    Easier if you use zsh: https://unix.stackexchange.com/a/616227/70524 – muru Jun 27 '23 at 13:48
  • @muru zsh is unfortunately not an option. – Hate9 Jun 27 '23 at 14:39
  • Please [edit] your question and show an examples with input and expected output. Showing your solution in a different language might also help. You could solve this with `bc` or (GNU) `dc`. Example conversion of number `1234567890` to base 26 assuming `a` = value 0, `b` = 1 etc. `echo 26 o 1234567890 p | dc | awk '{ for(i=1; i<=NF; i++) printf("%c",$i+97) } END { printf("\n") }'` – Bodo Jun 27 '23 at 15:11
  • This is one of those things that would be more sanely done in pretty much any other programming language; the shell languages are mostly about running commands and really aren't made for general-purpose number-crunching or data mangling. (though Bash does support the conversion in the other direction out of the box, using prefixes like `12#` for base 12 in front of the numbers. E.g. `echo $(( 12#100 )) $(( 12#bb ))` prints `144 143`. Not that it helps much.) – ilkkachu Jun 27 '23 at 15:25
  • Related: [BASH base conversion from decimal to hex](https://unix.stackexchange.com/q/191205) – Stéphane Chazelas Jun 27 '23 at 16:38

2 Answers2

3

I had something similar lying around, so I polished it up a bit for you

#!/bin/bash
#
# usage: convert_base <number-to-convert> <output-base> <output-digit-string>
#
# example:
#   $ convert_base 3735928559 16 "0 1 2 3 4 5 6 7 8 9 A B C D E F"
#   DEADBEEF

decimal_number=$1
output_base=$2
read -ra output_digits <<< "$3"

# TODO various assertions

if ((decimal_number == 0)); then
    output=${output_digits[0]}
else
    while ((decimal_number > 0)); do
        digit=$(( decimal_number % output_base ))
        output="${output_digits[digit]}$output"
        decimal_number=$(( decimal_number / output_base ))
    done
fi

printf '%s\n' "$output"
glenn jackman
  • 84,176
  • 15
  • 116
  • 168
0

Maybe stating the obvious, but bash's printf builtin can do base 8 and 16:

$ printf '%o\n' 1234
2322
$ printf '%#o\n' 1234
02322
$ printf '%x\n' 1234
4d2
$ printf '%#x\n' 1234
0x4d2
$ printf '%X\n' 1234
4D2
$ printf '%#X\n' 1234
0X4D2

Even hexadecimal floats:

$ LC_ALL=C printf '%a\n' 1234.56
0x9.a51eb851eb851ecp+7
$ LC_ALL=C printf '%A\n' 1234.56
0X9.A51EB851EB851ECP+7

(here using LC_ALL=C to make sure the decimal radix character is .)

But it doesn't do other bases for which you'd need more advanced shells such as zsh or ksh93 or do it by hand.

To store the result in a variable:

printf -v oct %o 1234

is more efficient than:

oct=$(printf %o 1234)

Which in bash (contrary to ksh93 or the sh of some BSDs) means forking a process and send the result through a pipe.

For those who're not stuck with bash, some alternatives with some other shells can be found at:

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501