在 awk 或 sed 中将十六进制转换为十进制

Posted

技术标签:

【中文标题】在 awk 或 sed 中将十六进制转换为十进制【英文标题】:Converting hex to decimal in awk or sed 【发布时间】:2011-06-04 15:02:40 【问题描述】:

我有一个数字列表,以逗号分隔:

123711184642,02,3583090366663629,639f02012437d4
123715942138,01,3538710295145500,639f02afd6c643
123711616258,02,3548370476972758,639f0200485732

我需要将第 3 列分成如下三列:

123711184642,02,3583090366663629,639f02,0124,37d4
123715942138,01,3538710295145500,639f02,afd6,c643
123711616258,02,3548370476972758,639f02,0048,5732

并将最后两列的数字转换为十进制:

123711184642,02,3583090366663629,639f02,292,14292
123715942138,01,3538710295145500,639f02,45014,50755
123711616258,02,3548370476972758,639f02,72,22322

【问题讨论】:

你的意思是你需要拆分第四列。 如果您不喜欢 bash 脚本中不需要的分叉,这里有一个解决方案:让 $f 中的十六进制值。那么十进制值为$[0x"$f"]。不需要复杂的子shell 执行"$(printf "%d" 0x"$f")" 和类似的东西。 【参考方案1】:

这是乔纳森回答的一个变体:

awk $([[ $(awk --version) = GNU* ]] && echo --non-decimal-data) -F, '
    BEGIN OFS = FS
    
        $6 = sprintf("%d", "0x" substr($4, 11, 4))
        $5 = sprintf("%d", "0x" substr($4,  7, 4))
        $4 = substr($4,  1, 6)
        print
    '

如果需要,我添加了一种相当扭曲的方式来添加 --non-decimal-data 选项。

编辑

顺便说一句,这里是纯 Bash 等价物:

saveIFS=$IFS
IFS=,
while read -r -a line
do
    printf '%s,%s,%d,%d\n' "$line[*]:0:3" "$line[3]:0:6" "0x$line[3]:6:4" "0x$line[3]:10:4"
done
IFS=$saveIFS

"$line[*]:0:3"(引用 *)与 AWK 的 OFS 类似,因为它会在输出的数组元素之间插入 Bash 的 IFS(此处为逗号)。我们可以通过插入以下数组元素来进一步利用该功能,这与我上面的 AWK 版本更接近。

saveIFS=$IFS
IFS=,
while read -r -a line
do
    line[6]=$(printf '%d' "0x$line[3]:10:4")
    line[5]=$(printf '%d' "0x$line[3]:6:4")
    line[4]=$(printf '%s' "$line[3]:0:6")
    printf '%s\n' "$line[*]"
done
IFS=$saveIFS

不幸的是,Bash 不允许printf -v(类似于sprintf())对数组元素进行赋值,所以printf -v "line[6]" ... 不起作用。

编辑:从 Bash 4.1 开始,printf -v 现在可以对数组元素进行赋值。示例:

printf -v 'line[6]' '%d' "0x$line[3]:10:4"

需要在数组引用周围加上引号以防止可能的文件名匹配。如果当前目录中存在名为“line6”的文件并且未引用引用,则将创建(或更新)名为 line6 的变量,其中包含 printf 输出。该文件的任何其他内容(例如其内容)都不会发挥作用。只有名称 - 只是切线。

【讨论】:

@bernie:我删除了一个错误的sprintf printf -v 添加到数组元素中,我相信,Bash 4.1。 干得好;值得将2>/dev/null 添加到awk --version,因为mawk 将打印带有--version 的错误消息。 根据GNU Awk User's Guide不推荐使用--non-decimal-data。或者,添加 -Wposix 选项似乎适用于 Ubuntu Linux 可用的所有 awk 实现,即 mawk、gawk 和 original-awk。 虽然 -Wposix 可能不适用于 OS X 和某些 BSD 操作系统中使用的 nawk,就像在 post 中所说的那样。【参考方案2】:
printf "%d\n", strtonum( "0x"$1 )"

【讨论】:

仅限gawk 使用。【参考方案3】:

这似乎有效:

awk -F, ' p1 =       substr($4,  1, 6);
           p2 = ("0x" substr($4,  7, 4)) + 0;
           p3 = ("0x" substr($4, 11, 4)) + 0;
           printf "%s,%s,%s,%s,%d,%d\n", $1, $2, $3, p1, p2, p3;
         '

对于您的示例输入数据,它会产生:

123711184642,02,3583090366663629,639f02,292,14292
123715942138,01,3538710295145500,639f02,45014,50755
123711616258,02,3548370476972758,639f02,72,22322

'0x' 加上 4 位十六进制的字符串连接,然后加上 0 强制 awk 将数字视为十六进制。

您可以将其简化为:

awk -F, ' p1 =      substr($4,  1, 6);
           p2 = "0x" substr($4,  7, 4);
           p3 = "0x" substr($4, 11, 4);
           printf "%s,%s,%s,%s,%d,%d\n", $1, $2, $3, p1, p2, p3;
         '

printf()%d 格式呈现时,前缀为0x 的字符串被强制为整数。


上面的代码与 MacOS X 10.6.5(版本 20070501)上的原生 awk 完美配合;遗憾的是,它不适用于 GNU gawk 3.1.7。这似乎是根据 POSIX 允许的行为(参见下面的 cmets)。然而,gawk 有一个非标准函数 strtonum 可用于强制其正确执行 - 可惜有必要强制执行。

gawk -F, ' p1 =      substr($4,  1, 6);
            p2 = "0x" substr($4,  7, 4);
            p3 = "0x" substr($4, 11, 4);
            printf "%s,%s,%s,%s,%d,%d\n", $1, $2, $3, p1, strtonum(p2), strtonum(p3);
          '

【讨论】:

我在最后 2 列中得到零。 123711184642,02,3583090366663629,639f02,0,0 123715942138,01,3538710295145500,639f02,0,0 123711616258,02,35483704769,72758,6390f awk 的哪个版本在哪个平台上?我正在使用 MacOS X 10.6.5 及其 awk - 版本 20070501;当我使用 gawk 3.1.7 时,它给出了零。这值得向 GNU 报告错误。我会想办法解决... 我在 Redhat Linux 2.6 和 SunOS 5.10 上使用 GNU Awk 3.1.5 得到了类似的结果 @bernie:如果您使用--non-decimal-data 选项,第一个版本将适用于gawk POSIX 说它是特定于实现的。【参考方案4】:

Perl 版本,向@Jonathan 致敬:

perl -F, -lane '$p1 = substr($F[3], 0, 6); $p2 = substr($F[3], 6, 4); $p3 = substr($F[3], 10, 4); printf "%s,%s,%s,%s,%d,%d\n", @F[0..2], $p1, hex($p2), hex($p3)' file

-a 开启自动拆分模式,以填充 @F 数组-F, 将自动拆分分隔符更改为 ,(默认为空格)substr() 索引比它们的 awk 等价物小 1,因为 Perl 数组从 0 开始。

输出:

123711184642,02,3583090366663629,639f02,292,14292
123715942138,01,3538710295145500,639f02,45014,50755
123711616258,02,3548370476972758,639f02,72,22322

【讨论】:

【参考方案5】:

通过 AWK

这个答案集中展示了如何通过 awk 便携式进行转换。

根据GNU Awk User's Guide,不建议将--non-decimal-data 用于gawk。而且使用strtonum() 是不可移植的。

在以下示例中,每条记录的第一个单词都会被转换。

通过用户自定义函数

最便携的转换方式是通过用户定义的 awk 函数 [reference]:

function parsehex(V,OUT)

    if(V ~ /^0x/)  V=substr(V,3);

    for(N=1; N<=length(V); N++)
        OUT=(OUT*16) + H[substr(V, N, 1)]

    return(OUT)


BEGIN  for(N=0; N<16; N++)
          H[sprintf("%x",N)]=N; H[sprintf("%X",N)]=N  

 print parsehex($1) 

通过调用shell的printf

你可以用这个

awk 'cmd="printf %d 0x" $1; cmd | getline decimal; close(cmd); print decimal'

但速度相对较慢。如果您有许多换行符分隔的十六进制数字要转换,则以下一个更快:

awk 'BEGINcmd="printf \"%d\n\""cmd=cmd " 0x" $1ENDwhile ((cmd | getline dec) > 0)  print dec ; close(cmd)'

如果为单个 printf 命令添加了很多参数,则可能会出现问题。

在 Linux 中

根据我的经验,以下在 Linux 中有效:

awk -Wposix 'printf("%d\n","0x" $1)'

我在 Ubuntu Linux 14.04 中通过 gawk、mawk 和 original-awk 对其进行了测试。通过 original-awk 命令显示警告消息,但您可以通过 shell 中的重定向指令 2&gt;/dev/null 隐藏它。如果您不想这样做,您可以像这样在 original-awk 的情况下剥离 -Wposix

awk $(awk -Wversion >/dev/null 2>&1 && printf -- "-Wposix") 'printf("%d\n","0x" $1)'

(在 Bash 4 中,您可以将 &gt;/dev/null 2&gt;&amp;1 替换为 &amp;&gt;/dev/null

注意:不过,-Wposix 技巧可能不适用于 OS X 和一些 BSD OS 变体中使用的 nawk。

【讨论】:

这在“original-awk”中有效,因为它在没有-W posix 的情况下也有效除非它是 gawk; gawk 需要-W posix。这包括 Debian 系统上的 mawk 1.3.3 以及 FreeBSD 7.3 的 awk 20070501 和 FreeBSD 11.2 的 awk 20121220。【参考方案6】:

这可能对你有用(GNU sed 和 printf):

sed -r 's/(....)(....)$/ 0x\1 0x\2/;s/.*/printf "%s,%d,%d" &/e' file

拆分最后八个字符并在字段前面添加空格,然后使用 printf 评估整行。

【讨论】:

【参考方案7】:
cat all_info_List.csv| awk 'BEGIN FS="|"print $21'| awk 'BEGIN FS=":"p1=$1":"$2":"$3":"$4":"$5":";  p2 = strtonum("0x"$6); printf("%s%02X\n",p1,p2+1) '

上述命令打印“all_info_List.csv”的内容,该文件的字段分隔符为“|”。 然后获取字段 21(MAC 地址)并使用字段分隔符“:”将其拆分。 它将每个 MAC 地址的前 5 个字节分配给变量“p1”,所以如果我们有这个 MAC 地址:“11:22:33:44:55:66”,p1 将是:“11:22 :33:44:55:"。 p2 分配有最后一个字节的十进制值:“0x66”会将“102”十进制分配给p2。 最后,我使用printf 加入p1p2,同时将p2 添加一个后转换回十六进制。

【讨论】:

strtonum() 仅由 gawk 工作;不是通过例如 awk 可能符号链接到的 mawk。

以上是关于在 awk 或 sed 中将十六进制转换为十进制的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Ruby 中将字符串或整数转换为二进制?

如何在Java中将非常大的十进制数转换为二进制数

在c#中将十进制值转换为两个精度[重复]

在python中将IPv4地址转换为十六进制

在 XSL 中将地理坐标从度-小时-分钟转换为十进制

严格使用乘法和除法在Java中将二进制数转换为十进制数