![Bash:将 100,000 多个字符转换为十进制格式?](https://linux22.com/image/178240/Bash%EF%BC%9A%E5%B0%86%20100%2C000%20%E5%A4%9A%E4%B8%AA%E5%AD%97%E7%AC%A6%E8%BD%AC%E6%8D%A2%E4%B8%BA%E5%8D%81%E8%BF%9B%E5%88%B6%E6%A0%BC%E5%BC%8F%EF%BC%9F.png)
我正在寻找一种快速且不那么占用 CPU 资源的解决方案,将 100,000 多行文本转换为十进制格式。
# random ascii
string='QPWOEIRUTYALSKDJFHGZMXNCBV,./;[]75498053$#!@*&^%(*'
convert () {
for ((b=0; b<${#string}; b++ ))
do
# convert to dec, append colon character, add to array
arr+=$(printf '%d,' "'${string:$b:1}");
done;
# show array contents
printf '%s' "${arr[@]::-1}"
}
time convert
上面的方法适用于短线,任务在一秒钟之内完成:
$ ./stackexchange.sh
81,80,87,79,69,73,82,85,84,89,65,76,83,75,68,74,70,72,71,90,77,88,78,67,66,86,44,46,47,59,91,93,55,53,52,57,56,48,53,51,36,35,33,64,42,38,94,37,40,42
real 0m0.059s
user 0m0.032s
sys 0m0.016s
但对于包含许多字符的文件来说,这不是一个可行的解决方案。下面的函数会导致我的 CPU 峰值,并且基本上无法完成任务。好吧,几分钟后我按 Ctrl+c 将其停止。这是带有修改后的变量的相同脚本string
。
# random ascii
string="$(cat /tmp/100000-characters.txt)"
convert () {
for ((b=0; b<${#string}; b++ ))
do
arr+=$(printf '%d,' "'${string:$b:1}");
done;
printf '%s' "${arr[@]::-1}"
}
time convert
我也尝试了 while 循环。它成功地转换了 100,000 个字符的文件,但仍然需要很长时间才能完成。
string="$(cat /tmp/100000-characters.txt)"
convert () {
# iteracte through each like
while read -r -n1 char; do
arr+=$(printf '%d,' "'$char");
done <<< "$string"
printf '%s' "${arr[@]::-3}"
}
time convert
是否有一个优雅/简单的解决方案将大量文本文件转换为冒号分隔的十进制值?
答案1
答案2
您的绝大多数时间都花在构建数组上,而您似乎只是为了删除末尾的冒号。相反,尝试仅使用标志并避免完全构建数组,这会明显更快:
#!/bin/bash
string='QPWOEIRUTYALSKDJFHGZMXNCBV,./;[]75498053$#!@*&^%(*'
convert() {
local first=1
for ((b=0; b<${#string}; b++ )); do
(( first )) && first=0 || printf ,
printf '%d' "'${string:$b:1}"
done
}
time convert
这是比较时间。首先,您的初始解决方案包含 1000 个字符:
real 0m0.454s
user 0m0.439s
sys 0m0.057s
这个解决方案有 1000 个字符:
real 0m0.148s
user 0m0.147s
sys 0m0.001s
这与仅使用内置命令进入 bash 的速度差不多。如果可以的话,我强烈建议您购买更好的工具来处理这个问题,比如上面的 perl。
答案3
hexdump
这就是/的用途od
:
<input hexdump -ve '/1 ",%u"' | tail -c+2
例如。
请注意,它打印每个字节的值,而不是像您的方法那样打印每个字符的代码点 1 值。在仅包含 ASCII 字符的示例中,这不会产生任何影响。
要获取 unicode 代码点,您可以先将输入转换为 UCS4。比较:
$ printf %s 'Stéphane' | hexdump -ve '/1 ",%u"' | tail -c+2
83,116,195,169,112,104,97,110,101
$ printf %s 'Stéphane' | iconv -t ucs-4le | hexdump -ve '/4 ",%u"' | tail -c+2
83,116,233,112,104,97,110,101
在具有小端处理器 (x86) 的 UTF-8 语言环境中,查看第一种方法如何转储é
(U+00E9) 字符的 UTF-8 编码的两个字节(195 和 169),第二种方法如何打印 233 (0xe9) )。
1bash
的printf
内置函数通常会在具有单字节字符映射的语言环境中打印字节值,因此给出相应字符集中的代码点,对于多字节字符映射,将给出在大多数系统上是 Unicode 代码点的宽字符值
答案4
假设字符串存储在名为的文件中,file
我们可以使用Python评估每个字符的序数。
$ python3 -c 'import sys
with open(sys.argv[1]) as fh: s=fh.read()
print(*[ord(c) for c in s.rstrip("\n")], sep=",")
' ./file
file 被放入字符串变量 s 中。
使用列表理解构造,我们逐个字符地循环字符串,并使用内置函数评估每个字符串的序数
ord()
,然后将它们累积在一个匿名列表中,该列表以逗号作为列表分隔符打印。
或者,我们可以使用 Linux 实用程序的 gnu 版本xargs+grep
。这最大限度地减少了对 printf 的调用次数
$ printf '%s\0' "$string" \
| grep -oz '.' \
| xargs -r0 printf "'%s\\0" \
| xargs -r0 printf '%d\n' \
| paste -sd,