我有以下(gnu)sed
脚本,用于解析另一个sed脚本,并在单独的行上输出不同的命令。
简而言之,此脚本应在每个分号;
后面添加换行符,但匹配或替换命令中的分号除外。
Sed脚本:
#!/bin/sed -rf
# IDEA:
# replace ';' by ';\n' except when it's inside a match expression or subst. expression.
# Ignored patterns:
/^#/b # commented lines
/^$/b # empty lines
# anything in a single line, without semicolon except at the end
/^[^\n;]*;?$/b
# Processed patterns (put on separate lines):
# Any match preceding a semicolon, or the end of the line, or a substitution
s_/^[^/]+/[^;s]*;?_&\n_; t printtopline
s/^\\(.)[^\1]+\1[^;s]*;?/&\n/;t printtopline
# Any substitution (TODO)
# Any other command, separated by semicolon
s/\;/\;\n/; t printtopline;
:printtopline
P;D; # print top line, delete it, start new cycle
例如,我使用以下文件对其进行了测试(实际上是根据我以前的sed问题的answer @ one改编的:
输入文件:
#!/bin/sed -f
#/^>/N;
:A;
/\n>/!{s/\n/ /;N;bA}; # join next line if not a sequence label
#h;
#s/\(.*\)\n.*/\1/p;
s/^>//g;P
#x;
#s/.*\n//;
D
bA;
以上脚本生成正确的输出,例如,行/\n>/!{s/\n/ /;N;bA}; # join next line if not a sequence label
变为:
/\n>/!{s/\n/ /;
N;
bA};
# join next line if not a sequence label
但是,你能帮助我理解为什么这部分剧本有效:
s/\;/\;\n/; t printtopline;
:printtopline
吗
在我看来,分支命令t printtopline
在这里毫无用处。我认为无论取代成功与否,接下来要执行的事情都是:printtopline
。
但是,如果我注释掉t
命令,或者我将其替换为b
,则该脚本会生成以下输出行:
/\n>/!{s/\n/ /;
N;bA}; # join next line if not a sequence label
来自info sed
,以下是t
的解释:
't LABEL' Branch to LABEL only if there has been a successful 's'ubstitution since the last input line was read or conditional branch was taken. The LABEL may be omitted, in which case the next cycle is started.
为什么 t
命令后面紧跟其标签的行为根本不像没有命令或b
命令?
答案 0 :(得分:4)
关键部分是:
分支到标签只有在读取了最后一个输入行或获取条件分支后才成功替换 。
即。 t
查看过去,并考虑到最近所有替换的成功,直到最近
考虑您要问的输入行。在完成所有替换之后
/\n>/!{s/\n/ /;
N;bA}; # join next line if not a sequence label
当我们到达P;D;
时,在我们的模式空间中。 P
命令输出第一行,然后D
删除第一行并重新启动主循环。现在我们只有
N;bA}; # join next line if not a sequence label
请注意,这并不涉及阅读任何其他行。没有输入; D
刚删除了部分模式空间。
我们处理剩余的文本(因为没有其他模式匹配),直到我们到达代码的这一部分为止:
s_/^[^/]+/[^;s]*;?_&\n_; t printtopline
替换失败(模式空间不包含/^
)。但t
命令不检查这一s
命令的状态;它会查看自最近的输入或条件分支以来所有替换的历史记录。
读取/\n>/!{s/\n/ /;N;bA};
时发生了最近的输入。
最近采取的条件分支是
s/\;/\;\n/; t printtopline;
:printtopline
在原始版本的代码中。从那时起,没有其他替换成功,因此t
命令不执行任何操作。该计划的其余部分按预期继续进行。
但是在代码的修改版本中,此时没有条件分支(b
是无条件分支):
s/\;/\;\n/; b printtopline;
:printtopline
这意味着来自t
"的s_/^[^/]+/[^;s]*;?_&\n_; t printtopline
看到" s/\;/\;\n/;
已成功,因此立即跳转到P;D;
部分。这就是输出
N;bA}; # join next line if not a sequence label
未修饰的。
总结:t
在这里有所作为,不是因为它跳转到标签的直接影响,而是因为它作为下一个t
被执行的动态分隔符。如果此处没有t
,则会在下一个s
中考虑先前执行的t
命令。
答案 1 :(得分:0)
比较这两个命令的输出:sed 's/;/;\n/'
和sed 's/;/;\n/; P;D;'
。
<强>首先强>
$ sed 's/;/;\n/' <<< 'one;two;three;four'
one;
two;three;four
<强>第二强>
$ sed 's/;/;\n/; P;D;' <<< 'one;two;three;four'
one;
two;
three;
four
为何与众不同?我会解释一下。
第一个命令仅替换第一次出现的;
字符。要替换所有匹配项,应将g
修饰符添加到s
命令:sed 's/;/;\n/g'
。
第二个命令以这种方式工作:
sed 's/;/;\n/;
- 与第一个命令相同 - 没有区别。在此命令之前,模式空间为one;two;three;four
,后面是one\ntwo;three;four
。P;
-
one
。模式空间保持不变:one\ntwo;three;four
D;
-
:“如果模式空间不包含换行符,请启动正常的新循环,就像 d 命令是 发行。否则,删除模式空间中的文本直到第一个换行符,然后重新启动 使用生成的模式空间循环,而不读取新的输入行。“
在我们的例子中,模式空间有换行符 - one\ntwo;three;four
。 D;
删除one\n
部分并从头开始重复所有命令。现在,模式空间为:two;three;four
。
sed 's/;/;\n/;
- 模式空间:two\nthree;four
,然后P;
- 打印two
,模式空间不变:two\nthree;four
,{{1} } - 删除D;
,模式空间变为:two\n
。等等。我查看了three;four
源代码并找到了下一个信息:
当sed
命令正在执行并且匹配时,s
标志设置为true:
replaced
如果/* We found a match, set the 'replaced' flag. */
replaced = true;
标志为true,则t
命令正在执行。它正在将此标志更改为replaced
:
false
因此,在第一个,case 't':
if (replaced)
{
replaced = false;
情况下,替换成功 - 因此,s/\;/\;\n/; t printtopline;
标志设置为true。然后,以下replaced
命令正在运行,并将t
标志更改回false。
在第二的情况下,如果没有replaced
命令 - t
,替换也会成功 - 因此,s/\;/\;\n/;
标志设置为true。
但是现在,这个标志被存储到下一个周期,由replaced
命令启动。那么,第一个D
命令出现在新周期 - t
中,它检查s_/^[^/]+/[^;s]*;?_&\n_; t printtopline
标志,看到标志是replaced
并跳转到标签{ {1}},省略标签前的所有其他命令。
模式空间没有换行符,因此true
序列只打印模式空间并使用新的输入行开始下一个循环。