我正在尝试创建一个sed
命令,以便将长度超过 3 位的数字转换为十六进制。即像这样的字符串124 3275 7535
应该产生124 0xccb 0x1d6f
.这是我目前拥有的:
sed 's@\([0-9]\{4,\}\)@sh -c "printf 0x%x \1"@ge'
但是当字符串不匹配时,它会尝试将未更改的字符串作为外部命令运行,因此对于上面的示例字符串,我得到
sh:1:124:未找到
我怎样才能实现我想要做的事情(最好仍然使用sed
)?
答案1
虽然根据您的问题标题,它不是“with sed”,但如果您从 sed 切换到 perl,您可以使用等效的表达式,例如
perl -p -e 's/\b\d{4,}\b/sprintf "%#x", $&/ge'
这应该允许您或多或少地保留链中的其他表达式。
答案2
GNU 实现的命令e
标志是评估s
sed
模式空间的内容应用替换(成功)后,并用其输出替换模式空间,而不是评估代换。
在这里,对于如下输入:
foo 1234 123
您需要替换才能产生包含以下内容的模式空间:
printf %s 'foo '
printf 0x%x 1234
printf %s ' 123'
通过 shell 命令e
将其转换为标志。foo 0x3d2 123
这并非不可能,例如:
LC_ALL=C sed -E "
/[0-9]{4}/!b # optimisation
s/'/&\\\\&/g
s/[0-9]{4,}/'\nprintf 0x%x &\nprintf %s '/g
s/.*/printf %s '&'/e"
但这是相当尴尬的,并且意味着每个匹配的输入行运行一个 shell。即使不使用 GNUism,您也可以这样做:
LC_ALL=C sed "
s/'/&\\\\&/g
s/[0-9]\{4,\}/'\\
printf 0x%x &\\
printf %s '/g
s/.*/printf %s '&\\
'/" | sh
哪个会运行一 sh
。
另外,像这样将任意数据评估为 shell 代码往往会让我感到紧张。例如,如果没有上面的 LC_ALL=C,就会构成任意命令执行漏洞。例如,尝试类似以下输出的内容:
printf '0000\200; echo GOTCHA>&2\n'
在 UTF-8 语言环境中。
在这里,您宁愿使用类似的东西perl
:
perl -pe 's/\d{4,}/sprintf "0x%x", $&/ge'
perl
的e
标志更符合您的期望。它确实将替换评估为代码(并且不会像 GNU那样perl
每次都启动新的 perl 解释器)。sed
e
答案3
awk
是为确切地这种类型的广谱文本操作。请注意,无需通过管道连接到任何辅助工具。
awk '{ for( fn=1;fn<=NF;fn++ ){
fmat=(length($fn)>3)?"0x%x":"%s"
dlim=(fn==NF?"\n":" ")
printf( fmat dlim, $fn )}}' <<<'124 3275 7535'
输出,根据您的示例:
124 0xccb 0x1d6f
答案4
我赞同 Peter.O 在评论中所说的话:这是一种bash
方法(它需要在每个数字的末尾):
echo '124 3275 7535 ' | while read -d ' ' x; do [ ${#x} -ge 4 ] && printf "0x%x " $x || printf "%d " $x; done
如果您的输入流在行末尾没有 a (从您的示例来看),那么
sed
会派上用场:
echo '124 3275 7535' | sed 's/$/ /' | while read -d ' ' x; do [ ${#x} -ge 4 ] && printf "0x%x " $x || printf "%d " $x; done