通过 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~|~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~|~2ten12345
63one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten123456
我假设您的评论中存在一些错误:如果您的输入包含 ten1234562one
和 2ten1234563one
,则必须在第一种情况下和之后的 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.
它没有说明 \n
在 s///
替换的替换部分中被视为 <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
.
我有一个单行大字符串,它以'~|~'作为分隔符。 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~|~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~|~2ten12345
63one~|~3two~|~3three~|~3four~|~3five~|~3six~|~3seven~|~3eight~|~3nine~|~3ten123456
我假设您的评论中存在一些错误:如果您的输入包含 ten1234562one
和 2ten1234563one
,则必须在第一种情况下和之后的 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.
它没有说明 \n
在 s///
替换的替换部分中被视为 <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
.