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
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.
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…
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!
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