Contents

SED编辑器指南

1. 概述

处理文本数据几乎总是涉及解析和修改它。 在本教程中,我们将了解sed ,这是一种用于编辑文本输入流的非交互式流编辑实用程序。

2. sed命令

好吧,整个想法是 sed 将输入视为文本流。

此外,在执行核心编辑操作之前,它会在Pattern的帮助下过滤线条:

[Pattern] Action

要定义模式,我们可以使用行号形式的固定地址或正则表达式形式的上下文地址

address[,address]

我们必须注意,逗号分隔的地址对允许我们选择一系列行。因此,选择从第一个地址与一行匹配时开始,一直持续到我们得到与第二个地址匹配的一行。

当我们在以下部分中解决编辑问题时,我们将详细探讨操作和模式。

然而,现在,让我们通过知道我们将使用函数来定义命令中的动作来解决我们的好奇心:

function[arguments]

3. sed在行动

让我们通过学习执行 sed 命令的不同方式将迄今为止获得的知识付诸实践。

3.1. 单行命令

首先,让我们看看如何从 Unix shell 执行单个 sed 命令:

sed [-Ealn] [-i extension] command [file ...]

除了修改文件的内容外,我们还应该能够查看它。让我们看看如何使用 print 函数并执行相应的 sed 命令:

% sed 'p' input.txt
line-1
line-1
line-2
line-2

我们可以看到,当显式使用 print 函数时,每一行都打印了两次。这是因为sed 总是在所有命令完成执行后执行打印每一行的默认操作。

此外,为了抑制这种默认行为,我们可以在同一命令中使用-n*标志*并控制一切:

% sed -n 'p' input.txt
line-1
line-2

但是,如果我们重新考虑一下,我们一开始并不真的想要打印功能,我们可以只依靠 sed 的默认操作来查看文件:

% sed '' input.txt
line-1
line-2

我们必须注意,虽然没有函数,但我们仍然必须包含一个空的命令字符串。

3.2. 多个命令

由于搜索是处理文本的典型,如果我们可以通过 sed 命令获得这种能力,那不是很好吗?是的,这很有可能!

所以,让我们继续使用 sed 从我们的input.txt文件中获取包含单词“line”的第一行:

% sed -n -e '/line/ p' -e '/line/ q'  input.txt
line-1

我们必须注意使用*-e*标志来分隔 sed 命令。第一个命令搜索模式并使用 print 函数打印该行。然而,第二个命令利用q, quit 函数停止在输入流上进一步执行 sed 命令。如果没有q函数,sed 将打印与正则表达式匹配的所有行。

或者,我们也可以用分号分隔多个命令

% sed -n '/line/ p; /line/ q'  input.txt
line-1

3.3. sed脚本

随着复杂性的增加,我们必须将问题分解为越来越多的子问题。

每个子问题由单个 sed 命令解决,并将它们放在一起解决整个用例。但是,如果我们仍然继续使用*-e*标志或分号,那么我们的代码可读性就会很差,从长远来看通常会产生负面影响。

假设在我们之前的 sed 实用程序中查找第一个匹配项,我们现在还想知道模式第一次匹配的行号。因此,我们可以通过使用=*命令显示行号*来扩展我们之前的解决方案。

此外,让我们通过将所有 sed 命令保存在find_first_match.sed脚本中来研究解决方案,并首先使用sed本身预览脚本的内容:

% sed -n 'p' find_first_match.sed
/line/ =
/line/ p
/line/ q

最后,让我们使用*-f*标志执行我们的脚本:

% sed -n -f find_first_match.sed input.txt
1
line-1

4. 搜索和替换

sed 提供了多种我们可以用来搜索和替换文本的函数。让我们探讨几个重要的。

4.1. 基本替代

假设我们的团队遵循使用空格进行代码缩进的约定。但是,我们经常最终使用标签。因此,我们的同行经常要求我们在代码审查周期中将制表符更改为空格。为了解决这个用例,我们可以使用替换函数:

[address[,address]]s/regular expression/replacement/flags

因此,替换函数在地址范围选择的每一行中搜索正则表达式。然后,它只用替换字符串替换第一个匹配的子字符串,除非我们通过使用标志来修改这个默认行为。

由于我们打算重用我们的解决方案,因此,让我们在名为indentation_fix.sed的脚本中添加我们的 sed 命令:

/^[ 	]\+/ s/	/    /g

好吧,脚本的内容包含一些制表符和空格,很难区分它们。因此,让我们通过使用 sed 的 l 命令查看脚本的内容来消除这种歧义:

% sed -n 'l' indentation_fix.sed
/^[ \t]+/ s/\t/    /$

我们必须注意,\t 字符的存在代表制表符,$符号代表行尾

现在,让我们使用一个使用制表符而不是空格的示例Test.java文件:

% sed -n 'l' Test.java
public class Test {$
\tpublic static void main(String args[]){$
\t// do something$
\t}$
}$

接下来,让我们通过实际操作来测试我们的indentation_fix.sed脚本的功能:

% sed -n -E -f "indentation_fix.sed" -e 'l' Test.java
public class Test {$
    public static void main(String args[]){$
    // do something$
    }$
}$

最后,让我们用这个脚本就地修改我们的原始文件:

% sed -n -i .save -E -f "indentation_fix.sed" -e 'p' Test.java

我们使用带有扩展名*.save-i标志在名为Test.java.save*的文件中创建原始文件的备份副本

4.2. 分组替换

有时,我们经常需要在代码中进行语法更正。让我们以访问 Java 字符串中的字符为例,我们错误地将其视为数组,就像在 C 等许多其他编程语言中一样:

String name = "Blogdemo";
if (name.length() > 0) {
    System.out.println("Name starts with " + name[0]);
}

相反,我们应该使用*charAt()*方法来访问第 0个位置的字符。由于我们文件中的许多地方都可能出现此错误,因此如果我们使用 sed 来解决此问题,我们可以节省时间。

现在,要提出一个 sed 命令,我们需要了解替换函数中的分组和格式化字符串的概念。前者是一种使用正则表达式匹配将一行分成组的方法,后者是包含特殊字符的替换字符串,用于对这些组进行反向引用。

因此,分组有助于我们保留原始字符串的一部分并将其作为替换字符串的一部分重新使用。在这种情况下,我们可以将包含名称字符串的行分成三组:

  • *(.*name)*表示的第一组将引用左方括号的左侧
  • *([0-9]+)*表示的第二组将引用正在访问的字符的索引
  • *由(.*)*表示的最后一组将引用右方括号的右侧

此外,我们可以**在替换字符串中对这些组进行反向引用,分别为*\1*、\2和*\3*。**所以,把它们放在一起,我们有我们的 sed 命令:

% sed -n -E '1,$ s/(/.*name)\[([0-9]+)\](/.*)/\1.charAt(\2)\3/; p' AccessString.java
String name = "Blogdemo";
if (name.length() > 0) {
    System.out.println("Name starts with " + name.charAt(0));
}

我们可以注意到,我们并不打算在索引周围保留方括号。所以,我们没有让他们成为任何组的一部分。

5. 工作区

如果我们打算像专家一样使用 sed,那么我们首先要熟悉 sed 的内部工作区域,即模式空间和保持空间。为此,让我们继续探索 sed 的工作区。

5.1. 模式和保持空间

到目前为止,我们在没有明确了解模式空间和保持空间概念的情况下解决了所有问题,这很好;我们真的不需要剑来切苹果。

因此,模式空间和保持空间具有直观的名称。前者是一个活动存储区域,在将输入流与模式匹配后,sed 首先保持从输入流中读取的每一行。相比之下,后者是一个可以按需使用的临时存储区域

sed 提供了多种功能来帮助我们管理工作空间。但是,它们中的一些有细微的差别,重要的是我们要了解模式空间如何适应 sed 脚本的执行周期。

5.2. 执行周期中的模式空间

首先,sed 从输入流中读取一行文本并将其保存在模式空间中。然后,它执行每个 sed 命令,直到没有更多命令可以执行。最后,sed 自己执行函数n (next) 以重复循环,直到它消耗完完整的输入流。

现在,我们必须了解,函数n的执行对于在工作流中创建循环具有非常关键的作用。因此,n函数在标准输出上打印模式空间,然后用输入流中的下一行替换它。这就是为什么当我们在没有任何命令的情况下执行 sed 时,它只是将输入流打印到标准输出。

因此,函数n不符合单一职责原则,因为它正在做两件事,即打印模式空间和用输入中的下一行替换内容。为了克服这个问题,我们之前使用了*-n标志来抑制打印行为,但不是它重新填充模式空间的部分。如果-n抑制了n*的完整功能,那么我们一开始就不会留下一个循环。

5.3. np

next 函数由 sed 在后台隐式调用以继续处理每一行输入。但是,我们甚至可以显式地使用它。

假设我们有一个文件,其中包含交替行的书名和通讯作者:

% sed '' books_authors.txt
Milk and Honey
- Rupi Kaur
Ariel
- Sylvia Plath
The Waste Land
- T.S. Eliot
The Chord
- Randhir Kaur

现在,如果我们需要列出书籍的名称并与books_authors.txt文件分开,那么我们将使用np函数的组合。

首先,让我们列出出现在所有奇数行的书名:

% sed -n 'p;n' books_authors.txt
Milk and Honey
Ariel
The Waste Land
The Chord

接下来,让我们列出出现在所有偶数行上的作者姓名:

% sed -n 'n;p' books_authors.txt
- Rupi Kaur
- Sylvia Plath
- T.S. Eliot
- Randhir Kaur

我们可以注意到,我们通过使用*-n*标志抑制了“ n ”的打印功能。结果,我们能够在输出中吃掉我们不想要的行。在第一种情况下,我们进行了一个打印和吃活动的循环,而在第二种情况下,这是一个吃和打印的循环。

此外,如果需要,我们可以使用替换函数删除作者姓名前的连字符:

% sed -n 'n;s/^- //;p' books_authors.txt
Rupi Kaur
Sylvia Plath
T.S. Eliot
Randhir Kaur

5.4. 持有、获取和交换

有时,我们需要将模式空间的当前内容保存到保持空间中,以便稍后检索以供进一步使用。为了在这两个工作空间之间轻松传输内容,我们可以使用hold、get和exchange函数:

  • h,保持功能将模式空间中的内容保存到保持空间中
  • g , get 函数将内容从保持空间检索到模式空间
  • x,交换函数交换模式空间和保持空间的内容

假设我们需要从books_authors.txt文件中获取作者 Rupi Kaur 的书名。由于书名在作者姓名之前,我们需要在保存空间中保存该值。如果这样做,我们可以使用模式空间来匹配具有作者姓名的下一行与给定作者姓名。

对于任意两行,我们可以使用 hold 后跟 next 函数将书名保留在保留空间中,将作者姓名保留在模式空间中。但是,我们还需要确保在进行模式匹配之后从保持空间中获取值并打印它。为此,我们可以*将它们放在*{}中作为函数列表

% sed -n 'h;n;/Rupi Kaur/{g;p;}' books_authors.txt
Milk and Honey

因此,当我们调用 get 函数时,我们将模式空间中的作者姓名丢失到书名中。好吧,这很好,因为我们事先已经知道作者的名字,我们不想显示这些信息。

但是,有时我们不知道作者的全名,而只知道名字或姓氏。自然,在这种情况下,我们还可以将作者的全名与书名一起列出。

让我们通过作者解决方案扩展我们书籍的功能列表部分。如果我们使用x函数交换模式空间和保持空间的内容,那么我们将在保持空间中获得作者姓名,在模式空间中获得书名。因此,我们可以使用p函数打印书名。最后,我们可以从保持空间中获取作者的姓名并像之前一样打印出来:

% sed -n 'h;n;/Kaur/{x;p;g;p;}' books_authors.txt
Milk and Honey
- Rupi Kaur
The Chord
- Randhir Kaur

我们可以注意到,同一个姓氏有两个不同的作者姓名。因此,这验证了我们将作者姓名与书名一起列出的决定。

6. 使用多行

到目前为止,我们只对单行进行了模式匹配。这是因为,默认情况下, sed 从输入流中逐行读取,并在放入模式空间之前丢弃 \n 字符。但是,这并不意味着我们不能使用多行。事实上,sed 提供了多种**功能,可以帮助管理工作区,**并考虑到新的线路:

  • P , print 函数将模式空间的第一行打印到标准输出
  • H , hold 函数添加一个*\n*字符来保存空间,然后从模式空间中追加内容
  • G , get 函数将一个*\n*字符添加到模式空间,然后从保持空间附加内容
  • N , next 函数将一个*\n*字符添加到模式空间,然后从输入流中附加下一行

现在,让我们使用N函数提高books_authors.txt文件的可读性:

% sed -E -n 'N; s/(.*)\n- (.*)/"\1" by \2/; p' books_authors.txt
"Milk and Honey" by Rupi Kaur
"Ariel" by Sylvia Plath
"The Waste Land" by T.S. Eliot
"The Chord" by Randhir Kaur

因此,通过使用N函数,我们可以将书名和作者与换行符连接起来。此外,我们使用组替换在引号中引用书名并固定“by”字符串。

接下来,让我们使用G函数来提高当我们通过部分已知的作者姓名搜索书籍时输出的可读性:

% sed -E -n 'h;n; /Kaur/ {G;s/- (.*)\n(.*)/"\2" by \1/;p;}' books_authors.txt
"Milk and Honey" by Rupi Kaur
"The Chord" by Randhir Kaur

或者,我们也可以使用Hg函数得出相同的结果:

% sed -E -n 'h;n; /Kaur/ {H;g;s/(.*)\n- (.*)/"\1" by \2/;p;}' books_authors.txt
"Milk and Honey" by Rupi Kaur
"The Chord" by Randhir Kaur

7. 流量控制

默认情况下,脚本中的 sed 命令按顺序逐行执行。现在,让我们继续学习如何更改 sed 脚本的默认执行流程。

7.1. 空行

假设有人编辑了包含书籍和作者列表的文本文件。现在它包含几个空行:

% sed -n 'l' books_authors_empty_lines.txt
Milk and Honey$
$
- Rupi Kaur$
$
$
Ariel$
- Sylvia Plath$
$
The Waste Land$
- T.S. Eliot$
$
$
$
The Chord$
- Randhir Kaur$

由于我们之前编写的所有 sed 脚本都假设中间没有空行,因此这些脚本可能会停止工作。

为了解决这个问题,我们可以修改所有早期的脚本,或者我们可以对我们的文本文件执行清理活动以删除这些空行。当然,出于时间的考虑,后者似乎更合理。

7.2. 分枝

首先,我们应该能够在读取空行时识别空行。因此, sed 不会将换行符复制到模式空间中。因此,我们将使用*^$进行模式匹配,而不是使用\n*进行模式匹配。

然后,如果我们看到有一个空行,那么我们需要开始吃掉这些行,直到我们找到一个有一些内容的行。此外,对于所有非空行,我们只需要按原样打印它们。

好吧,要使用 sed 构造这个逻辑,我们需要创建一个循环。在 sed 中,我们可以通过使用**: (label)、b (branch) 和 t (test) 函数执行条件或无条件分支来**做到这一点:

% sed -n 'p' fix_empty_spacing.sed
:check_empty_line
s/^$//
t eat_empty_line
b print_content
:eat_empty_line
n
b check_empty_line
:print_content
p

我们必须注意我们创建了三个标签,即check_empty_lineeat_empty_lineprint_content。一开始,我们根据前一行中的替换成功与否,使用测试函数有条件地分支到eat_empty_line块。此外,我们使用 n 函数消耗空行,并使用 branch 函数无条件分支到check_empty_line块。

对于那些更熟悉 while() 循环和 if 语句的人,我们可以通过更相关的伪代码来思考这个逻辑:

line = getNextLine();
while( true ) {
    if ( isEmptyLine(line) ) {
        line = getNextLine();
        continue;
    }
    print_content(line);
    if ( hasNextLine() ) {
        line = getNextLine();
    } else {
        exit();
    }
}

最后,让我们进行删除空行的清理任务:

% sed -n -f fix_empty_spacing.sed books_authors_empty_lines.txt
Milk and Honey
- Rupi Kaur
Ariel
- Sylvia Plath
The Waste Land
- T.S. Eliot
The Chord
- Randhir Kaur