最近工作中常涉及到编码转换的问题,比如发送URL的时候需要进行urlencode,上传内容的时候要对MD5进行BASE64的转换。当debug的只能看到8或者16进制的编码而想不到实际的字符是什么,就让人非常烦躁,所以总想有个什么快速的方式能帮助自己进行编码转换 。
最快的方式,应该就像小时候被乘法口诀表一样把他们的对应关系一一记住,但是可能年龄大了记住了东西,外加现在越来越依赖技术带来的便利所以总是不能也不愿意记这些进制关系,一碰到这些问题就Google搜索一下。但这显然这是很 浪费时间的。
如果是用Linux或者Mac系统,用Terminal的话就能带来很多的帮助,比如man ascii
就会显出中ASCII在8、10和16进制下所有编码值了。这样子看到一个8或16进制的数,也能很快的找到对应的字符是什么了。不过这还是不过快,毕竟人眼查这样一张有序的表并不会进行二分查找。而且我们希望是能够在O(1)时间复杂度就能得到结果方法。
本文就总结怎么用printf的格式化在Bash环境中实现进制的转换,如果有那个比较好用或常用的,就可以添加到profile中在启动时载入使用。
10进制转8或16进制
这个转换比较简单,%o
和%x
(或%X
)两个格式化字符就分别对应输出8进制或者16进制数。所以很快就能得到如下的转换方法:
[codesyntax lang=”bash” lines=”normal”]
function d2o() { for num in $@; do printf '%-8s%8o\n' $num $num done } function d2h() { for num in $@; do printf '%-8s%8x\n' $num $num done } function d2H() { for num in $@; do printf '%-8s%8X\n' $num $num done }
[/codesyntax]
转换16进制有两个版本,他们的区别只在于输出的A-F
是大写还是小写。下边是测试结果:
ider$ d2o `seq 7 21` 7 7 8 10 9 11 10 12 11 13 12 14 13 15 14 16 15 17 16 20 17 21 18 22 19 23 20 24 21 25 ider$ d2h `seq 7 21` 7 7 8 8 9 9 10 a 11 b 12 c 13 d 14 e 15 f 16 10 17 11 18 12 19 13 20 14 21 15
唯一的不足的地方可能就是传入的参数数字不能以0
和0x
(或0X
)开头,因为那样printf
会将他们当做是8
和16
进制数字,也就不需要转换了。
ider$ d2o 021 21 021 21 21 25 ider$ d2h 0X21 21 0X21 21 21 15
8和16进制转10进制
前面说道如果数字以0
开头,就会被当做是8
进制数进行处理,而%d
格式化字符则会把数字以10进制输出,所以只要在数字前加0
就可以让printf
实现8
进制到其它进制的转换了。
[codesyntax lang=”bash” lines=”normal”]
function o2d() { for num in $@; do printf '%-8s' $num if [[ $num != 0* ]]; then num=0$num fi printf '%8d\n' $num done }
[/codesyntax]
条件语句只在检查输入是否已经是以0
开头,如果没有加上去以表示数字是8
进制。但如果数字中有大于7
的,那么转换就会出错了:
o2d `seq 1 10` 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 -bash: printf: 08: invalid number 0 9 -bash: printf: 09: invalid number 0 10 8
对于16
进制也一样,只要在数字前加上0x
就可以了,大小写通吃。
[codesyntax lang=”bash” lines=”normal”]
function h2d() { for num in $@; do printf '%-8s' $num if [[ $num != 0x* ]] && [[ $num != 0X* ]]; then num=0x$num fi printf '%d\n' $num done }
[/codesyntax]
ider$ h2d A B C D E F ABCDEF A 10 B 11 C 12 D 13 E 14 F 15 ABCDEF 11259375
数值转字符
Bash中的printf和C语言中的printf在格式字符上基本上是一样的,但是对于其它参数的处理方式还是有些不同,毕竟Bash中变量其实是没有类型的,只在特定的表达式和语境之下才可能变成了数值类型,一般的它们都只是不需要引号的字符串而已。
C语言中,%c
表示将参数值以对应的ASCII字符输出,但是在Bash中对%c
在printf
上的处理方式却不同,查看man printf
可以看到它是这样表述的:
c The first character of argument is printed.
也就是说它只输出第一个字符,其它的全部忽略,即使是数值也只当字符串而已:
[codesyntax lang=”bash” lines=”normal”]
ider$ printf '%c %c\n' 21 721 2 7
[/codesyntax]
所以%c就无法帮助完成数字到字符的转换了。好在printf
除了格式字符,还有转义字符可以用。对于\num
,printf
会将num
以8进制数计算找到ASCII码中相应的字符输出出来,所以就变成了把数值转换成8进制,然后添加\后由printf
输出
[codesyntax lang=”bash” lines=”normal”]
function d2c() { for num in $@; do oct=$(printf '%o\n' $num) printf $num\\t\\$oct\\n done }
[/codesyntax]
ider$ d2c `seq 65 90` 65 A 66 B 67 C 68 D 69 E 70 F 71 G 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W 88 X 89 Y 90 Z
字符转数值
反向转换也会有问题,C语言中对%d
、%o
和%x
格式字符可以让字符输出对应的ASCII值,但是Bash中并没有单独的字符,一个字符也是个字符串,Bash中的printf也不会直接将字符转换成ASCII码上的值。回看手册,可以看对printf
的参数有这样的解释:
The arguments after the first are treated as strings if the corresponding
format is either c, b or s; otherwise it is evaluated as a C constant,
with the following extensions:
• A leading plus or minus sign is allowed.
• If the leading character is a single or double quote, the value
is the ASCII code of the next character.
看到第二条,可以知道要想让printf
输出字符的ASCII码值,就需要在字符前加上一个单引号('
)或双引号("
)。
[codesyntax lang=”bash” lines=”normal”]
function c2d() { for str in $@; do for (( i=0; i<${#str}; i++ )); do c=${str:$i:1} printf '%-8c%8d\n' $c "'$c" done done }
[/codesyntax]
测试一下:
ider$ c2d abcd 0123 a 97 b 98 c 99 d 100 0 48 1 49 2 50 3 51
其它转换
除了8、10和16进制外,2进制也是十分常用的,不过一般Debug或开发也不太会看到2进制出现,那是给机器阅读的。Bash的printf
也不支持2进制格式的输出,不过perl的printf
倒是提供了%b格式字符,来输出数值的2进制编码。
[codesyntax lang=”bash” lines=”normal”]
function d2b() { for num in $@; do perl -e 'printf "%-8d%8b\n",'"$num, $num" done }
[/codesyntax]
在Bash环境下其实还有很多其它工具和指令可以实现进制的转换,比如在算术运算中,可以通过BASE#NUMBER
的格式来指定数字所使用的进制,这样就可以将任意进制的数转成10进制表示了:
ider$ echo $((2#1010)) 10 ider$ echo $((8#721)) 465 ider$ echo $((32#Ider)) 603611
可以用bc指令设置输入和输出的进制来实现转换
ider$ echo 'ibase=10;obase=16;721' | bc 2D1
还有什么使用的进制转换工具,欢迎留言分享。
泉水,奋斗之路越曲折,心灵越纯洁。