通过 shell 脚本在模式第 n 次出现后在第 n 个字符处插入一个新行

Insert a new line at nth character after nth occurence of a pattern via a shell script

我有一个单行大字符串,它以'~|~'作为分隔符。 10 个字段组成一行,第 10 个字段为 9 个字符。我想在每一行之后插入一个新行,这意味着在第 (9,18,27 ..) 次出现“~|~”

之后的第 10 个字符处插入一个 \n

是否有任何无需循环遍历字符串的快速单行 sed/awk 选项可用?

我用过

sed -e's/\(\([^~|~]*~|~\)\{9\}[^~|~]*\)~|~/\n/g'

但它会用一个新行替换每 10 次出现。我想保留分隔符,但在字段 10

的 9 个字符后添加一个新行
cat test.txt

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten1234562one~|~2two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten1234563one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten123456

sed -e's/\(\([^~|~]*~|~\)\{9\}[^~|~]*\)~|~/\n/g'  test.txt

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten1234562one
2two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten1234563one~|~3two
3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten123456

下面是我想要的

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten123456
2one~|~2two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten123456
63one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten123456

让我们试试 awk:

awk 'BEGIN{FS="[~|~]+"; OFS="~|~"}
     {for(i=10; i<NF; i+=9){
          str=$i
          $i=substr(str, 1, 9)"\n"substr(str, 10, length(str))
     }
     print [=10=]}' t.txt 

输入:

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten1234562one~|~2‌​two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten1234563one~|~‌​3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten123456

输出:

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten123456
2one~|~2‌​two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten12345
63one~|~‌​3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten123456

我假设您的评论中存在一些错误:如果您的输入包含 ten1234562one2ten1234563one,则必须在第一种情况下和之后的 2 之后插入换行符6 在第二种情况下(因为这是第十个字符)。但是您的预期输出与此不同。

您的 sed 脚本并不太差。这似乎可以完成您想要的工作:

sed -e '/^$/d' \
    -e 's/\([^~|]*~|~\)\{9\}.\{9\}/&\' \
    -e '/' \
    -e 'P;D' \
    data

对于你的输入文件(我称之为 data),我得到:

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten123456
2one~|~2two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten12345
63one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten12345
6

恐怕脚本需要一点解释。它使用一些晦涩的 shell 和一些晦涩的 sed 行为。晦涩的 shell 行为是在单引号字符串中,反斜杠没有特殊含义,因此第二个 -e 中第二个单引号之前的反斜杠在 sed 中显示为反斜杠争论的结尾。晦涩的 sed 行为是它将每个 -e 选项的参数视为一行。因此,尾部反斜杠加上第三个 -e 之后的 / 被视为有反斜杠、换行符、斜杠序列,这就是 BSD sed(和 POSIX sed) 要求您添加换行符。 GNU sed 将替换中的 \n 视为换行符,但 POSIX(和 BSD)表示:

The escape sequence '\n' shall match a <newline> embedded in the pattern space.

它没有说明 \ns/// 替换的替换部分中被视为 <newline>。因此,前两个 -e 选项结合起来在匹配的内容之后添加一个换行符。匹配的是什么?好吧,这是 'zero or more non-tilde, non-pipe characters followed by ~|~' 的序列,重复 9 次,然后是 9 'any characters'。这是您想要的近似值。如果您有一个字段,例如 ~|~tilde~pipe|bother~|~,正则表达式将失败,因为 'tilde' 和 'pipe' 之间的 ~ 以及 [=] 之间的 | 90=] 和 'bother'。修复它以处理所有可能的序列是非常重要的,并且样本数据不能保证。

脚本的其余部分是直截了当的:-e '/^$/d' 删除一个空行,如果数据的长度恰好正确,这很重要,而在 -e 'P;D' 中,P打印模式的初始段 space 到第一个换行符(我们刚刚添加的那个); D 删除模式的初始段 space 直到第一个换行符并重新开始。

我不认为这值得这么复杂。如果脚本在文件中,可能更容易理解,script.sed:

/^$/d
s/\([^~|]*~|~\)\{9\}.\{9\}/&\
/
P
D

命令行是:

$ sed -f script.sed data
one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten123456
2one~|~2two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten12345
63one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten12345
6
$

不用说,它产生相同的输出。如果没有 /^$/d,脚本只能工作,因为输入末尾有奇数 6。第三条记录后恰好 9 个字符,然后进入无限循环。

使用扩展正则表达式

如果你使用扩展的正则表达式,你可以处理中间包含 ~|(或者实际上是 ~|)的奇数字段。

script2.sed:

/^$/d
s/(([^~|]{1,}|~[^|]|~\|[^~])*~\|~){9}.{9}/&\
/
P
D

data2:

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten1234562one~|~2two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten1234563one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten12345666=beast~tilde|pipe~|twiddle~|~4-two~|~4-three~|~4-four~|~4-five~|~4-six~|~4-seven~|~4-eighty-eight~|~4-999~|~987654321

来自sed -E -f script.sed data2的输出:

one~|~two~|~three~|~four~|~five~|~six~|~seven~|~eight~|~nine~|~ten123456
2one~|~2two~|~2three~|~2four~|~2five~|~2six~|~2seven~|~2eight~|~2nine~|~2ten12345
63one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten12345
666=beast~tilde|pipe~|twiddle~|~4-two~|~4-three~|~4-four~|~4-five~|~4-six~|~4-seven~|~4-eighty-eight~|~4-999~|~987654321

那仍然无法处理像 tilde~~|~ 这样的字段。使用 -E 对于 BSD 是正确的 (Mac OS X) sed;它启用扩展的正则表达式。 GNU sed 的等效选项是 -r.