龙空技术网

shell代码模拟演示UTF-8是如何进行动态字节编码和解码

西红柿武士 256

前言:

而今同学们对“shell 16进制输出”大致比较关注,咱们都想要知道一些“shell 16进制输出”的相关内容。那么小编在网上汇集了一些对于“shell 16进制输出””的相关内容,希望咱们能喜欢,咱们一起来学习一下吧!

“Hello世界2021”这句话包含了英文大小写字母,中文和数字,使用了UTF-8的编码规则后,前面的英文字母用一个字节表示,中文用3个字节表示,而数字和英文字母一样都是一个字节表示。我在《底层都是0和1,UTF-8可变长编码是如何让计算机看见中文字编码的》视频中介绍了UTF-8利用了定位符的模版,根据Unicode的代码的范围,选择相应字节的编码模版进行数据加载。

UTF-8根据Unicode的段位来对应模版和字节长度

为了更深入的了解UTF-8的动态编码和解码的规则,我尝试用shell编写了脚本程序和函数,目的是为模拟和演示规则,同时和大家分享一些编程的思维。同时这篇文章是对视频中的内容做补充和验证,也再次感谢小伙伴们的反馈意见,认为视频可以着重讲原理,文章比较适合详解代码。

编程之前需要理清的思路是:

语言:shell (不是因为效率,而是方便,任何电脑都有,不用IDE)

流程:(编码)字符串 -> Unicode (16进制)-> 二进制 -> 加载UTF-8模版 -> UTF-8编码 (16进制)

(解码)UTF-8编码 (16进制)-> 二进制 -> 提取有效数据

(解码的步骤没有到最终的字符串,因为关键是从UTF-8的模版中提取有效数据,后面的步骤就是进制的转化,可以留给小伙伴们自己去练习。

关键命令:

bc :主要是进制的转化,尤其是16进制到2进制的转化。

printf:是格式打印的神器,可以非常方便的转化各种字符的Unicode。

${string:position:span}:这是shell中字符串处理的又一神器,比函数还好用,在这里,我只用了提取出字符串中的字符的功能,还有很多替换和处理字符串的功能有待日后展示。

接下来将逐步解析核心步骤的算法

1,将字符串编码Unicode的16进制

UTF-8处理的字符串的中间码是16进制的,所以第一步就是将字符串“Hello世界2021”的对应的Unicode码打印出来。shell内置命令的printf的格式参数“%x” / “%X”,就可以将单个字符的的Unicode打印出来,两者的区别在于,第一个所有的16进制中的英文字母为小写,后者为大写。为了提取字符串的单个字符,一般的高级语言使用的都是字符串函数,或者类,而shell也有自己的特殊表达式${string:position:span},将string为待处理的字符串:冒号后为起始的字符串下标位置,以0开始(和c语言一样):第二个冒号后为提取的字符数,在这里,我们每次需要提取一个字符,所以第二个参数为1。还没有结束,还需要告诉printf,我们的用意是打印字符的Unicode码,所以需要在每个字符前加一个【’】单引号,这样printf就会识别你的用意是打印单个字符的Unicode,否则将返回0。

最后的核心代码是这样的:

string=“Hello世界2021”

printf “%X” “‘${string:0:1}”

当然这个还不够,为了获取所有的字符串的Unicode码,需要将核心代码包含在for循环中,遍历提出所有的字符

UTF-8编码的主函数程序

2,加载UTF-8的模版信息

UTF-8是根据Unicode的不同的段位来选择模版进行编码的,大部分的英文字母和数字都是单字节的,不需要进行编码,比较麻烦的两个字节以上的字符,因为2字节的都是西文字符,比如拉丁文,西班牙文,中东文字等,我们平时主要使用的是英文字母,数字和中文,所以除了单字节的,主要就是3字节的模版,因为中文字符在Unicode 0800 ~ FFFF段位中,所以这里的脚本程序主要展示3字节的模版处理。

既然之前已经获得字符串“Hello世界2021”Unicode,现在就需要进行比较,找出每个字符所在的Unicode段位。但是有个问题,shell程序的测试函数只能进行10进制的整数计算,如果你不想调用复杂的bc命令的话。所以需要利用$((16#digit))转化16进制为10进制数字

hex = \u4e16 #将Unicode赋值给hex变量

int = $((16#$hex)) # 利用赋值计算将16进制转化为10进制整数

[ $int -ge 2048 ] && [ $int -le 65535 ] # 测试目标Unicode是否在3字节的模版段位中

三字节的模版长的这样 1110 xxxx 10xx xxxx 10xx xxxx

“世” = \u4e16 = 0100 111000 010110

现在需要把目标字符串的2进制码从高位到低位的顺序依次替换x标示的位置

同样利用${string: position: span}语句将目标数据的二进制码分为

p1=0100

p2=111000

p3=010110

然后使用printf 将这些二进制数据和模版组装起来,最后就是这样

printf “%s%s%s%s%s%s” “1110” “$p1” “10” “$p2” “10” “$p3”

这样就得到了模版组装好的二进制代码11100100 10111000 10010110

UTF-8三字节编码模版信息加载函数

3, 格式输出

虽然底层都是0和1,但是还是需要将这些0和1转化成标准的16进制表达式,并在之前加上\x用于区别10进制的数字。这里我使用了bc命令,

binary = 111001001011100010010110

echo “obase=16; ibase=2; $binary” | bc

得到E4B896

但是这还不是最终的输出,为了让计算机识别是16进制的,还需要在每一组的16进制的数字之前加上\x, 所以我编了一个函数addx

最终输出\xE4\xB8\x96

UTF-8编码16进制格式化输出

4,解码的过程就是编码的反向操作,16进制到2进制

将16进制变为2进制,还是需要使用bc命令

hex=\xE4\xB8\x96 (当然在提供16进制数字给bc前,需要去除\x,因为bc只能识别16进制数字,对于16进制辅助标识符号会解析错误的,利用sed的替换命令很轻松就可以进行数据净化

echo “obase=2; ibase=16; E4B896” | bc

得到:11100100 10111000 10010110

UTF-8解码脚本主程序

5, 提取数据

之前我们知道中文的UTF-8编码是三个字节,在高位字节以1110开头,之后的两个字节都是以10开头。这里我们利用一个技巧${string#pattern}, 这个表达式会把符合pattern的字符串从源字符串中删除,只留下剩余的字符串,如果我们把UTF-8的三字节的定位符号1110和10放在pattern中,就可以筛选出在模版中的数据信息了,比如

binary = 11100100

pattern=1110

${binary#$pattern} 过滤定位符号后得到 0100

binary = 10111000

pattern=10

${binary#$pattern} 过滤定位符号后得到111000

binary = 10010110

pattern=10

${binary#pattern} 过滤定位符号后得到010110

最后利用printf 将 过滤出来的有效信息按顺序拼接起来的到0100111000010110

UTF-8解码脚本提取模版有效信息函数

6,格式输出

最后一步还是格式输出,利用bc命令将有效数据转化为16进制

binary = 0100111000010110

echo “obase=16; ibase=2; 0100111000010110” | bc

得到 Unicode 4E16

演示的脚本程序只提取了UTF-8编码的2进制代码,因为提取有效数据是核心,是关键,后面的格式转化非常简单。

UTF-8解码脚本格式化输出,脚本选项参数设置

通过函数和脚本程序我们深入理解了UTF-8的编码规则和在2进制数据流中如何通过定位符找到目标信息进行准确的提取。真实的UTF-8编码过程一般用C语言编写在命令中以获得高的效率,但是编码和解码的过程都是相似的,UTF-8通过变长字节编码的方式极大的节省了内存空间,而且为网络数据传播提供了很好的方式,当然这是明文编码,没有任何的加密。希望这篇文章对大家的UTF-8可变字节编码的理解有进一步的帮助,此文章配合视频《底层都是0和1,UTF-8可变长编码是如何让计算机看见中文字编码的》效果更佳!有喜欢的朋友欢迎点赞的加关注,敬请期待下一个分享!

视频加载中...

标签: #shell 16进制输出