从每行中获取最后一个词
1. 概述
当我们在 Linux 命令行下处理文件时,我们经常需要对输入文件的每一行进行操作,比如删除每一行的最后一个字符 。
这一次,让我们看另一个问题:从每一行中提取最后一个单词。
2. 问题介绍
2.1. 示例输入
示例总是有助于快速理解问题。
首先,让我们看一个输入文件:
$ cat input.txt
Linux rocks!
Next line is an empty line:
I have trailing spaces:
I have a number: 42
我们的 input.txt 有几行文本。
此外,该文件包含一个空行和一个带有尾随空格的行。但是,此信息在上面的输出中并不那么明显。
带有-e*选项的cat 命令将在每行的末尾打印一个“ $ ”符号:*
$ cat -e input.txt
Linux rocks!$
Next line is an empty line:$
$
I have trailing spaces: $
I have a number: 42$
现在,我们可以清楚地看到输出中的尾随空格。
让我们重新审视我们的目标,“从每一行中提取最后一个单词”——这似乎很清楚。但是,有几件事我们需要注意。
2.2. 一个词的定义
一个词可以有不同的定义:
- 一个单词可能意味着一个英文单词——像“ ab_cd_1234 ”这样的字符串不算数。
- 单词是匹配正则表达式“ \w+ ”的字符串。也就是说,它只包含字母数字字符(字母或数字,不分大小写)或下划线字符(“ _ ”)。例如,“ ab_cd_1234 ”是一个正则表达式单词,但“ ab.cd#1234 ”不是。
- 单词是任何非空白字符的组合。例如,“ ab_cd_1234 ”和“ ab.cd#1234 ”都是单词。
我们对“词”的定义会影响问题的解决方案。因此,在本教程中,我们将上面列表中的最后一个作为“单词”的定义。
2.3. 处理尾随空格
根据要求,当一行包含尾随空格时,问题可能有两种不同的变体:
- 结果返回一个空字符串。
- 返回最后一个非空白字符序列。如果整行是空白或空的,我们希望得到一个空字符串作为结果。
在本教程中,我们将介绍这两种变体并解决解决问题的两种方法:
接下来,让我们看看他们的行动。
3. 使用sed 命令
sed是一个非交互式流编辑实用程序。让我们看看如何使用这个出色的工具来解决问题。
3.1. 尾随空格:取一个空字符串
使用 sed命令解决问题的一个方法是删除行中最后一个水平空白字符之前的所有内容,例如空格或制表符。 sed的“ s/pattern/replacement/ ”命令很好的解决了这个问题:
$ sed 's/.*[[:blank:]]//' input.txt | cat -e
rocks!$
line:$
$
$
42$
如上例所示,我们 将sed的输出通过管道传送到cat -e命令,以便更轻松地检查空白字符。
输出是我们所期望的。此外,我们注意到对于空行和带有尾随空格的行,我们将空字符串作为单词。
值得一提的是[:blank:]是一个 POSIX 标准字符类。
我们在本教程中使用了 GNU sed ,因此如果我们使用“ \s ”而不是*[[:blank:]]*,该解决方案也可以正常工作。但是,使用 POSIX 标准字符类使解决方案最便携。
3.2. 尾随空格:取最后一个非空白字符序列
如果我们已经解决了问题的第一个变体,那么解决这个问题对我们来说不是挑战。
我们可以通过添加预处理步骤来扩展第一个解决方案:删除所有尾随空格。
简单来说,我们可以先右剪线,然后应用“ s/.*[[:blank:]]// ”替换命令:
$ sed 's/[[:blank:]]*$//; s/.*[[:blank:]]//' input.txt | cat -e
rocks!$
line:$
$
spaces:$
42$
同样,我们将sed的输出传送到cat -e命令以验证空白字符。
如上面的输出所示,我们为输入文件中的空行提供了一个空字符串,而对于带有尾随空格的行,我们提取了最后一个非空白字符序列(“空格: ”)作为结果.
4. 使用 awk命令
awk是 Linux 命令行下另一个强大的文本处理工具。
与sed类似, awk命令提供了替换函数sub()和gsub()。因此,我们当然可以在这里采取同样的思路来解决问题。
但是,awk默认为基于字段的输入提供了良好的支持。例如,我们可以将一行中的每个单词视为一个字段。
因此,如果要求提取最后一个单词,那么我们只需让awk返回最后一个字段。
但是,在我们开始查看问题的 awk解决方案之前,让我们花几分钟时间仔细查看awk的FS变量。
4.1. 简而言之awk FS变量
awk对FS变量的值的处理方式取决于我们如何定义它,我们可以用三种不同的方式定义FS:
- 作为空字符串
- 作为单个字符
- 作为多个字符
让我们看看awk如何处理每种情况。
首先,如果FS为空,则输入记录中的每个字符都是一个字段:
$ awk 'BEGIN{FS=""}{print $1,$2,$3}' <<< "AWK"
A W K
其次,如果FS是单个字符,则文字字符将是分隔符:
$ awk 'BEGIN{FS="*"}{print $1,$2,$3}' <<< "A*W*K"
A W K
但是,在这种情况下,有一个例外。
**当FS是单个空格字符时,也是默认值,分隔符将与 Regex 分隔符“ [[:space:]]+ ”或“ [[ \t\n]]+ ”**相同:
$ awk 'BEGIN{FS=" "}{print $1,$2,$3}' <<< " A W K "
A W K
第三,如果FS的值不是空的或单个字符,则awk将其视为正则表达式:
$ awk 'BEGIN{FS="[#@]"}{print $1,$2,$3}' <<< "A#[[email protected]](/cdn_cgi/l/email_protection)"
A W K
现在,让我们看看如何通过调整FS变量来解决这个问题。
4.2. 尾随空格:取一个空字符串
如果我们希望在一行有尾随空白字符或空白时得到一个空字符串,我们需要使用水平空白字符类设置awk的FS内置变量:
$ awk -F'[[:blank:]]' '{print $NF}' input.txt | cat -e
rocks!$
line:$
$
$
42$
我们应该注意,使用“ \s ”设置FS变量适用于某些 awk实现,例如广泛使用的 GNU awk。
但是,我们需要转义反斜杠:awk -F’\s’ ‘{print $NF}’ input.txt。否则,awk会将“ \s ”视为文字“ s ”。
4.3. 尾随空格:取最后一个非空白字符序列
为了解决这个问题的变体,我们可以编写一个更紧凑的 awk单行代码:
$ awk '{print $NF}' input.txt | cat -e
rocks!$
line:$
$
spaces:$
42$
正如我们所见,我们得到了预期的输出。
敏锐的眼睛会看到,在上面的awk命令中,我们没有设置FS变量。也就是说,我们使用FS的默认值。
正如我们所了解的,默认的FS将清除字段中的前导和尾随空白字符。因此,短的单线可以完成这项工作。