使用 sed 脚本:
#!/bin/sed -nf
: loop
H
s/\([^_]*_[^_]*\)_.*/\1/g
t clear_flag
: clear_flag
$! {
N
s/^\([^_]*_[^\n]*\)\n\(\1[^\n]*\)$/\2/
t loop
}
x
s/^\n//
s/\([^_]*_[^_]*\)_/\1=\1_/
s/\n/ /gp
s/.*//
x
D
我会尽力解释一切。首先,我们有一个循环将所有以相同前缀开头的文件连接在一起。我根据您的示例定义了一个前缀,它被定义为以第二个下划线结尾的字符串。循环由标签定义,使用“:”命令。在这里,我们将循环标记为“循环”。再往下,必要时,我们使用“t”测试命令“跳”回循环的开始。
第一个命令是将行附加到保持空间(辅助缓冲区)中。该行在附加之前由 sed 自动以换行符 ('\n') 为前缀。
第二个命令提取前缀。我们通过捕获一系列非下划线 ( [^_]*
) 的字符来做到这一点,然后是一个下划线,然后是更多的非下划线字符。因为此模式位于反斜线括号 (\(
和\)
) 之间,所以 sed 将捕获与此模式匹配的输入并保存到一个名为的辅助变量中\1
(因为它是该行上的第一个捕获)。然后我们跳过一个下划线,后跟一个任意字符序列。替换是我们捕获的,所以实际上我们只是删除了包括第二个下划线之后的所有内容。
我们现在使用一种解决方法来清除 seds 内部标志,该标志指示自上一个“t”命令或脚本启动以来是否发生了成功的替换。如果替换命令成功,测试命令(“t”)将分支(跳转)到标签,然后清除内部标志。这对于我们下面的第二个“t”命令是必要的。如果它成功或失败(即它是否分支),它仍然会在“clear_flag”标签之后继续执行。
现在我们使用“{”命令来启动一组命令。但是,我们在它之前有一个地址前缀,sed 使用它来确定它是否应该运行这些命令。在我们的例子中,只有在读取的最后一个输入行不是最后一行时才执行该组(美元符号“$”代表最后一个输入行,“!”代表否定)。
该组中的第一个命令会将输入中的下一行附加到当前模式空间(即工作缓冲区)中。上一行和新行由换行符 ( \n
) 分隔。
第三个命令将检查新读取的行是否以我们的前缀开头,并删除孤立的前缀(即前一行)。因为我们从前一行保留的前缀中删除了第二个下划线,并且因为我们附加了一个新行,所以隔离的前缀现在在换行符之前结束。因此,捕获的模式现在读取下划线后不是换行符 ( [^\n]*
) 的字符。在我们捕获隔离前缀后,我们跳过分隔前一行和新行的换行符,然后我们开始另一个捕获(将存储在\2
,因为它是这条线上的第二次捕获)。此捕获将(希望)与第二行匹配。希望因为我们要求匹配完全按照第一次捕获中匹配的内容开始(这就是为什么第二次捕获中的第一件事是对第一个 cature 的反向引用,即。\1
)。之后,我们匹配一个不是换行符的字符序列,并且在第二次捕获之后,我们期望行尾。
如果最后一个替换命令成功,我们发现新读取的行也有相同的前缀,所以我们现在必须跳回到循环的开头。这就是“t”命令的功能。它将测试自上一个“t”命令以来是否有任何替换命令成功,如果是,则分支到给定标签。在我们的例子中,我们分支(跳转)回“循环”标签。现在我们可以看到为什么我们需要以前的“t”解决方法。没有它,第一个替代命令可能会成功,而我们真正感兴趣的命令可能会失败,并且“t”仍会分支回“循环”标签。
如果它离开循环,则意味着新读取的行没有相同的前缀。因此,我们现在可以打印之前匹配的内容。
我们首先使用交换(“x”)命令将模式空间的内容与保持空间的内容交换。现在我们的模式空间包含所有具有相同前缀的文件,并且我们的保持空间包含当前前缀在一个孤立的行中,然后是第一个文件不共享相同前缀的行。
由于之前我们将所有文件名附加到保持空间,所有文件名都由换行符分隔,并且由于第一个文件名也被附加,当前模式空间中的第一个字节是换行符。要删除它,我们只需将其替换为空即可。
现在我们必须生成作业的格式。这就是为什么我们有一个熟悉的替代命令,我们再次提取前缀,除了现在我们已经删除了.*
以保持该行的其余部分完整。替换包括前缀(捕获的)、一个等号,我们还恢复了我们从模式空间中的第一个文件中删除的内容:它的前缀和它的下划线。
我们几乎准备好打印出该行,但文件名仍由换行符分隔。因此,我们g
用空格替换所有换行符(该标志告诉 sed 在输入行上尽可能多地重复替换命令)。由于现在该行已准备就绪,我们可以添加p
前缀来告诉 sed 打印它。
最后一步是为下一个前缀准备再次启动脚本。保留空间必须为空,以便用于存储具有新前缀的文件名。那就是我们有一个命令将模式空间中的每个字符都替换为空,然后是一个交换命令。
存放空间已准备就绪。现在我们必须准备模式空间。它必须仅包含带有新前缀的文件名的第一行。要处于这种状态,我们所要做的就是删除存储在第一行中的旧前缀。我们可以做一些事情,比如s/.*\n//
替换除最后一行的字符(包含带有新前缀的文件名)之外的所有字符,但是该D
命令将执行此操作并强制脚本再次开始执行而不读取另一行,所以它为我们节省了一些打字时间。
尽管脚本可能有点神秘并且描述过于繁琐,但一旦您了解发生了什么,它就会开始变得简单(r) =)
必须提到的一点:输入必须排序(或者至少具有相同前缀的文件必须组合在一起)。
希望这可以帮助!