TOC

uu 编码(Unix-to-Unix Encode)

uu 编码是加州大学伯克利分校 1980 年为在邮件中传输二进制文件而设计的一种 B2T(Binary-To-Text)/ B2A(Binary-To-Ascii)编码。
九十年代,MIME 被设计出来并成为国际标准之后,uu 编码就正式退出了历史舞台。

uu 代表 Unix-to-Unix Copy,表示将一个文件从一个 Unix 系统传输到另一个 Unix 系统。

示例

$ echo -n 'Cat' > /tmp/cat.txt
$ cat /tmp/cat.txt
Cat%
$ uuencode /tmp/cat.txt -
begin 664 -
#0V%T
`
end

字符集

类似 Base64,不过字符不一样。
选择的是 ASCII 96(```), 33 - 95,这 64 个字符。

` ! " # $ % & ' ( ) * + , - . /
0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O
P Q R S T U V W X Y Z [ \ ] ^ _

每 6 位的值,加上 32,得到的 ASCII 字符就是 uu 编码字符。
有一种例外,这六位的值是 0,加上 32 得到的是空格。这种时候改用反引号(```)代替。
这个例外好像是一个拓展,Python uu 编码默认就用的空格。

格式

begin <mode> <file><newline>
<length character><formatted characters><newline>...
`<newline>
end<newline>

例如:

$ uuencode /bin/pip /tmp/a
begin 755 /tmp/a
M(R$O=7-R+V)I;B]P>71H;VXS"B,@+2HM(&-O9&EN9SH@=71F+3@@+2HM"FEM
M<&]R="!R90II;7!O<G0@<WES"F9R;VT@<&EP+E]I;G1E<FYA;"YC;&DN;6%I
M;B!I;7!O<G0@;6%I;@II9B!?7VYA;65?7R`]/2`B7U]M86EN7U\B.@H@("`@
M<WES+F%R9W9;,%T@/2!R92YS=6(H<B(H+7-C<FEP=%PN<'EW?%PN97AE*3\D
I(BP@(B(L('-Y<RYA<F=V6S!=*0H@("`@<WES+F5X:70H;6%I;B@I*0H`
`
end
$ python3 -c "from codecs import encode;print(encode(b'Cat', 'uu'))"
b'begin 666 <data>\n#0V%T\n \nend\n'
$ python2 -c 'print("Cat".encode("uu"))'
begin 666 <data>
#0V%T

end
  1. 每行的长度算出来之后 + 32,所在位置的 ASCII 字符,就是 <length character>
    比如说如果这一行有 3 个字节,采用 ASCII 字符 35 对应的字符 # 作为这一行的开头。
    其实,因为字符集选择的原因,这个字符也就是 uu 字符集中的对应序号(0 开始排序)的字符。
  2. 每行最多 45 个字节,编码之后 60 个字符。
    因此,大多数时候行首表示长度的字符都是 M

填充

$ echo -n Cate | uuencode /dev/null
begin 664 /dev/null
$0V%T90``
`
end

相较于上面的例子,长度字符从井号变成了美元符号,末尾多了 90 和两个反引号。
长度从 3 变成 4,所以井号变成美元符号。
小写字符 e 的二进制表示位 0110 0101,后面需要填充两个零字节来补齐:

01100101 00000000 00000000   # 8bit
011001 010000 000000 000000  # 6bit
25     16     0      0       # number
9      0      `      `       # uu char

对 Base64 的兼容

$ echo -n Cat | uuencode -m /dev/null
begin-base64 664 /tmp/a
Q2F0
====
$ uuencode -m /bin/pip /dev/null
begin-base64 755 /dev/null
IyEvdXNyL2Jpbi9weXRob24zCiMgLSotIGNvZGluZzogdXRmLTggLSotCmlt
cG9ydCByZQppbXBvcnQgc3lzCmZyb20gcGlwLl9pbnRlcm5hbC5jbGkubWFp
biBpbXBvcnQgbWFpbgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAg
c3lzLmFyZ3ZbMF0gPSByZS5zdWIociIoLXNjcmlwdFwucHl3fFwuZXhlKT8k
IiwgIiIsIHN5cy5hcmd2WzBdKQogICAgc3lzLmV4aXQobWFpbigpKQo=
====

Python uu

uu.encode(in_file, out_file, name=None, mode=None, *, backtick=False)

Uuencode file in_file into file out_file. The uuencoded file will have the header specifying name and mode as the defaults for the results of decoding the file. The default defaults are taken from in_file, or '-' and 0o666 respectively. If backtick is true, zeros are represented by '`' instead of spaces.

Changed in version 3.7: Added the backtick parameter.

uu.decode(in_file, out_file=None, mode=None, quiet=False)

This call decodes uuencoded file in_file placing the result on file out_file. If out_file is a pathname, mode is used to set the permission bits if the file must be created. Defaults for out_file and mode are taken from the uuencode header. However, if the file specified in the header already exists, a uu.Error is raised.

decode() may print a warning to standard error if the input was produced by an incorrect uuencoder and Python could recover from that error. Setting quiet to a true value silences this warning.
import uu
import io

content = b'This is some binary data that we want to UUEncode.'

in_file = io.BytesIO(content)
out_file = io.BytesIO(b'')
uu.encode(in_file, out_file, backtick=True)
out_file.seek(0)
# print(out_file.read().decode(), end='')
print(out_file.read())

# b'begin 666 -\nM5&AI<R!I<R!S;VUE(&)I;F%R>2!D871A(\'1H870@=V4@=V%N="!T;R!5545N\n%8V]D92X`\n`\nend\n'

# begin 666 -
# M5&AI<R!I<R!S;VUE(&)I;F%R>2!D871A('1H870@=V4@=V%N="!T;R!5545N
# %8V]D92X`
# `
# end