Bash:将 100,000 多个字符转换为十进制格式?

Bash:将 100,000 多个字符转换为十进制格式?

我正在寻找一种快速且不那么占用 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

Perl 来救援!

perl -nE 'say join ",", map ord, split //' < file
  • -n逐行读取输入并运行每行的代码
  • 分裂在空正则表达式//上将输入拆分为单个字符
  • 地图将每个字符映射到它的秩序
  • 加入从字符创建一个字符串,并在它们之间插入逗号
  • 输出结果

如果您不想逐行处理输入,可能需要进行更多调整。

答案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) )。


1bashprintf内置函数通常会在具有单字节字符映射的语言环境中打印字节值,因此给出相应字符集中的代码点,对于多字节字符映射,将给出在大多数系统上是 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,

相关内容