解释这个sed条件分支行为

时间:2017-10-27 18:43:25

标签: sed

我有以下(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命令?

2 个答案:

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

第1部分 - P; D;序列有效。

比较这两个命令的输出: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'

第二个命令以这种方式工作:

  1. sed 's/;/;\n/; - 与第一个命令相同 - 没有区别。在此命令之前,模式空间为one;two;three;four,后面是one\ntwo;three;four
  2. P; -
    • 来自man:“打印到当前模式空间的第一个嵌入换行符。”
    • 也就是说,它打印到第一个换行符 - one。模式空间保持不变:one\ntwo;three;four
  3. D; -

      来自man的
    • :“如果模式空间不包含换行符,请启动正常的新循环,就像 d 命令是       发行。否则,删除模式空间中的文本直到第一个换行符,然后重新启动       使用生成的模式空间循环,而不读取新的输入行。“

    • 在我们的例子中,模式空间有换行符 - one\ntwo;three;fourD;删除one\n部分并从头开始重复所有命令。现在,模式空间为:two;three;four

    • 也就是说,再次sed 's/;/;\n/; - 模式空间:two\nthree;four,然后P; - 打印two,模式空间不变:two\nthree;four,{{1} } - 删除D;,模式空间变为:two\n。等等。
  4. 第2部分 - 分支发生了什么。

    我查看了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序列只打印模式空间并使用新的输入行开始下一个循环。