mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
Merge pull request #11192 from wxy/20180615-Complete-Sed-Command-Guide--Explained-with-Practical-Examples]
PRF&PUB:20180615 Complete Sed Command Guide Explained with Practical Examples]
This commit is contained in:
commit
d7ced5d580
@ -577,40 +577,40 @@ sed < inputfile -En -e '
|
||||
|
||||
我们刚才已经看到,Sed 因为有保持空间所以有了缓存的功能。其实它还有测试和分支的指令。因为有这些特性使得 Sed 是一个[图灵完备][30]的语言。虽然它可能看起来很傻,但意味着你可以使用 Sed 写任何程序。你可以实现任何你的目的,但并不意味着实现起来会很容易,而且结果也不一定会很高效。
|
||||
|
||||
但是,不用担心。在本文中,我们将使用能够展示测试和分支功能的最简单的例子。虽然这些功能乍一看似乎很有限,但请记住,有些人用 Sed 写了 <http://www.catonmat.net/ftp/sed/dc.sed> [calculators]、<http://www.catonmat.net/ftp/sed/sedtris.sed> [Tetris] 或许多其它类型的应用程序!
|
||||
不过不用担心。在本文中,我们将使用能够展示测试和分支功能的最简单的例子。虽然这些功能乍一看似乎很有限,但请记住,有些人用 Sed 写了 <http://www.catonmat.net/ftp/sed/dc.sed> [计算器]、<http://www.catonmat.net/ftp/sed/sedtris.sed> [俄罗斯方块] 或许多其它类型的应用程序!
|
||||
|
||||
#### 标签和分支
|
||||
|
||||
从某些方面,你可以将 Sed 看到是一个功能有限的汇编语言。因此,你不会找到在高级语言中常见的 “for” 或 “while” 循环,或者 “if … else” 语句,但是你可以使用分支来实现同样的功能。
|
||||
从某些方面,你可以将 Sed 看到是一个功能有限的汇编语言。因此,你不会找到在高级语言中常见的 “for” 或 “while” 循环,或者 “if ... else” 语句,但是你可以使用分支来实现同样的功能。
|
||||
|
||||
![The Sed `branch` command][31]
|
||||
![The Sed branch command][31]
|
||||
|
||||
如果你在本文开始部分看到了用流程图描述的 Sed 运行模型,那么你应该知道 Sed 会自动增加程序计数器的值,命令是按程序的指令顺序来运行的。但是,使用分支指令,你可以通过选择程序中的任意命令来改变顺序运行的程序。跳转目的地是使用一个标签来显式定义的。
|
||||
如果你在本文开始部分看到了用流程图描述的 Sed 运行模型,那么你应该知道 Sed 会自动增加程序计数器(PC)的值,命令是按程序的指令顺序来运行的。但是,使用分支(`b`)指令,你可以通过选择执行程序中的任意命令来改变顺序运行的程序。跳转目的地是使用一个标签(`:`)来显式定义的。
|
||||
|
||||
![The Sed `label` command][32]
|
||||
![The Sed label command][32]
|
||||
|
||||
这是一个这样的示例:
|
||||
|
||||
```
|
||||
echo hello | sed -ne '
|
||||
:start # 在程序的那个行上放置一个 "start" 标签
|
||||
p # 输出 pattern 空间内容
|
||||
b start # 继续在 :start 标签上运行
|
||||
:start # 在程序的该行上放置一个 “start” 标签
|
||||
p # 输出模式空间内容
|
||||
b start # 继续在 :start 标签上运行
|
||||
' | less
|
||||
|
||||
```
|
||||
|
||||
那个 Sed 程序的行为非常类似于 `yes` 命令:它获取一个流并产生一个包含那个字符串的无限流。
|
||||
那个 Sed 程序的行为非常类似于 `yes` 命令:它获取一个字符串并产生一个包含那个字符串的无限流。
|
||||
|
||||
切换到一个标签就像我们旁通了 Sed 的自动化特性一样:它既不读取任何输入,也不输出任何内容,更不更新任何缓冲区。它只是跳转到一个不同于源程序指令顺序的另一个指令。
|
||||
切换到一个标签就像我们绕开了 Sed 的自动化特性一样:它既不读取任何输入,也不输出任何内容,更不更新任何缓冲区。它只是跳转到源程序指令顺序中下一条的另外一个指令。
|
||||
|
||||
值得一提的是,如果在分支命令(`b`)上没有指定一个标签作为它的参数,那么分支将直接切换到程序结束的地方。因此,Sed 将启动一个新的循环。这个特性可以用于去跳过一些指令并且因此可以用于作为“块”的替代者:
|
||||
|
||||
值得一提的是,如果在分支命令(`b`)上没有指定一个标签作为它的参数,那么分支将直接切换到程序结束的地方。因此,Sed 将启动一个新的循环。这个特性可以用于去旁通一些指令并且因此可以用于作为块的替代者:
|
||||
```
|
||||
cat -n inputfile | sed -ne '
|
||||
/usb/!b
|
||||
/daemon/!b
|
||||
p
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
#### 条件分支
|
||||
@ -619,93 +619,90 @@ p
|
||||
|
||||
但是,在传统意义上,一个无条件分支也是一个分支,当它运行时,将跳转到特定的目的地,而条件分支既有可能也或许不可能跳转到特定的指令,这取决于系统的当前状态。
|
||||
|
||||
Sed 只有一个条件指令,就是 test(`t`) 命令。只有在当前循环的开始或因为前一个条件分支运行了替换,它才跳转到不同的指令。更多的情况是,只有替换标志被设置时,test 命令才会切换。
|
||||
Sed 只有一个条件指令,就是测试(`t`)命令。只有在当前循环的开始或因为前一个条件分支运行了替换,它才跳转到不同的指令。更多的情况是,只有替换标志被设置时,测试命令才会切换分支。
|
||||
|
||||
![The Sed `test` command][3]![The Sed `test` command][33]
|
||||
![The Sed `test` command][33]
|
||||
|
||||
使用测试指令,你可以在一个 Sed 程序中很轻松地执行一个循环。作为一个特定的示例,你可以用它将一个行填充到某个长度(这是使用正则表达式无法实现的):
|
||||
|
||||
使用 test 指令,你可以在一个 Sed 程序中很轻松地执行一个循环。作为一个特定的示例,你可以用它将一个行填充到某个长度(这是使用正则表达式无法实现的):
|
||||
```
|
||||
# Center text
|
||||
# 居中文本
|
||||
cut -d: -f1 inputfile | sed -Ee '
|
||||
:start
|
||||
s/^(.{,19})$/ \1 / # 用空格在开始处填充少于 20 个字符的行
|
||||
# 并在结束处
|
||||
# 添加一个空格
|
||||
t start # 如果我们已经添加了一个空格,则返回到 :start 标签
|
||||
s/(.{20}).*/| \1 |/ # 保留一个行的前 20 个字符
|
||||
# 以修复由于奇数行引起的
|
||||
# 差一错误
|
||||
:start
|
||||
s/^(.{,19})$/ \1 / # 用一个空格填充少于 20 个字符的行的开始处
|
||||
# 并在结束处添加另一个空格
|
||||
t start # 如果我们已经添加了一个空格,则返回到 :start 标签
|
||||
s/(.{20}).*/| \1 |/ # 只保留一个行的前 20 个字符
|
||||
# 以修复由于奇数行引起的差一错误
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
如果你仔细读前面的示例,你可能注意到,在将要把数据“喂”给 Sed 之前,我会通过使用 cut 命令创建一个比特去预处理数据。
|
||||
如果你仔细读前面的示例,你可能注意到,在将要把数据“喂”给 Sed 之前,我通过 `cut` 命令做了一点小修正去预处理数据。
|
||||
|
||||
不过,我们也可以只使用 Sed 对程序做一些小修改来执行相同的任务:
|
||||
|
||||
然后,我们可以只使用 Sed 对程序做一些小的修改来执行相同的任务:
|
||||
```
|
||||
cat inputfile | sed -Ee '
|
||||
s/:.*// # 除第 1 个字段外删除剩余字段
|
||||
t start
|
||||
:start
|
||||
s/^(.{,19})$/ \1 / # 在开始处使用空格去填充
|
||||
# 并在结束处填充一个空格
|
||||
# 使行的长度不短于 20 个字符
|
||||
t start # 如果添加了一个空格,则返回到 :start
|
||||
s/(.{20}).*/| \1 |/ # 仅保留一个行的前 20 个字符
|
||||
# 以修复由于奇数行引起的
|
||||
# 差一错误
|
||||
s/:.*// # 除第 1 个字段外删除剩余字段
|
||||
t start
|
||||
:start
|
||||
s/^(.{,19})$/ \1 / # 用一个空格填充少于 20 个字符的行的开始处
|
||||
# 并在结束处添加另一个空格
|
||||
t start # 如果我们已经添加了一个空格,则返回到 :start 标签
|
||||
s/(.{20}).*/| \1 |/ # 仅保留一个行的前 20 个字符
|
||||
# 以修复由于奇数行引起的差一错误
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
在上面的示例中,你或许对下列的结构感到惊奇:
|
||||
|
||||
```
|
||||
t start
|
||||
:start
|
||||
|
||||
```
|
||||
|
||||
乍一看,在这里的分支并没有用,因为它只是跳转到将要运行的指令处。但是,如果你仔细阅读了 `test` 命令的定义,你将会看到,如果在当前循环的开始或者前一个 test 命令运行后发生了一个替换,分支才会起作用。换句话说就是,test 指令有清除替换标志的副作用。这也正是上面的代码片段的真实目的。这是一个在包含条件分支的 Sed 程序中经常看到的技巧,用于在使用多个替换命令时避免出现 false 的情况。
|
||||
乍一看,在这里的分支并没有用,因为它只是跳转到将要运行的指令处。但是,如果你仔细阅读了测试命令的定义,你将会看到,如果在当前循环的开始或者前一个测试命令运行后发生了一个替换,分支才会起作用。换句话说就是,测试指令有清除替换标志的副作用。这也正是上面的代码片段的真实目的。这是一个在包含条件分支的 Sed 程序中经常看到的技巧,用于在使用多个替换命令时避免出现<ruby>误报<rt>false positive</rt></ruby>的情况。
|
||||
|
||||
通过它并不能绝对强制地清除替换标志,我同意这一说法。因为在将字符串填充到正确的长度时我使用的特定的替换命令是<ruby>幂等<rt>idempotent</rt></ruby>的。因此,一个多余的迭代并不会改变结果。不过,我们可以现在再次看一下第二个示例:
|
||||
|
||||
通过它并不能绝对强制地清除替换标志,我同意这一说法。因为我使用的特定的替换命令在将字符串填充到正确的长度时是幂等的。因此,一个多余的迭代并不会改变结果。不过,我们可以现在再次看一下第二个示例:
|
||||
```
|
||||
# 基于它们的登录程序来分类用户帐户
|
||||
cat inputfile | sed -Ene '
|
||||
s/^/login=/
|
||||
/nologin/s/^/type=SERV /
|
||||
/false/s/^/type=SERV /
|
||||
t print
|
||||
s/^/type=USER /
|
||||
:print
|
||||
s/:.*//p
|
||||
s/^/login=/
|
||||
/nologin/s/^/type=SERV /
|
||||
/false/s/^/type=SERV /
|
||||
t print
|
||||
s/^/type=USER /
|
||||
:print
|
||||
s/:.*//p
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
我希望在这里根据用户默认配置的登录程序,为用户帐户打上 “SERV” 或 “USER” 的标签。如果你运行它,预计你将看到 “SERV” 标签。然而,并没有在输出中跟踪到 “USER” 标签。为什么呢?因为 `t print` 指令不论行的内容是什么,它总是切换,替换标志总是由程序的第一个替换命令来设置。一旦替换标志设置完成后,在下一个行被读取或直到下一个 test 命令之前,这个标志将保持不变。下面我们给出修复这个程序的解决方案:
|
||||
我希望在这里根据用户默认配置的登录程序,为用户帐户打上 “SERV” 或 “USER” 的标签。如果你运行它,预计你将看到 “SERV” 标签。然而,并没有在输出中跟踪到 “USER” 标签。为什么呢?因为 `t print` 指令不论行的内容是什么,它总是切换,替换标志总是由程序的第一个替换命令来设置。一旦替换标志设置完成后,在下一个行被读取或直到下一个测试命令之前,这个标志将保持不变。下面我们给出修复这个程序的解决方案:
|
||||
|
||||
```
|
||||
# 基于用户登录程序来分类用户帐户
|
||||
cat inputfile | sed -Ene '
|
||||
s/^/login=/
|
||||
s/^/login=/
|
||||
|
||||
t classify # clear the "substitution flag"
|
||||
:classify
|
||||
t classify # clear the "substitution flag"
|
||||
:classify
|
||||
|
||||
/nologin/s/^/type=SERV /
|
||||
/false/s/^/type=SERV /
|
||||
t print
|
||||
s/^/type=USER /
|
||||
:print
|
||||
s/:.*//p
|
||||
/nologin/s/^/type=SERV /
|
||||
/false/s/^/type=SERV /
|
||||
t print
|
||||
s/^/type=USER /
|
||||
:print
|
||||
s/:.*//p
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
### 精确地处理文本
|
||||
|
||||
Sed 是一个非交互式文本编辑器。虽然是非交互式的,但仍然是文本编辑器。而如果没有在输出中插入一些东西的功能,那它就不算一个完整的文本编辑器。我不是很喜欢它的文本编辑的特性,因为我发现它的语法太难用了(即便是使用标准的 Sed),但有时你难免会用到它。
|
||||
Sed 是一个非交互式文本编辑器。虽然是非交互式的,但仍然是文本编辑器。而如果没有在输出中插入一些东西的功能,那它就不算一个完整的文本编辑器。我不是很喜欢它的文本编辑的特性,因为我发现它的语法太难用了(即便是以 Sed 的标准而言),但有时你难免会用到它。
|
||||
|
||||
采用严格的 POSIX 语法的只有三个命令:改变(`c`)、插入(`i`)或追加(`a`)一些文字文本到输出,都遵循相同的特定语法:命令字母后面跟着一个反斜杠,并且文本从脚本的下一行上开始插入:
|
||||
|
||||
在严格的 POSIX 语法中,所有通过这三个命令:change(`c`)、insert(`i`)或 append(`a`)来处理一些到输出的文字文本,都遵循相同的特定语法:命令字母后面跟着一个反斜杠,并且文本从脚本的下一行上开始插入:
|
||||
```
|
||||
head -5 inputfile | sed '
|
||||
1i\
|
||||
@ -713,10 +710,10 @@ head -5 inputfile | sed '
|
||||
$a\
|
||||
# end
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
插入多行文本,你必须每一行结束的位置使用一个反斜杠:
|
||||
|
||||
```
|
||||
head -5 inputfile | sed '
|
||||
1i\
|
||||
@ -728,45 +725,46 @@ $a\
|
||||
|
||||
```
|
||||
|
||||
一些 Sed 实现,比如 GNU Sed,在初始的反斜杠后面有一个可选的换行符,即便是在 `--posix` 模式下仍然如此。我在标准中并没有找到任何关于替代该语法的授权(如果是因为我没有在标准中找到那个特性,请在评论区留言告诉我!)。因此,如果对可移植性要求很高,请注意使用它的风险:
|
||||
一些 Sed 实现,比如 GNU Sed,在初始的反斜杠后面的换行符是可选的,即便是在 `--posix` 模式下仍然如此。我在标准中并没有找到任何关于该替代语法的说明(如果是因为我没有在标准中找到那个特性,请在评论区留言告诉我!)。因此,如果对可移植性要求很高,请注意使用它的风险:
|
||||
|
||||
```
|
||||
# 非 POSIX 语法:
|
||||
head -5 inputfile | sed -e '
|
||||
1i \# List of user accounts
|
||||
1i\# List of user accounts
|
||||
$a\# end
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
也有一些 Sed 的实现,让初始的反斜杠完全是可选的。因此毫无疑问,它是一个厂商对 POSIX 标准进行扩展的特定版本,它是否支持那个语法,你需要去查看那个 Sed 版本的手册。
|
||||
|
||||
在简单概述之后,我们现在来回顾一下这些命令的更多细节,从我还没有介绍的 change 命令开始。
|
||||
在简单概述之后,我们现在来回顾一下这些命令的更多细节,从我还没有介绍的改变命令开始。
|
||||
|
||||
#### change 命令
|
||||
#### 改变命令
|
||||
|
||||
change 命令(`c\`)就像 `d` 命令一样删除模式空间的内容并开始一个新的循环。唯一的不同在于,当命令运行之后,用户提供的文本是写往输出的。
|
||||
改变命令(`c\`)就像 `d` 命令一样删除模式空间的内容并开始一个新的循环。唯一的不同在于,当命令运行之后,用户提供的文本是写往输出的。
|
||||
|
||||
![The Sed change command][34]
|
||||
|
||||
![The Sed `change` command][34]
|
||||
```
|
||||
cat -n inputfile | sed -e '
|
||||
/systemd/c\
|
||||
# :REMOVED:
|
||||
s/:.*// # This will NOT be applied to the "changed" text
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
如果 change 命令与一个地址范围关联,当到达范围的最后一行时,这个文本将仅输出一次。这在某种程度上成为 Sed 命令将被重复应用在地址范围内所有行这一惯例的一个例外情况:
|
||||
如果改变命令与一个地址范围关联,当到达范围的最后一行时,这个文本将仅输出一次。这在某种程度上成为 Sed 命令将被重复应用在地址范围内所有行这一惯例的一个例外情况:
|
||||
|
||||
```
|
||||
cat -n inputfile | sed -e '
|
||||
19,22c\
|
||||
# :REMOVED:
|
||||
s/:.*// # This will NOT be applied to the "changed" text
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
因此,如果你希望将 change 命令重复应用到地址范围内的所有行上,除了将它封装到一个块中之外,你将没有其它的选择:
|
||||
因此,如果你希望将改变命令重复应用到地址范围内的所有行上,除了将它封装到一个块中之外,你将没有其它的选择:
|
||||
|
||||
```
|
||||
cat -n inputfile | sed -e '
|
||||
19,22{c\
|
||||
@ -774,14 +772,14 @@ cat -n inputfile | sed -e '
|
||||
}
|
||||
s/:.*// # This will NOT be applied to the "changed" text
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
#### insert 命令
|
||||
#### 插入命令
|
||||
|
||||
insert 命令(`i\`)将立即在输出中给出用户提供的文本。它并不以任何方式修改程序流或缓冲区的内容。
|
||||
插入命令(`i\`)将立即在输出中给出用户提供的文本。它并不以任何方式修改程序流或缓冲区的内容。
|
||||
|
||||
![The Sed insert command][35]
|
||||
|
||||
![The Sed `insert` command][35]
|
||||
```
|
||||
# display the first five user names with a title on the first row
|
||||
sed < inputfile -e '
|
||||
@ -790,16 +788,16 @@ USER NAME
|
||||
s/:.*//
|
||||
5q
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
#### append 命令
|
||||
#### 追加命令
|
||||
|
||||
当输入的下一行被读取时,append 命令(`a\`)将一些文本追加到显示队列。文本在当前循环的结束部分(包含程序结束的情况)或当使用 `n` 或 `N` 命令从输入中读取一个新行时被输出。
|
||||
当输入的下一行被读取时,追加命令(`a\`)将一些文本追加到显示队列。文本在当前循环的结束部分(包含程序结束的情况)或当使用 `n` 或 `N` 命令从输入中读取一个新行时被输出。
|
||||
|
||||
![The Sed `append` command][36]
|
||||
![The Sed append command][36]
|
||||
|
||||
与上面相同的一个示例,但这次是插入到底部而不是顶部:
|
||||
|
||||
与上面相同的一个示例,但这次是插入到底部而是顶部:
|
||||
```
|
||||
sed < inputfile -e '
|
||||
5a\
|
||||
@ -807,80 +805,81 @@ USER NAME
|
||||
s/:.*//
|
||||
5q
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
#### read 命令
|
||||
#### 读取命令
|
||||
|
||||
这是插入一些文本内容到输出流的第四个命令:read 命令(`r`)。它的工作方式与 append 命令完全一样,但不同的,它不从 Sed 脚本中取得硬编码到脚本中的文本,而是在一个输出上写一个文件的内容。
|
||||
这是插入一些文本内容到输出流的第四个命令:读取命令(`r`)。它的工作方式与追加命令完全一样,但不同的,它不从 Sed 脚本中取得硬编码到脚本中的文本,而是把一个文件的内容写入到一个输出上。
|
||||
|
||||
read 命令只调度要读取的文件。当刷新 append 队列时,后者被高效地读取,而不是在 read 命令运行时。如果这时候对这个文件有并发的访问,或那个文件不是一个普通的文件(比如,它是一个字符设备或命名管道),或文件在读取期间被修改,这时可能会产生严重的后果。
|
||||
读取命令只调度要读取的文件。当清理追加队列时,后者才被高效地读取,而不是在读取命令运行时。如果这时候对这个文件有并发的访问读取,或那个文件不是一个普通的文件(比如,它是一个字符设备或命名管道),或文件在读取期间被修改,这时可能会产生严重的后果。
|
||||
|
||||
作为一个例证,如果你使用我们将在下一节详细讲述的写入命令,它与读取命令共同配合从一个临时文件中写入并重新读取,你可能会获得一些创造性的结果(使用法语版的 [Shiritori][37] 游戏作为一个例证):
|
||||
|
||||
作为一个例证,如果你使用我们将在下一次详细讲的 write 命令,它与 read 命令共同去写入并从一个临时文件中重新读取,你可能会获得一些创造性的结果(使用法语版的 [Shiritori][37] 游戏作为一个例证):
|
||||
```
|
||||
printf "%s\n" "Trois p'tits chats" "Chapeau d' paille" "Paillasson" |
|
||||
sed -ne '
|
||||
r temp
|
||||
a\
|
||||
----
|
||||
w temp
|
||||
r temp
|
||||
a\
|
||||
----
|
||||
w temp
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
现在,在流输出中专门用于插入一些文本的 Sed 命令的清单结束了。我的最后一个示例纯属好玩,但是由于我前面提到过有一个 write 命令,这个示例将我们完美地带到下一节,在下一节我们将看到在 Sed 中如何将数据写入到一个外部文件。
|
||||
现在,在流输出中专门用于插入一些文本的 Sed 命令清单结束了。我的最后一个示例纯属好玩,但是由于我前面提到过有一个写入命令,这个示例将我们完美地带到下一节,在下一节我们将看到在 Sed 中如何将数据写入到一个外部文件。
|
||||
|
||||
### 输出的替代
|
||||
### 替代的输出
|
||||
|
||||
Sed 的设计思想是,所有的文本转换都将写入到进程的标准输出上。但是,Sed 也有一些特性支持将数据发送到替代的目的地。你有两种方式去实现上述的输出目标替换:使用专门的 `write` 命令,或者在一个 `substitution` 命令上添加一个写入标志。
|
||||
Sed 的设计思想是,所有的文本转换都将写入到进程的标准输出上。但是,Sed 也有一些特性支持将数据发送到替代的目的地。你有两种方式去实现上述的输出目标替换:使用专门的写入命令(`w`),或者在一个替换命令(`s`)上添加一个写入标志。
|
||||
|
||||
#### write 命令
|
||||
#### 写入命令
|
||||
|
||||
write 命令(`w`)追加模式空间的内容到给定的目标文件中。POSIX 要求在 Sed 处理任何数据之前,目标文件能够被 Sed 所创建。如果给定的目标文件已经存在,它将被覆写。
|
||||
写入命令(`w`)会追加模式空间的内容到给定的目标文件中。POSIX 要求在 Sed 处理任何数据之前,目标文件能够被 Sed 所创建。如果给定的目标文件已经存在,它将被覆写。
|
||||
|
||||
![The Sed `write` command][38]
|
||||
![The Sed write command][38]
|
||||
|
||||
因此,即便是你从未真的写入到该文件中,但该文件仍然会被创建。例如,下列的 Sed 程序将创建/覆写这个 `output` 文件,那怕是这个写入命令从未被运行过:
|
||||
|
||||
因此,即便是你从未真实地去写入到一个文件中,但文件仍然会被创建。例如,下列的 Sed 程序将创建/覆写这个 “output” 文件,那怕是这个写入命令从未被运行过:
|
||||
```
|
||||
echo | sed -ne '
|
||||
q # 立刻退出
|
||||
w output # 这个命令从未被运行
|
||||
q # 立刻退出
|
||||
w output # 这个命令从未被运行
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
你可以将几个写入命令指向到同一个目标文件。指向同一个目标文件的所有写入命令将追加那个文件的内容(工作方式几乎与 shell 的重定向符 `>>` 相同):
|
||||
你可以将几个写入命令指向到同一个目标文件。指向同一个目标文件的所有写入命令将追加那个文件的内容(工作方式几乎与 shell 的重定向符 `>>` 相同):
|
||||
|
||||
```
|
||||
sed < inputfile -ne '
|
||||
/:\/bin\/false$/w server
|
||||
/:\/usr\/sbin\/nologin$/w server
|
||||
w output
|
||||
/:\/bin\/false$/w server
|
||||
/:\/usr\/sbin\/nologin$/w server
|
||||
w output
|
||||
'
|
||||
cat server
|
||||
|
||||
```
|
||||
|
||||
#### 替换命令的写入标志
|
||||
|
||||
在前面,我们已经学习了替换命令,它有一个 `p` 选项用于在替换之后输出模式空间的内容。同样它也提供一个类似功能的 `w` 选项,用于在替换之后将模式空间的内容输出到一个文件中:
|
||||
```
|
||||
sed < inputfile -ne '
|
||||
s/:.*\/nologin$//w server
|
||||
s/:.*\/false$//w server
|
||||
'
|
||||
cat server
|
||||
在前面,我们已经学习了替换命令(`s`),它有一个 `p` 选项用于在替换之后输出模式空间的内容。同样它也提供一个类似功能的 `w` 选项,用于在替换之后将模式空间的内容输出到一个文件中:
|
||||
|
||||
```
|
||||
sed < inputfile -ne '
|
||||
s/:.*\/nologin$//w server
|
||||
s/:.*\/false$//w server
|
||||
'
|
||||
cat server
|
||||
```
|
||||
|
||||
### 注释
|
||||
|
||||
我无数次使用过它们,但我从未花时间正式介绍过它们,因此,我决定现在来正式地介绍它们:就像大多数编程语言一样,注释是添加软件不去解析的自由格式文本的一种方法。Sed 的语法很晦涩,我不得不强调在脚本中需要的地方添加足够的注释。否则,除了作者外其他人将几乎无法理解它。
|
||||
|
||||
![The Sed `comment` command][39]
|
||||
![The Sed comment command][39]
|
||||
|
||||
不过,和 Sed 的其它部分一样,注释也有它自己的微妙之处。首先并且是最重要的,注释并不是语法结构,但它在 Sed 中很成熟。注释虽然是一个“什么也不做”的命令,但它仍然是一个命令。至少,它是在 POSIX 中定义了的。因此,严格地说,它们只允许使用在其它命令允许使用的地方。
|
||||
不过,和 Sed 的其它部分一样,注释也有它自己的微妙之处。首先并且是最重要的,注释并不是语法结构,但它是真正意义的 Sed 命令。注释虽然是一个“什么也不做”的命令,但它仍然是一个命令。至少,它是在 POSIX 中定义了的。因此,严格地说,它们只允许使用在其它命令允许使用的地方。
|
||||
|
||||
大多数 Sed 实现都通过允许行内命令来放松了那种要求,就像在那个文章中我到处都使用的那样。
|
||||
|
||||
结束那个主题之前,需要说一下 `#n` 注释(`#` 后面紧跟一个`n`,中间没有空格)的特殊情况。如果在脚本的第一行找到这个精确注释,Sed 将切换到静默模式(即:清除自动输出标志),就像在命令行上指定了 `-n` 选项一样。
|
||||
结束那个主题之前,需要说一下 `#n` 注释(`#` 后面紧跟一个字母 `n`,中间没有空格)的特殊情况。如果在脚本的第一行找到这个精确注释,Sed 将切换到静默模式(即:清除自动输出标志),就像在命令行上指定了 `-n` 选项一样。
|
||||
|
||||
### 很少用得到的命令
|
||||
|
||||
@ -888,64 +887,63 @@ cat server
|
||||
|
||||
#### 行数命令
|
||||
|
||||
这个 `=` 命令将向标准输出上显示当前 Sed 正在读取的行数,这个行数就是行计数器的内容。没有任何方式从任何一个 Sed 缓冲区中捕获那个数字,也不能对它进行输出格式化。由于这两个限制使得这个命令的可用性大大降低。
|
||||
这个 `=` 命令将向标准输出上显示当前 Sed 正在读取的行数,这个行数就是行计数器(`LC`)的内容。没有任何方式从任何一个 Sed 缓冲区中捕获那个数字,也不能对它进行输出格式化。由于这两个限制使得这个命令的可用性大大降低。
|
||||
|
||||
![The Sed `line number` command][40]
|
||||
![The Sed line number command][40]
|
||||
|
||||
请记住,在严格的 POSIX 兼容模式中,当在命令行上给定几个输入文件时,Sed 并不重置那个计数器,而是连续地增长它,就像所有的输入文件是连接在一起的一样。一些 Sed 实现,像 GNU Sed,它就有一个选项可以在每个输入文件读取结束后去重置计数器。
|
||||
|
||||
#### 明确的 print 命令
|
||||
#### 明确打印命令
|
||||
|
||||
这个 `l`(小写的字母 `l`)作用类似于 print 命令(`p`),但它是以精确的格式去输出模式空间的内容。以下引用自 [POSIX 标准][12]:
|
||||
这个 `l`(小写的字母 `l`)作用类似于打印命令(`p`),但它是以精确的格式去输出模式空间的内容。以下引用自 [POSIX 标准][12]:
|
||||
|
||||
> 在 XBD 转义序列中列出的字符和相关的动作(‘\\\’、‘\a’、‘\b’、‘\f’、‘\r’、‘\t’、‘\v’)将被写为相应的转义序列;在那个表中的 ‘\n’ 是不适用的。不在那个表中的不可打印字符将被写为一个三位八进制数字(在前面使用一个 <反斜杠>),表示字符中的每个字节(最重要的字节在前面)。长行应该被换行,通过写一个 <反斜杠>后跟一个 <换行符> 来表示换行点;发生换行时的长度是不确定的,但应该适合输出设备的具体情况。每个行应该以一个 ‘$’ 标记结束。
|
||||
> 在 XBD 转义序列中列出的字符和相关的动作(`\\`、`\a`、`\b`、`\f`、`\r`、`\t`、`\v`)将被写为相应的转义序列;在那个表中的 `\n` 是不适用的。不在那个表中的不可打印字符将被写为一个三位八进制数字(在前面使用一个反斜杠 `\`),表示字符中的每个字节(最重要的字节在前面)。长行应该被换行,通过写一个反斜杠后跟一个换行符来表示换行位置;发生换行时的长度是不确定的,但应该适合输出设备的具体情况。每个行应该以一个 `$` 标记结束。
|
||||
|
||||
![The Sed `unambiguous print` command][3]![The Sed `unambiguous print` command][41]
|
||||
![The Sed unambiguous print command][41]
|
||||
|
||||
我怀疑这个命令是在非 [8位规则化信道][42] 上交换数据的。就我本人而言,除了调试用途以外,也从未使用过它。
|
||||
我怀疑这个命令是在非 [8 位规则化信道][42] 上交换数据的。就我本人而言,除了调试用途以外,也从未使用过它。
|
||||
|
||||
#### transliterate 命令
|
||||
#### 移译命令
|
||||
|
||||
<ruby>移译<rt>transliterate</rt></ruby>(`y`)命令允许映射模式空间的字符从一个源集到一个目标集。它非常类似于 `tr` 命令,但是限制更多。
|
||||
<ruby>移译<rt>transliterate</rt></ruby>(`y`)命令允许从一个源集到一个目标集映射模式空间的字符。它非常类似于 `tr` 命令,但是限制更多。
|
||||
|
||||
![The Sed transliterate command][43]
|
||||
|
||||
![The Sed `transliterate` command][43]
|
||||
```
|
||||
# The `y` c0mm4nd 1s for h4x0rz only
|
||||
sed < inputfile -e '
|
||||
s/:.*//
|
||||
y/abcegio/48<3610/
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
虽然 transliterate 命令语法与 substitution 命令的语法有一些相似之处,但它在替换字符串之后不接受任何选项。这个移译总是全局的。
|
||||
虽然移译命令语法与替换命令的语法有一些相似之处,但它在替换字符串之后不接受任何选项。这个移译总是全局的。
|
||||
|
||||
请注意,移译命令要求源集和目标集之间要一一对应地转换。这意味着下面的 Sed 程序可能所做的事情并不是你乍一看所想的那样:
|
||||
|
||||
```
|
||||
# BEWARE: this doesn't do what you may think!
|
||||
# 注意:这可能并不如你想的那样工作!
|
||||
sed < inputfile -e '
|
||||
s/:.*//
|
||||
y/[a-z]/[A-Z]/
|
||||
s/:.*//
|
||||
y/[a-z]/[A-Z]/
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
### 写在最后的话
|
||||
|
||||
```
|
||||
# 它要做什么?
|
||||
# 提示:答案就在不远处...
|
||||
sed -E '
|
||||
s/.*\W(.*)/\1/
|
||||
h
|
||||
${ x; p; }
|
||||
d' < inputfile
|
||||
|
||||
s/.*\W(.*)/\1/
|
||||
h
|
||||
${ x; p; }
|
||||
d' < inputfile
|
||||
```
|
||||
|
||||
我们已经学习了所有的 Sed 命令,真不敢相信我们已经做到了!如果你也读到这里了,应该恭喜你,尤其是如果你花费了一些时间,在你的系统上尝试了所有的不同示例!
|
||||
|
||||
正如你所见,Sed 是非常复杂的,不仅因为它的语法比较零乱,也因为许多极端案例或命令行为之间的细微差别。毫无疑问,我们可以将这些归结于历史的原因。尽管它有这么多缺点,但是 Sed 仍然是一个非常强大的工具,甚至到现在,它仍然是大量使用的、为数不多的 Unix 工具箱中的命令之一。是时候总结一下这篇文章了,如果你不先支持我,我将不去总结它:请节选你对喜欢的或最具创意的 Sed 脚本,并共享给我们。如果我收集到的你们共享出的脚本足够多了,我将会把这些 Sed 脚本结集发布!
|
||||
正如你所见,Sed 是非常复杂的,不仅因为它的语法比较零乱,也因为许多极端案例或命令行为之间的细微差别。毫无疑问,我们可以将这些归结于历史的原因。尽管它有这么多缺点,但是 Sed 仍然是一个非常强大的工具,甚至到现在,它仍然是 Unix 工具箱中为数不多的大量使用的命令之一。是时候总结一下这篇文章了,没有你们的支持我将无法做到:请节选你对喜欢的或最具创意的 Sed 脚本,并共享给我们。如果我收集到的你们共享出的脚本足够多了,我将会把这些 Sed 脚本结集发布!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -954,7 +952,7 @@ via: https://linuxhandbook.com/sed-reference-guide/
|
||||
作者:[Sylvain Leroux][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[qhwdw](https://github.com/qhwdw)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
Loading…
Reference in New Issue
Block a user