在 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,6390fawk
的哪个版本在哪个平台上?我正在使用 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>/dev/null
隐藏它。如果您不想这样做,您可以像这样在 original-awk 的情况下剥离 -Wposix
:
awk $(awk -Wversion >/dev/null 2>&1 && printf -- "-Wposix") 'printf("%d\n","0x" $1)'
(在 Bash 4 中,您可以将 >/dev/null 2>&1
替换为 &>/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
加入p1
和p2
,同时将p2
添加一个后转换回十六进制。
【讨论】:
strtonum() 仅由 gawk 工作;不是通过例如 awk 可能符号链接到的 mawk。以上是关于在 awk 或 sed 中将十六进制转换为十进制的主要内容,如果未能解决你的问题,请参考以下文章