在没有定界符的固定长度编码记录中的字段之间插入空格的 sed 脚本?

A sed script that inserts spaces between field in a fixed length encoded record without delimiters?

我有这样的记录

000000000011111111112222222222333444555666777888999aaabbbcccdddeee

没有分隔符,我想将其读入 bash 脚本数组。如果有分隔符,我可以说

IFS='|' record=($line)

到此为止。但是当字段完全填充时,没有分隔符。

所以我想我应该制作一个快速的 sed 脚本

IFS='|' record=( $( echo "$line" |sed 's/\(...\)/ /g' ) )

这将 - 在这种情况下 - 在 3 个字符的等长字段之间放置一个 space 分隔符。

但我的字段宽度不同!

IFS='|' record=( $( echo "$line" |sed 's/\(.\{10\}\)\(.\{10\}\)\(.\{10\}\)\(.\{3\}\)\(.\{3\}\)\(.\{3\}\)/      /g' ) )

简单!但在我的情况下不可能,因为我有超过 9 个字段!

我想 sed 必须有某种方法来控制 s/.../.../g 行为,这样你就可以做 3 x 10 的宽度,然后 10 乘以 3 的宽度,或者任何字段长度。但我真的不记得这是如何用 sed 完成的,而且众所周知,手册页没有教育意义。

我想我可以做一个循环,然后用 read -n $width 读取每个字段并建立我自己的数组。这就是我要做的,但我更喜欢一次性的方式。如果在 shell 环境中有一个可用的 scanf(1) 命令,就像有一个 printf(1) 命令一样,或者如果 bash read -a 有一个 -n 10,那就真的很容易了,10,10,3,3,3* 格式字符串或类似的东西。

使用 GNU awkFIELDWIDTHS 引入定界符的一个想法:

x='000000000011111111112222222222333444555666777888999aaabbbcccdddeee'

awk '
BEGIN { OFS="|"                                              # define output delimiter
        FIELDWIDTHS = "10 10 10 3 3 3 3 3 3 3 3 3 3 3 3"     # define width of each field
      }
      { =                                                # force an evaluation so that fields are parsed
        print 
      }
' <<< "${x}"

这会生成:

0000000000|1111111111|2222222222|333|444|555|666|777|888|999|aaa|bbb|ccc|ddd|eee

从这里您可以使用 | 分隔数据执行您想要的操作(例如,读入数组)。

备注:

  • FIELDWIDTHS 只是 一个变量,因此要处理具有可变数量字段的输入,您当然可以概括此 awk 脚本并传入一个字符串定义 FIELDWIDTHS
  • 对于此示例,我们使用了单个变量 (x),但我们可以轻松地将文件提供给 awk 脚本以向所有输入行添加分隔符
  • 如果在几个地方需要,将其包装在用户定义的函数中应该很容易

您可以使用 Perl 的 unpack 函数。

对于少量记录,您可以这样做(使用示例行):

IFS='|' record=($(perl -ple '$_=join"|",unpack"(a10)3(a3)12"' <<<"$line"))

因为它为每一行运行一个新的 perl 进程,如果你有很多,那么沿着以下行包装一个循环会更有效:

perl -ple '$_=join"|",unpack"(a10)3(a3)12"' inputfile |\
while IFS='|' read -ra record; do
    : process ${record[@]}
done

(假设固定宽度的记录由换行分隔)

因为你想创建一个 bash 数组,使用像 sed 这样的外部工具是次优的。您必须解析输入两次,首先使用外部工具,然后 bash。在 bash.

中做所有事情更安全、更高效,而且可能更容易

Bash 的内置 [[ 可以使用 =~ 匹配正则表达式。匹配的组存储在数组 BASH_REMATCH:

printf -v regex '(.{0,%s})' 10 10 10 3 3 3 3
line=000000000011111111112222222222333444555666777888999aaabbbcccdddeee
[[ "$line" =~ ^$regex ]]
fields=("${BASH_REMATCH[@]:1}")

这将忽略超出指定字段的字符,如果指定字段超出该行,则保留(部分)空数组条目。但您可以根据自己的需要进行调整。

如果您从右到左工作,此 sed 应该会产生示例数据的预期结果。

$ array=($(sed -E 's/(.{3})/ /g10;s/(.{10})/ /2;s/(.{10})/ /' input_file))

s/(.{3})/ /g10 - 这将处理最初从第 30 个字符开始的 10x3 宽度,每第 3 个字符插入一个 space。

s/(.{10})/ /2;s/(.{10})/ /开头剩余的 30 个字符现在将再次从右到左拆分为 3x10。

回显创建的数组,结果如下

$ echo ${array[@]}
0000000000 1111111111 2222222222 333 444 555 666 777 888 999 aaa bbb ccc ddd eee