在Bash中提取子字符串
1. 概述
从字符串中提取子字符串是 Linux 中文本处理的基本且常见的操作。
在本教程中,我们将了解使用 Linux 命令行提取子字符串的各种方法。
2. 问题介绍
顾名思义,子字符串是字符串的一部分。问题很简单:我们想提取给定字符串的一部分。但是,有两种不同类型的提取要求:基于索引的和基于模式的。
让我们通过几个例子来了解这两种不同的需求。
基于索引的子字符串由原始字符串的开始和结束索引定义。让我们看一下提取基于索引的子字符串的场景。
假设我们有一个输入字符串“ 0123Linux9 ”,我们想要从索引位置 4 到 8 中提取子字符串。那么,预期的结果将是“ Linux ”。
接下来,让我们看一个基于模式的子字符串的示例。
例如,我们有一个输入字符串“ Eric,Male,28,USA ”。它是一串以逗号分隔的值(Name,Gender,Age,Country)。
现在,假设我们要提取第三个字段28,即Eric的年龄。在这种情况下,我们无法预测目标子字符串的起始索引,因为Name和Gender具有动态长度。因此,实现将不同于基于索引的提取。
在本文中,我们将介绍一些在 Linux 命令行中提取子字符串的常用方法。当然,我们将涵盖这两种提取类型。
3. 提取基于索引的子字符串
首先,让我们看看如何提取基于索引的子字符串。我们将介绍四种方法来做到这一点:
- 使用 cut 命令
- 使用awk 命令
- 使用Bash 的子字符串扩展
- 使用 expr 命令
接下来,我们将看到它们的实际应用。
3.1.使用 cut命令
我们可以使用cut命令从输入字符串中提取第 N 个直到第M 个字符: cut -c NM。
正如我们在前一节中讨论的那样,我们的要求是从索引 4 到索引 8 获取子字符串。
在这里,当我们谈论索引时,它是在 Bash 的上下文中,这意味着它是一个从 0 开始的索引。
因此,如果我们想使用 cut命令解决问题,我们需要在开始和结束索引中添加一个。因此,范围将变为 5-9。 现在,让我们看看 cut命令是否可以解决问题:
$ cut -c 5-9 <<< '0123Linux9'
Linux
如输出所示,我们得到了预期的子字符串“ Linux ”——问题已解决。
在上面的示例中,我们通过here-string 将输入字符串传递给cut命令并保存了一个 echo过程。
3.2. 使用awk命令
当我们需要解决 Linux 中的一些文本处理问题时,我们不应该忘记瑞士军刀:awk。
awk 脚本有一个内置的*substr()*函数。因此,我们可以直接调用该函数来获取子字符串。
*substr(s , i, n)*函数接受三个参数。让我们仔细看看它们:
- s -输入字符串
- i – 子字符串的起始索引(awk使用从 1 开始的索引系统)
- n – 子字符串的长度。如果省略, awk将从索引 i返回直到输入字符串中的最后一个字符作为子字符串
现在,让我们看看 awk的 *substr()*函数是否可以给我们预期的结果:
$ awk '{print substr($0, 5, 5)}' <<< '0123Linux9'
Linux
好的!awk命令按预期工作。 在这里,我们通过 i=5。这是因为我们需要从 1 开始的索引。第二个参数 5 是目标子串的长度,我们通过8-4+1 得到它。
3.3. 使用 Bash 的子字符串扩展
我们已经看到cut 和 awk如何轻松提取基于索引的子字符串。
或者,Bash 足以解决问题,因为它支持通过*${VAR:start_index:length}* 进行子字符串扩展。
今天,Bash 是许多现代 Linux 发行版的默认 shell。换句话说,我们可以在不使用任何外部命令的情况下解决问题:
$ STR="0123Linux9"
$ echo ${STR:4:5}
Linux
正如我们在上面的输出中看到的,我们已经使用纯 Bash 解决了这个问题。
3.4. 使用expr命令
即使 Bash 在大多数 Linux 发行版上都可用,仍然有一些 Linux 系统没有 Bash,特别是在嵌入式 Linux 世界中。
expr命令是Coreutils 包的 成员。因此,它适用于所有 Linux 系统。 此外,expr还有一个 substr子命令,我们可以使用它轻松提取基于索引的子字符串:
expr substr <input_string> <start_index> <length>
值得一提的是expr命令使用从 1 开始的索引系统。
让我们使用 expr和 substr命令来解决我们的问题:
$ expr substr "0123Linux9"5 5
Linux
上面的输出表明 expr命令已经解决了这个问题。
4. 提取基于模式的子串
我们已经学习了几种提取基于索引的子字符串的方法。接下来,在本节中,让我们看看基于模式的子字符串。
这些解决方案可能看起来与基于索引的解决方案不同,但它们也很容易学习。
我们将解决两种方法来解决我们的问题:
- 使用 cut命令
- 使用 awk命令 此外,我们将看看一个不同的基于模式的子字符串提取问题。
4.1.使用 cut命令
cut命令是处理基于字段的数据的便捷工具。
让我们快速回顾一下我们的问题。我们的输入字符串是逗号分隔值:“Eric,Male,28,USA”。我们的目标是提取第三个字段“ 28 ”。
为了解决这个问题,我们可以告诉 cut字符串是用逗号分隔的(-d ,),然后让 cut给我们第三个字段(-f 3):
$ cut -d , -f 3 <<< "Eric,Male,28,USA"
28
我们得到了预期的结果并解决了问题。
4.2. 使用 awk命令
awk还擅长处理基于字段的数据。一个紧凑的同一行awk可以解决这个问题:
$ awk -F',' '{print $3}' <<< "Eric,Male,28,USA"
28
此外,由于 awk的字段分隔符 ( FS ) 支持正则表达式,我们可以使用awk构建更通用的解决方案 。
例如,如果我们通过在每个逗号后添加一个空格来更改输入字符串,我们就有“ Eric, Male, 28, USA ”。这是我们在现实世界中可以看到的常见格式。
在这种情况下, cut命令将不是解决问题的好选择。这是因为cut命令仅支持单个字符作为字段分隔符。
但是,对于awk来说,这仍然是小菜一碟 :
$ awk -F', ' '{print $3}' <<< "Eric, Male, 28, USA"
28
我们甚至可以编写一个 awk命令来处理这两种情况。这在现实世界中可能是一个有用的技巧:
$ awk -F', ?' '{print $3}' <<< "Eric, Male, 28, USA"
28
$ awk -F', ?' '{print $3}' <<< "Eric,Male,28,USA"
28
4.3. 一个不同的基于模式的子串案例
至此,我们已经解决了“ Eric 的年龄”问题。在这个问题中,我们的输入是一个基于字段的值。
但是,在实践中,基于模式的子字符串可能并不总是位于 CSV 条目中。让我们看另一个例子。
假设我们有一个输入字符串“ whatever dataBEGIN:Interesting dataEND:something else ”,我们的目标是提取“ BEGIN: ”和“ END: ”之间的子字符串——即两个模式之间。
显然, cut命令在这种情况下无法帮助我们。但这对 awk 来说仍然不是挑战。它可以通过不同的方式解决这个问题。
接下来,让我们看看awk是如何解决的。我们将输入字符串保存在变量*$STR*中,以使命令更易于阅读:
$ STR="whatever dataBEGIN:Interesting dataEND:something else"
$ awk -F'BEGIN:|END:' '{print $2}' <<< "$STR"
Interesting data
$ awk '{ sub(/.*BEGIN:/, ""); sub(/END:.*/, ""); print }' <<< "$STR"
Interesting data
第一个 awk命令将“ BEGIN: ”或“ END: ”定义为字段分隔符,并采用第二个字段。
但是,第二个awk解决方案不会调整字段分隔符。相反,它应用两个正则表达式替换来实现目标:
- sub(/.*BEGIN:/, “”) - 删除从字符串开头到“ BEGIN: ”的所有内容
- *sub(/END:.*/, “”) – 从“ END: ”中删除直到输入字符串的结尾
在执行这两个替换之后,我们将得到预期的结果。我们需要做的就是打印出来。