diff --git a/translated/tech/20180615 Complete Sed Command Guide [Explained with Practical Examples].md b/translated/tech/20180615 Complete Sed Command Guide [Explained with Practical Examples].md index 2e78145914..fd47d9610e 100644 --- a/translated/tech/20180615 Complete Sed Command Guide [Explained with Practical Examples].md +++ b/translated/tech/20180615 Complete Sed Command Guide [Explained with Practical Examples].md @@ -271,7 +271,7 @@ Sed 允许在一个块中使用花括号 `{…}` 组合命令。你可以利用 ``` sed -n -e '/usb/{ -/daemon/p + /daemon/p }' inputfile sed -n -e '/usb.*daemon/p' inputfile @@ -311,8 +311,8 @@ sed -e '5q' inputfile sed -n -e '5p' -e '5q' inputfile sed -n -e ' - 5p - 5q + 5p + 5q ' inputfile sed -n -e '5p;5q' inputfile @@ -323,8 +323,8 @@ sed -n -e '5p;5q' inputfile ``` # 组合命令 sed -e '5{ - p - q + p + q }' inputfile # 可以简写为: @@ -336,272 +336,241 @@ sed '5{p;q}' inputfile ### 替换命令 -你可以将替换命令想像为 Sed 的“查找替换”功能,这个功能在大多数的“所见即所得”的编辑器上都能找到。Sed 的替换命令与之类似,但比它们更强大。替换命令是 Sed 中最著名的命令之一,在网上有大量的关于这个命令的文档。 +你可以将替换命令(`s`)想像为 Sed 的“查找替换”功能,这个功能在大多数的“所见即所得”的编辑器上都能找到。Sed 的替换命令与之类似,但比它们更强大。替换命令是 Sed 中最著名的命令之一,在网上有大量的关于这个命令的文档。 ![The Sed `substitution` command][19] [在前一篇文章][20]中我们已经讲过它了,因此,在这里就不再重复了。但是,如果你对它的使用不是很熟悉,那么你需要记住下面的这些关键点: - * 替换命令有两个参数:查找模式和替换字符串:`sed s/:/-----/ inputfile` - - * 命令和它的参数是用任意一个字符来分隔的。这主要看你的习惯,在 99% 的时间中我都使用斜杠,但也会用其它的字符:`sed s%:%-----% inputfile`、`sed sX:X-----X inputfile` 或者甚至是 `sed 's : ----- ' inputfile` - - * 默认情况下,替换命令仅被应用到模式空间中匹配到的第一个字符串上。你可以通过在命令之后指定一个匹配指数作为标志来改变这种情况:`sed 's/:/-----/1' inputfile`、`sed 's/:/-----/2' inputfile`、`sed 's/:/-----/3' inputfile`、… - - * 如果你想执行一个全面的替换(即:在模式空间上的每个非重叠匹配),你需要增加 `g` 标志:`sed 's/:/-----/g' inputfile` - - * 在字符串替换中,出现的任何一个 `&` 符号都将被与查找模式匹配的子字符串替换:`sed 's/:/-&&&-/g' inputfile`、`sed 's/…./& /g' inputfile` - - * 圆括号(在扩展的正则表达式中的 `(…)` 或者基本的正则表达式中的 `\(…\)`)被引用为捕获组。那是匹配字符串的一部分,可以在替换字符串中被引用。`\1` 是第一个捕获组的内容,`\2` 是第二个捕获组的内容,依次类推:`sed -E 's/(.)(.)/\2\1/g' inputfile`、`sed -E 's/(.):x:(.):(.*)/\1:\3/' inputfile`(后者之所能正常工作是因为 [正则表达式中的量词星号表示重复匹配下去,直到不匹配为止][21],并且它可以匹配许多个字符) - - * 在查找模式或替换字符串时,你可以通过使用一个反斜杠来去除任何字符的特殊意义:`sed 's/:/--\&--/g' inputfile`,`sed 's/\//\\/g' inputfile` - - +* 替换命令有两个参数:查找模式和替换字符串:`sed s/:/-----/ inputfile` +* `s` 命令和它的参数是用任意一个字符来分隔的。这主要看你的习惯,在 99% 的时间中我都使用斜杠,但也会用其它的字符:`sed s%:%-----% inputfile`、`sed sX:X-----X inputfile` 或者甚至是 `sed 's : ----- ' inputfile` +* 默认情况下,替换命令仅被应用到模式空间中匹配到的第一个字符串上。你可以通过在命令之后指定一个匹配指数作为标志来改变这种情况:`sed 's/:/-----/1' inputfile`、`sed 's/:/-----/2' inputfile`、`sed 's/:/-----/3' inputfile`、… +* 如果你想执行一个全局替换(即:在模式空间上的每个非重叠匹配上进行),你需要增加 `g` 标志:`sed 's/:/-----/g' inputfile` +* 在字符串替换中,出现的任何一个 `&` 符号都将被与查找模式匹配的子字符串替换:`sed 's/:/-&&&-/g' inputfile`、`sed 's/.../& /g' inputfile` +* 圆括号(在扩展的正则表达式中的 `(...)` ,或者基本的正则表达式中的 `\(...\)`)被当做捕获组capturing group。那是匹配字符串的一部分,可以在替换字符串中被引用。`\1` 是第一个捕获组的内容,`\2` 是第二个捕获组的内容,依次类推:`sed -E 's/(.)(.)/\2\1/g' inputfile`、`sed -E 's/(.):x:(.):(.*)/\1:\3/' inputfile`(后者之所能正常工作是因为 [正则表达式中的量词星号表示尽可能多的匹配,直到不匹配为止][21],并且它可以匹配许多个字符) +* 在查找模式或替换字符串时,你可以通过使用一个反斜杠来去除任何字符的特殊意义:`sed 's/:/--\&--/g' inputfile`,`sed 's/\//\\/g' inputfile` 所有的这些看起来有点抽象,下面是一些示例。首先,我想去显示我的测试输入文件的第一个字段并给它在右侧附加 20 个空格字符,我可以这样写: + ``` sed < inputfile -E -e ' - s/:/ / # 用 20 个空格替换第一个字段的分隔符 - s/(.{20}).*/\1/ # 只保留一行的前 20 个字符 - s/.*/| & |/ # 为了输出好看添加竖条 + s/:/ / # 用 20 个空格替换第一个字段的分隔符 + s/(.{20}).*/\1/ # 只保留一行的前 20 个字符 + s/.*/| & |/ # 为了输出好看添加竖条 ' - ``` 第二个示例是,如果我想将用户 sonia 的 UID/GID 修改为 1100,我可以这样写: + ``` sed -En -e ' - /sonia/{ - s/[0-9]+/1100/g - p + /sonia/{ + s/[0-9]+/1100/g + p }' inputfile - ``` 注意在替换命令结束部分的 `g` 选项。这个选项改变了它的行为,因此它将查找全部的模式空间并替换,如果没有那个选项,它只替换查找到的第一个。 -顺便说一下,这也是使用前面讲过的输出(`p`)命令的好机会,可以在命令运行时输出修改前后时刻模式空间的内容。因此,为了获得替换前后的内容,我可以这样写: +顺便说一下,这也是使用前面讲过的输出(`p`)命令的好机会,可以在命令运行时输出修改前后的模式空间的内容。因此,为了获得替换前后的内容,我可以这样写: + ``` sed -En -e ' - /sonia/{ - p - s/[0-9]+/1100/g - p + /sonia/{ + p + s/[0-9]+/1100/g + p }' inputfile - ``` 事实上,替换后输出一个行是很常见的用法,因此,替换命令也接受 `p` 选项: + ``` sed -En -e '/sonia/s/[0-9]+/1100/gp' inputfile - ``` 最后,我就不详细讲替换命令的 `w` 选项了,我们将在稍后的学习中详细介绍。 -#### delete 命令 +### 删除命令 删除命令(`d`)用于清除模式空间的内容,然后立即开始下一个处理循环。这样它将会跳过隐式输出模式空间内容的行为,即便是你设置了自动输出标志(AP)也不会输出。 ![The Sed `delete` command][22] 只输出一个文件前五行的一个很低效率的方法将是: + ``` sed -e '6,$d' inputfile - ``` -你猜猜看,我为什么说它很低效率?如果你猜不到,建议你再次去阅读前面的关于 quit 命令的章节,答案就在那里! +你猜猜看,我为什么说它很低效率?如果你猜不到,建议你再次去阅读前面的关于退出命令的章节,答案就在那里! + +当你组合使用正则表达式和地址,从输出中删除匹配的行时,删除命令将非常有用: -当你组合使用正则表达式和地址,从输出中删除匹配的行时,delete 命令将非常有用: ``` sed -e '/systemd/d' inputfile - ``` -#### next 命令 +### 次行命令 -如果 Sed 命令不是在静默模式中运行,这个命令将输出当前模式空间的内容,然后,在任何情况下它将读取下一个输入行到模式空间中,并使用新的模式空间中的内容来运行当前循环中剩余的命令。 +如果 Sed 命令没有运行在静默模式中,这个命令(`n`)将输出当前模式空间的内容,然后,在任何情况下它将读取下一个输入行到模式空间中,并使用新的模式空间中的内容来运行当前循环中剩余的命令。 -![The Sed `next` command][23] +![The Sed next command][23] + +用次行命令去跳过行的一个常见示例: -常见的用 next 命令去跳过行的一个示例: ``` cat -n inputfile | sed -n -e 'n;n;p' - ``` -在上面的例子中,Sed 将隐式地读取输入文件的第一行。但是 `next` 命令将丢弃对模式空间中的内容的输出(不输出是因为使用了 `-n` 选项),并从输入文件中读取下一行来替换模式空间中的内容。而第二个 `next` 命令做的事情和前一个是一模一样的,这就实现了跳过输入文件 2 行的目的。最后,这个脚本显式地输出包含在 `pattern ` 空间中的输入文件的第三行的内容。然后,Sed 将启动一个新的循环,由于 `next` 命令,它会隐式地读取第 4 行的内容,然后跳过它,同样地也跳过第 5 行,并输出第 6 行。如此循环,直到文件结束。总体来看,这个脚本就是读取输入文件然后每三行输出一行。 +在上面的例子中,Sed 将隐式地读取输入文件的第一行。但是次行命令将丢弃对模式空间中的内容的输出(不输出是因为使用了 `-n` 选项),并从输入文件中读取下一行来替换模式空间中的内容。而第二个次行命令做的事情和前一个是一模一样的,这就实现了跳过输入文件 2 行的目的。最后,这个脚本显式地输出包含在模式空间中的输入文件的第三行的内容。然后,Sed 将启动一个新的循环,由于次行命令,它会隐式地读取第 4 行的内容,然后跳过它,同样地也跳过第 5 行,并输出第 6 行。如此循环,直到文件结束。总体来看,这个脚本就是读取输入文件然后每三行输出一行。 + +使用次行命令,我们也可以找到一些显示输入文件的前五行的几种方法: -使用 next 命令,我们也可以找到一些显示输入文件的前五行的几种方法: ``` cat -n inputfile | sed -n -e '1{p;n;p;n;p;n;p;n;p}' cat -n inputfile | sed -n -e 'p;n;p;n;p;n;p;n;p;q' cat -n inputfile | sed -e 'n;n;n;n;q' - ``` -更有趣的是,如果你需要根据一些地址来处理行时,next 命令也非常有用: -``` -cat -n inputfile | sed -n '/pulse/p' # 输出包含 "pulse" 的行 -cat -n inputfile | sed -n '/pulse/{n;p}' # 输出包含 "pulse" 之后的行 -cat -n inputfile | sed -n '/pulse/{n;n;p}' # 输出下面的行 - # 下一行 - # 包含 "pulse" 的行 +更有趣的是,如果你需要根据一些地址来处理行时,次行命令也非常有用: +``` +cat -n inputfile | sed -n '/pulse/p' # 输出包含 “pulse” 的行 +cat -n inputfile | sed -n '/pulse/{n;p}' # 输出包含 “pulse” 之后的行 +cat -n inputfile | sed -n '/pulse/{n;n;p}' # 输出包含 “pulse” 的行的下一行的下一行 ``` ### 使用保持空间 到目前为止,我们所看到的命令都是仅使用了模式空间。但是,我们在文章的开始部分已经提到过,还有第二个缓冲区:保持空间,它完全由用户管理。它就是我们在第二节中描述的目标。 -#### exchange 命令 +#### 交换命令 -正如它的名字所表示的,exchange 命令(`x`)将交换保持空间和模式空间的内容。记住,你只要没有把任何东西放入到保持空间中,那么保持空间就是空的。 +正如它的名字所表示的,交换命令(`x`)将交换保持空间和模式空间的内容。记住,你只要没有把任何东西放入到保持空间中,那么保持空间就是空的。 -![The Sed `exchange` command][24] +![The Sed exchange command][24] + +作为第一个示例,我们可使用交换命令去反序输出一个输入文件的前两行: -作为第一个示例,我们可使用 exchange 命令去反序输出一个输入文件的前两行: ``` cat -n inputfile | sed -n -e 'x;n;p;x;p;q' - ``` -当然,在你设置 `hold` 之后你并没有立即使用它的内容,因为只要你没有显式地去修改它,保持空间中的内容就保持不变。在下面的例子中,我在输入一个文件的前五行后,使用它去删除第一行: +当然,在你设置保持空间之后你并没有立即使用它的内容,因为只要你没有显式地去修改它,保持空间中的内容就保持不变。在下面的例子中,我在输入一个文件的前五行后,使用它去删除第一行: + ``` cat -n inputfile | sed -n -e ' - 1{x;n} # 交换 hold 和 pattern 空间 - # 保存第 1 行到 hold 空间中 - # 然后读取第 2 行 + 1{x;n} # 交换保持和模式空间 + # 保存第 1 行到保持空间中 + # 然后读取第 2 行 5{ - p # 输出第 5 行 - x # 交换 hold 和 pattern 空间 - # 去取得第 1 行的内容放回到 - # pattern 空间 + p # 输出第 5 行 + x # 交换保持和模式空间 + # 去取得第 1 行的内容放回到模式空间 } - 1,5p # 输出第 2 到第 5 行 - # (不要输错了!尝试找出这个规则 - # 没有在第 1 行上运行的原因;) + 1,5p # 输出第 2 到第 5 行 + # (并没有输错!尝试找出这个规则 + # 没有在第 1 行上运行的原因 ;) ' - ``` -#### hold 命令 +#### 保持命令 -hold 命令(`h`)是用于将模式空间中的内容保存到保持空间中。但是,与 exchange 命令不同的是,模式空间中的内容不会被改变。hold 命令有两种用法: +保持命令(`h`)是用于将模式空间中的内容保存到保持空间中。但是,与交换命令不同的是,模式空间中的内容不会被改变。保持命令有两种用法: - * `h` -将复制模式空间中的内容到保持空间中,将覆盖保持空间中任何已经存在的内容。 +* `h` 将复制模式空间中的内容到保持空间中,覆盖保持空间中任何已经存在的内容。 +* `H` 将模式空间中的内容追加到保持空间中,使用一个新行作为分隔符。 - * `H` -使用一个独立的新行,追加模式空间中的内容到保持空间中。 +![The Sed hold command][25] +上面使用交换命令的例子可以使用保持命令重写如下: - - -![The Sed `hold` command][25] - -上面使用 exchange 命令的例子可以使用 hold 命令重写如下: ``` cat -n inputfile | sed -n -e ' - 1{h;n} # 保存第 1 行的内容到 hold 缓冲区并继续 - 5{ # 到第 5 行 - x # 交换 pattern 和 hold 空间 - # (现在 pattern 空间包含了第 1 行) - H # 在 hold 空间的第 5 行后追回第 1 行 - x # 再次交换取回第 5 行并将第 1 行插入 - # 到 pattern 空间 + 1{h;n} # 保存第 1 行的内容到保持缓冲区并继续 + 5{ # 在第 5 行 + x # 交换模式和保持空间 + # (现在模式空间包含了第 1 行) + H # 在保持空间的第 5 行后追加第 1 行 + x # 再次交换第 5 行和第 1 行,第 5 行回到模式空间 } - 1,5p # 输出第 2 行到第 5 行 - # (不要输错!尝试去打到为什么这个规则 - # 不在第 1 行上运行;) + 1,5p # 输出第 2 行到第 5 行 + # (没有输错!尝试去找到为什么这个规则 + # 不在第 1 行上运行 ;) ' - ``` -#### get 命令 +#### 获取命令 -get 命令(`g`)与 hold 命令恰好相反:它从保持空间中取得内容并将它置入到模式空间中。同样它也有两种方式: +获取命令(`g`)与保持命令恰好相反:它从保持空间中取得内容并将它置入到模式空间中。同样它也有两种方式: - * `g` -它将复制保持空间中的内容并将其放入到模式空间,覆盖 `pattern`空间中已存在的任何内容 +* `g` 将复制保持空间中的内容并将其放入到模式空间,覆盖模式空间中已存在的任何内容 +* `G` 将保持空间中的内容追加到模式空间中,并使用一个新行作为分隔符 - * `G` -使用一个单独的新行,追加保持空间中的内容到模式空间中 +![The Sed get command][26] +将保持命令和获取命令一起使用,可以允许你去存储并调回数据。作为一个小挑战,我让你重写前一节中的示例,将输入文件的第 1 行放置在第 5 行之后,但是这次必须使用获取和保持命令(使用大写或小写命令的版本)而不能使用交换命令。带点小运气,可以更简单! +同时,我可以给你展示另一个示例,它能给你一些灵感。目标是将拥有登录 shell 权限的用户与其它用户分开: - -![The Sed `get` command][26] - -将 hold 命令和 get 命令一起使用,可以允许你去存储并调回数据。作为一个小挑战,我让你重写前一节中的示例,将输入文件的第 1 行放置在第 5 行之后,但是这次必须使用 get 和 hold 命令(注意大小写)而不能使用 exchange 命令。只要运气好,它将使那个方式更简单! - -在这期间,我可以给你展示另一个示例,它能给你一些灵感。目标是将拥有登录 shell 权限的用户与其它用户分开: ``` cat -n inputfile | sed -En -e ' \=(/usr/sbin/nologin|/bin/false)$= { H;d; } - # 追回匹配的行到 hold 空间 - # 然后继续下一个循环 - p # 输出其它行 - $ { g;p } # 在最后一行上 - # 取得并输出 hold 空间中的内容 + # 追回匹配的行到保持空间 + # 然后继续下一个循环 + p # 输出其它行 + $ { g;p } # 在最后一行上 + # 获取并打印保持空间中的内容 ' - ``` -### 复习 print、delete 和 next - -现在你已经更熟悉使用 hold 空间了,我们回到 print、delete 和 next 命令。我们已经讨论了小写的 `p`、`d` 和 `n` 命令了。而它们也有大写的版本。因为每个命令都有大小写版本,似乎是 Sed 的习惯,这些命令的大写版本将与多行缓冲区有关: - - * `P` -将模式空间中第一个新行之前的内容输出 - - * `D` -删除模式空间中的内容并且包含新行,然后不读取任何新的输入而是使用剩余的文本去重启一个循环 - - * `N` -使用一个换行符作为新旧数据的分隔符,然后读取并追加一个输入的新行到模式空间。继续运行当前的循环。 - +### 复习打印、删除和次行命令 +现在你已经更熟悉使用保持空间了,我们回到打印、删除和次行命令。我们已经讨论了小写的 `p`、`d` 和 `n` 命令了。而它们也有大写的版本。因为每个命令都有大小写版本,似乎是 Sed 的习惯,这些命令的大写版本将与多行缓冲区有关: +* `P` 将模式空间中第一个新行之前的内容输出 +* `D` 删除模式空间中第一个新行之前的内容(包含新行),然后不读取任何新的输入而是使用剩余的文本去重启一个循环 +* `N` 读取输入并追加一个新行到模式空间,用一个新行作为新旧数据的分隔符。继续运行当前的循环。 ![The Sed uppercase `Delete` command][27] + ![The Sed uppercase `Next` command][28] 这些命令的使用场景主要用于实现队列([FIFO 列表][29])。从一个输入文件中删除最后 5 行就是一个很权威的例子: + ``` cat -n inputfile | sed -En -e ' - 1 { N;N;N;N } # 确保 pattern 空间中包含 5 行 + 1 { N;N;N;N } # 确保模式空间中包含 5 行 - N # 追加第 6 行到队列中 - P # 输出队列的第 1 行 - D # 删除队列的第 1 行 + N # 追加第 6 行到队列中 + P # 输出队列的第 1 行 + D # 删除队列的第 1 行 ' - ``` 作为第二个示例,我们可以在两个列上显示输入数据: + ``` # 输出两列 sed < inputfile -En -e ' - $!N # 追加一个新行到 pattern 空间 - # 除了输入文件的最后一行 - # 当在输入文件的最后一行使用 N 命令时 - # GNU Sed 和 POSIX Sed 的行为是有差异的 - # 需要使用一个技巧去处理这种情况 - # https://www.gnu.org/software/sed/manual/sed.html#N_005fcommand_005flast_005fline + $!N # 追加一个新行到模式空间 + # 除了输入文件的最后一行 + # 当在输入文件的最后一行使用 N 命令时 + # GNU Sed 和 POSIX Sed 的行为是有差异的 + # 需要使用一个技巧去处理这种情况 + # https://www.gnu.org/software/sed/manual/sed.html#N_005fcommand_005flast_005fline - # 用空间填充第 1 行的第 1 个字段 - # 并丢弃其余行 - s/:.*\n/ \n/ - s/:.*// # 除了第 2 行上的第 1 个字段外,丢弃其余的行 - s/(.{20}).*\n/\1/ # 修剪并连接行 - p # 输出结果 + # 用空间填充第 1 行的第 1 个字段 + # 并丢弃其余行 + s/:.*\n/ \n/ + s/:.*// # 除了第 2 行上的第 1 个字段外,丢弃其余的行 + s/(.{20}).*\n/\1/ # 修剪并连接行 + p # 输出结果 ' - ``` ### 分支