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

30

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

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

我需要将第三列按以下方式分成三部分:

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

2
你的意思是需要拆分第四列。 - jarno
2
如果你不喜欢 bash 脚本中的不必要的 fork,这里有一个解决方案:让十六进制值保存在 $f 中。然后十进制值是 $[0x"$f"]。不需要复杂的子 shell 执行 "$(printf "%d" 0x"$f")" 和类似操作。 - peterh
9个回答

32

这是对Jonathan答案的一个变化:

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的输出。关于文件的其他任何内容,例如其内容,都不会生效。只有名称 - 仅仅是间接的。


我相信在Bash 4.1中添加了将printf -v应用于数组元素的功能。 - Dennis Williamson
做得好,值得在“awk --version”后面添加“2>/dev/null”,因为“mawk”会打印一个带有“--version”的错误消息。 - mklement0
根据GNU Awk用户手册的建议,不推荐使用 --non-decimal-data。相反,添加 -Wposix选项似乎适用于Ubuntu Linux上所有可用的awk实现,例如mawk、gawk和original-awk。 - jarno
由于根据[GNU Awk用户手册](https://www.gnu.org/software/gawk/manual/html_node/Nondecimal-Data)的建议,不推荐使用“ -non-decimal-data”选项,因此替代方法是使用printf'%d' strtonum(“0x $ {line [3]:10:4}”) - user234461
@mklement0:我尝试了一下,这个语法似乎是“相对”可以的:::::::::::::[n/m/g]awk -Wv --version -Wversion::::::::::::::: mawk2输出mawk 1.9.9.6, 21 Aug 2016,…mawk1输出mawk 1.3.4 20200120nawk输出nawk: unknown option -Wv ignored awk version 20200816,而gawk则输出GNU Awk 5.2.1, API 3.2, (GNU MPFR 4.1.0-p13, GNU MP 6.2.1)….. - RARE Kpop Manifesto

14

前言

在这个回答中,我讨论了使用AWK进行十六进制数转换的一般方法,而不是特定于问题的情况。

在下面的示例中,给解释器提供的每条记录的第一个字段(即$1)将被转换。输入中只允许使用十六进制数字,而不包括"0x"前缀。

通过GNU Awk可以简单地转换任意大的十六进制值

如果编译gawk以使用GNU MPFR和GMP库,并且使用选项-M,它可以执行任意精度的算术运算。

gawk -M '{print strtonum("0x" $1)}'

通过AWK可移植地

根据GNU Awk用户指南,不建议在gawk中使用--non-decimal-data。而且据我所知,strtonum()只有gawk支持,因此让我们看看其他选择:

通过用户定义的函数

假设最可移植的转换方式是通过用户定义的awk函数[参考资料]:

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) }

注意:如果您的AWK解释器只支持32位整数,您可以通过将return(OUT)替换为return(sprintf("%.0f", OUT))来转换更大的十六进制数;我可以用这种方式将0x20000000000000 = 2^53进行转换。该函数忽略可能的"0x"前缀。
通过调用shell的printf函数,您可以使用以下方法:
awk '{cmd="printf %u 0x" $1; cmd | getline decimal; close(cmd); print decimal}'

但是它相对较慢,因为它需要启动一个子shell。如果你有很多以换行符分隔的十六进制数需要转换,下面这个方法会更快:
awk 'BEGIN{cmd="printf \"%u\n\""}{cmd=cmd " 0x" $1}END{while ((cmd | getline dec) > 0) { print dec }; close(cmd)}'

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

此外,这些方法对于可以转换的十六进制数的大小有限制。在我的系统中,我可以将0xFFFFFFFFFFFFFFFF = 2^64-1进行转换。

通过使用AWK的printf(或sprintf)

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

awk -Wposix '{ printf "%d\n", "0x" $1 }'

我在Ubuntu Linux 20.04中使用、和进行了测试。在这里,需要-Wposix-Wnon-decimal-data。其他实现可能会显示关于选项的警告消息,但您可以通过shell中的重定向指令2>/dev/null来隐藏它。如果您不想这样做,您可以像这样使其仅使用GNU Awk:-Wposix

awk -Wversion 2>/dev/null | ( unset -v IFS; read -r word _; [ "$word" = GNU ] && exit 0 || exit 1 ) && gawk_option="-Wposix" || gawk_option=""
awk $gawk_option '{ printf "%d\n", "0x" $1 }'

注意:再次实现或者你的解释器会限制可以通过这种方式转换的最大十六进制值。例如,在我的系统中,mawk的最大整数为2147483647;这在mawk -Wversion的标准错误输出中被告知(至少对于版本1.3.4)。你可以通过将printf "%d\n", "0x" $1替换为printf "%.0f\n", "0x" $1来转换更大的十六进制数;我可以用这种方式转换0x20000000000000 = 2^53;在我的经验中,使用Gawk时两种方式的限制是相同的。
如果你只想计算转换后的值
类似上面的方法,你可以使用显式转换:
awk -Wnon-decimal-data '{s="0x"$1; d=0+s}'
现在转换后的值存储在变量d中,但你可能需要使用一些格式化来输出它。

2
这在“original-awk”中有效,因为它可以在没有-W posix的情况下工作,除非是gawk;gawk需要-W posix。这包括Debian系统上的mawk 1.3.3以及FreeBSD 7.3的awk 20070501和FreeBSD 11.2的awk 20121220。 - Adam Katz
1
@jarno: 你可以将它简化为::::::::::::::::::::::::::::: :::::::::::::::: :::::::::::: ::::::::::::: ::::::::::::::::::: ::::::::::::: echo 0xEDCFAB | mawk '$++NF = +$!_' ——> 0xEDCFAB 15585195。对于更大的输入,使用echo 0xEDCFAB9877787 | mawk '$++NF = +$!_' CONVFMT='%.250g' 0xEDCFAB9877787 4183619086546823。如果你坚持要用gawk但又想避免使用gawk -n,那么可以使用gawk -P - RARE Kpop Manifesto
1
@RAREKpopManifesto 哦,我注意到我的答案在所有实现中都无法处理更大的输入。我会尝试改进我的答案。 - jarno
@RAREKpopManifesto echo 0xFFFFFFFFFFFFFF | mawk '$++NF = +$!_' CONVFMT='%.250g' 打印出一个偶数,这是错误的,所以即使使用这种方法,如果输入足够大,也必须小心。由于Awk在这种情况下似乎使用浮点转换,因此重要的是分析它可以准确处理多大的数字。 - jarno
@jarno:我说的是更大,而不是“大整数”。 对于这些,您需要使用gawk -nMbe。 小于16个十进制数字,mawk就可以了,但如果超过这个范围,它会遇到与其他人相同的限制:2 ^ 53-1 - RARE Kpop Manifesto
显示剩余4条评论

11

这个似乎可行:

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;
         }'

当字符串以0x为前缀时,当被呈现给printf()%d格式时,会强制转换为整数。


上面的代码在MacOS X 10.6.5(版本20070501)的本地awk上非常出色;可惜,在GNU gawk 3.1.7上不起作用。根据POSIX的规定,似乎这是允许的行为(请参见下面的注释)。然而,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);
          }'

我在最后两列中得到了零值。 123711184642,02,3583090366663629,639f02,0,0 123715942138,01,3538710295145500,639f02,0,0 123711616258,02,3548370476972758,639f02,0,0 - bernie
在哪个平台上使用哪个版本的awk?我正在使用MacOS X 10.6.5和它的awk-版本为20070501;当我使用gawk 3.1.7时,它会给出零。这值得向GNU提交错误报告。我将着手解决问题... - Jonathan Leffler
2
@bernie:如果您使用“--non-decimal-data”选项,第一个版本将与gawk一起使用。 - Dennis Williamson
1
POSIX说这是实现特定的。 - Dennis Williamson
@Dennis:哎呀,这就是最糟糕的情况!基本上,有些人做了,有些人没做,而且没有人能够下定决心。抱怨。 - Jonathan Leffler
显示剩余4条评论

3
printf "%d\n", strtonum( "0x"$1 )"

1
仅适用于 gawk 的工作。 - jarno

1
这可能适用于您(GNU sed和printf):

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

把最后八个字符分开,用十六进制标识符前导字段添加空格,然后使用printf评估整行。


0

--- 我的五分钱

如果这个话题仍然有兴趣,我想要加上我的五分钱。从帖子中的评论来看,似乎还是有人关注的。希望能对你有所帮助:

挑战:在运行最新 MacOS(2022)的 Apple M1 笔记本电脑上将十六进制数转换为十进制数,具体版本如下:

% uname -a
Darwin macbook 22.1.0 Darwin Kernel Version 22.1.0: Sun Oct  9 20:15:09 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T6000 arm64 arm Darwin

% gawk --version
GNU Awk 5.2.1, API 3.2, (GNU MPFR 4.1.0-p13, GNU MP 6.2.1)
Copyright (C) 1989, 1991-2022 Free Software Foundation.

--- 需要 gawk -Wposix

% echo "116B" | gawk '{p = ("0x" substr($1, 1, 4)) +0; printf("%d\n", p )}'
0

% echo "116B" | gawk -Wposix '{p = ("0x" substr($1, 1, 4)) +0; printf("%d\n", p )}'
4459

--- 有些简化也可以生效

% echo "116B" | gawk -Wposix '{p = "0x" substr($1, 1, 4); printf("%d\n", p )}'
4459

% echo "116B" | gawk -Wposix '{printf("%d\n", "0x" substr($1, 1, 4))}'
4459

--- 正在检查...

% echo "4459" | gawk '{printf("%X\n", $1 )}'
116B

--- 这个表单就是我要找的

% echo "00:11:6BX" | gawk -Wposix '{printf("%d\n", "0x" substr($1, 1, 2) substr($1, 4, 2) substr($1, 7, 2))}'
4459

0

这应该是比perl,python或printf更简洁的方法:

echo 0x7E07E30EAAC59DB8EB9FDAD2EE818EA7AEB70192DAE552AD06B9FE
       593BE89BC258483EA07C972B0FE7BA0D7B6CAC6DF338571F49CABB
       DD195629411CDF0F88858EC39F01AE181E60A4F0DAF5F4F0E86991
       82243BDF159AB588F11E3FF68E799509128EA7BA957B62DF103D0E
       B2C3195DA1CCDFDD0CAF0E9958C1AF3E2B6993AA74C255B711BE38
       DB031B26A596EFE19051A864000FB99F161923F12C2F9F40F18B6E
       064CCCAE4C0776D0EB815947A30AB68B1CF12CA6622CAECA530221
       2C27FD1579178363FE2E87B1F02FC0FDFFF | 
gawk -nMbe '$++NF = +$!_' OFS='\n\n' 
 1  0x7E07E30EAAC59DB8EB9FDAD2EE818EA7AEB70192DAE552AD06B9FE
      593BE89BC258483EA07C972B0FE7BA0D7B6CAC6DF338571F49CABB
      DD195629411CDF0F88858EC39F01AE181E60A4F0DAF5F4F0E86991
      82243BDF159AB588F11E3FF68E799509128EA7BA957B62DF103D0E
      B2C3195DA1CCDFDD0CAF0E9958C1AF3E2B6993AA74C255B711BE38
      DB031B26A596EFE19051A864000FB99F161923F12C2F9F40F18B6E
      064CCCAE4C0776D0EB815947A30AB68B1CF12CA6622CAECA530221
      2C27FD1579178363FE2E87B1F02FC0FDFFF

 2  985801769662049290799836483751359680713382803597807741
      342261221390727037343867491391068497002991150267570021
      888625408701957708383236015057159917981445085171196540
      056449671723413767151987807183076995694938175592905407
      706727043644590485574826597324100590757487981303537403
      481578192766548120367625144822345612103264180960846560
      558546717739085751660018602037450619797709845938562717
      870137791128285871274530893277287577788311030033741131
      093413810677239057304751530532826551215693481438241043
      55789791231

如果你想知道,这个数字是另一个梅森素数的幂:

8191 ^ 127 

它附近的两个质数应该是

  •  8191 ^ 127 - ( 16 + 512 )
    
  •  8191 ^ 127 + (     1450 )
    
  •  8191 ^ 127 - ( 16 + 512 )
    
  •  8191 ^ 127 + (     1450 )
    

那个 gawk 命令看起来有点晦涩,但它仍然有效,因为 -n 选项仍然存在;我会使用 strtonum() - jarno
1
@jarno:gawk -nMbe '$++NF = +$!_' OFS='\n\n'::::-n 标志用于自动解码十六进制和八进制;-M 标志通过 gnu GMP 实现大整数运算;-b 标志是字节码(启动和处理速度更快,特别是在不需要 Unicode 的情况下); $++NF = 是将输出分配到右侧最后一个字段之后的新字段中,而不是覆盖输入的任何部分;…… - RARE Kpop Manifesto
1
@jarno:+$!_+$1相同,这与$1 + 0的冗长形式相同。由于输入已经以0x7E07E3….的形式给出,因此这基本上相当于执行$(NF+1) = +"0x7E07E3….",它将该十六进制数的十进制整数分配到新字段中,然后打印整个内容。最后,OFS='\n\n'表示我希望输出打印在不同的行上,而不是附加在同一行上,并在它们之间留有一个空行。 - RARE Kpop Manifesto
1
@jarno:strtonum() 的问题在于它无法处理负的十六进制输入,例如 "-0xABCD",但是 -n 可以正确地处理它。 - RARE Kpop Manifesto

0
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”将向 p2 分配102十进制数值。 最后,我使用 printf 将 p1 和 p2 连接起来,并在添加1后将 p2 转换回十六进制。

1
strtonum() 只能在 gawk 中使用;而不能在例如 awk 可以链接到的 mawk 中使用。 - jarno

-1

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, 将自动分割分隔符更改为 ,(默认为空格)
由于 Perl 数组从 0 开始,因此 substr() 索引比其 awk 等效索引少 1。

输出:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接