## uudecode implemented entirely in bash

Posted on July 31st, 2009 by whinger. Filed under **Tech**.

With the internet being full of silly but interesting things I would have thought this would be an easy find but I couldn’t find it anywhere – a couple of people asking about it with some vague bits of code here and there but most of the time people would say something dumb like “use (awk|perl|uudecode)”

Not because I think it’s particularly useful or sensible (it certainly isn’t that, since it takes around 200 times as long as the C version!) but just to see if it were possible, here’s my bash uudecode implementation.

#!/bin/bash bs=0 while read -rs t ; do if [ $bs -eq 1 ] ; then if [ "a$t" = "aend" ] ; then bs=2 else x=1 i=($(printf "%d " "'${t:0:1}" "'${t:1:1}" "'${t:2:1}" "'${t:3:1}" "'${t:4:1}" "'${t:5:1}" "'${t:6:1}" "'${t:7:1}" "'${t:8:1}" "'${t:9:1}" "'${t:10:1}" "'${t:11:1}" "'${t:12:1}" "'${t:13:1}" "'${t:14:1}" "'${t:15:1}" "'${t:16:1}" "'${t:17:1}" "'${t:18:1}" "'${t:19:1}" "'${t:20:1}" "'${t:21:1}" "'${t:22:1}" "'${t:23:1}" "'${t:24:1}" "'${t:25:1}" "'${t:26:1}" "'${t:27:1}" "'${t:28:1}" "'${t:29:1}" "'${t:30:1}" "'${t:31:1}" "'${t:32:1}" "'${t:33:1}" "'${t:34:1}" "'${t:35:1}" "'${t:36:1}" "'${t:37:1}" "'${t:38:1}" "'${t:39:1}" "'${t:40:1}" "'${t:41:1}" "'${t:42:1}" "'${t:43:1}" "'${t:44:1}" "'${t:45:1}" "'${t:46:1}" "'${t:47:1}" "'${t:48:1}" "'${t:49:1}" "'${t:50:1}" "'${t:51:1}" "'${t:52:1}" "'${t:53:1}" "'${t:54:1}" "'${t:55:1}" "'${t:56:1}" "'${t:57:1}" "'${t:58:1}" "'${t:59:1}" "'${t:60:1}")) l=$[${i[0]} -32 & 63 ] while [ $l -gt 0 ] ; do i0=$[${i[$[x++]]} -32 & 63] i1=$[${i[$[x++]]} -32 & 63] i2=$[${i[$[x++]]} -32 & 63] i3=$[${i[$[x++]]} -32 & 63] if [ $l -gt 2 ] ; then echo -ne "\0$[$i0 >> 4]$[$i0 >> 1 & 7]$[$i0 << 2 & 4 | $i1 >> 4]\0$[$i1 >> 2 & 3]$[$i1 << 1 & 6 | $i2 >> 5]$[$i2 >> 2 & 7]\0$[$i2 & 3]$[$i3 >> 3 & 7]$[$i3 & 7]" elif [ $l -eq 2 ] ; then echo -ne "\0$[$i0 >> 4]$[$i0 >> 1 & 7]$[$i0 << 2 & 4 | $i1 >> 4]\0$[$i1 >> 2 & 3]$[$i1 << 1 & 6 | $i2 >> 5]$[$i2 >> 2 & 7]" else echo -ne "\0$[$i0 >> 4]$[$i0 >> 1 & 7]$[$i0 << 2 & 4 | $i1 >> 4]" fi l=$[l-3] done fi elif [ "${t:0:5}" = "begin" ]; then bs=1 fi done

Note that I’ve used as few subprocesses as possible – I’ve got it down to one per line of input (the stupidly long “printf” line – more about that later) because the thing that bash does *really* badly is spawn off a subprocess: you can do a **huge** amount of fairly complex string and number manipulation in bash in place of a single subprocess spawn and it will still cut the time used massively. So for example I was using (as recommended across the web)

h=$(printf "%X" $d)

to convert decimal-to-hex: it’s about 10 times quicker (and actually not much less obvious) to create an array (0 1 2 3 4 5 6 7 8 9 a b c d e f) and build the hex string yourself using ${arr[d>>4]}${arr[d&15]} (I suppose using a 256 entry array would actually be quicker still but I gave up on the hex thing anyway to use octal)

The most interesting thing (fairly obvious, when you think about it) is the speed increase when you change from dripping through converting character by character

c = `printf "%d" "'$c"`

to the massive and horrible one-line-at-a-time printf above. You’re talking about a 15x speedup for the entire operation just by doing that.

Anyway, I found it all very challenging; I hope you find it useful/interesting :-).

Feel free to tell me what an idiot I am or (if you’re feeling more constructive) suggesting optimisations. If you can figure out a way of getting a character into a charcode integer without using a subprocess then obviously that would be *really* useful…

Edit: this awk-based implementation is probably more useful (it’s smaller and *lots* faster!) if you want to include a backup in your script for when uudecode isn’t installed

Sam Kimbrel Says:November 23rd, 2010 at 6:57 am

Bash 4.0 offers associative arrays. Assuming these are implemented in a reasonable fashion, you could use one to map chars to int values and get rid of the printfs.

whinger Says:November 24th, 2010 at 11:54 am

Thanks for the comment and nice idea!

So here’s some timings…

array version:

real 4m14.280s

user 3m46.110s

sys 0m27.410s

original (printf) version:

real 7m31.346s

user 4m30.580s

sys 2m41.460s

C uudecode:

real 0m0.146s

user 0m0.130s

sys 0m0.010s

(lol)

So yes, a massive speedup. I suppose you could test $BASH_VERSINFO[0] and do two different versions if it’s -ge 4…

Steve McIntyre Says:December 2nd, 2011 at 3:38 pm

Awesome, very useful code!

Just talked a friend through recovering a remote system using this. He managed to break his libc, leaving (thankfully) two root shells open.

Uuencode a copy of busybox-static then paste it into a shell function using your code, output over the top of an existing binary (to keep the execute bits). Painfully slow, but much better than the alternatives!

G.Baroncelli Says:February 12th, 2012 at 10:51 am

Hi All,

I managed your script because it didn’t work with the busybox shell (ash). Basycally:

– replaced the array with the “set” command and the $1, $2, $3 variables

– replaced the math evaluation $[..] with $((..))

I know that busybox has the uudecode command, but unfortunately in my case (DDWRT for DIR615) isn’t compiled.

Below the source

———————-

#!/bin/ash

bs=0

while read -rs t ; do

if [ $bs -eq 1 ] ; then

if [ “a$t” = “aend” ] ; then

bs=2

else

set $(printf “%d ” “‘${t:0:1}” “‘${t:1:1}” “‘${t:2:1}” “‘${t:3:1}” “‘${t:4:1}” “‘${t:5:1}” “‘${t:6:1}” “‘${t:7:1}” “‘${t:8:1}” “‘${t:9:1}” “‘${t:10:1}” “‘${t:11:1}” “‘${t:12:1}” “‘${t:13:1}” “‘${t:14:1}” “‘${t:15:1}” “‘${t:16:1}” “‘${t:17:1}” “‘${t:18:1}” “‘${t:19:1}” “‘${t:20:1}” “‘${t:21:1}” “‘${t:22:1}” “‘${t:23:1}” “‘${t:24:1}” “‘${t:25:1}” “‘${t:26:1}” “‘${t:27:1}” “‘${t:28:1}” “‘${t:29:1}” “‘${t:30:1}” “‘${t:31:1}” “‘${t:32:1}” “‘${t:33:1}” “‘${t:34:1}” “‘${t:35:1}” “‘${t:36:1}” “‘${t:37:1}” “‘${t:38:1}” “‘${t:39:1}” “‘${t:40:1}” “‘${t:41:1}” “‘${t:42:1}” “‘${t:43:1}” “‘${t:44:1}” “‘${t:45:1}” “‘${t:46:1}” “‘${t:47:1}” “‘${t:48:1}” “‘${t:49:1}” “‘${t:50:1}” “‘${t:51:1}” “‘${t:52:1}” “‘${t:53:1}” “‘${t:54:1}” “‘${t:55:1}” “‘${t:56:1}” “‘${t:57:1}” “‘${t:58:1}” “‘${t:59:1}” “‘${t:60:1}”)

l=$(($1 -32 & 63 ))

shift

while [ $l -gt 0 ] ; do

i0=$(($1 -32 & 63))

shift

i1=$(($1 -32 & 63))

shift

i2=$(($1 -32 & 63))

shift

i3=$(($1 -32 & 63))

shift

if [ $l -gt 2 ] ; then

echo -ne “$(($i0 >> 4))$(($i0 >> 1 & 7))$(($i0 <> 4))$(($i1 >> 2 & 3))$(($i1 <> 5))$(($i2 >> 2 & 7))$(($i2 & 3))$(($i3 >> 3 & 7))$(($i3 & 7))”

true

elif [ $l -eq 2 ] ; then

echo -ne “$(($i0 >> 4))$(($i0 >> 1 & 7))$(($i0 <> 4))$(($i1 >> 2 & 3))$(($i1 <> 5))$(($i2 >> 2 & 7))”

true

else

echo -ne “$(($i0 >> 4))$(($i0 >> 1 & 7))$(($i0 <> 4))”

true

fi

l=$(($l-3))

done

fi

elif [ “${t:0:5}” = “begin” ]; then

bs=1

fi

done