Contents

在Bash中提取子字符串

1. 概述

从字符串中提取子字符串是 Linux 中文本处理的基本且常见的操作。

在本教程中,我们将了解使用 Linux 命令行提取子字符串的各种方法。

2. 问题介绍

顾名思义,子字符串是字符串的一部分。问题很简单:我们想提取给定字符串的一部分。但是,有两种不同类型的提取要求:基于索引的和基于模式的

让我们通过几个例子来了解这两种不同的需求。

基于索引的子字符串由原始字符串的开始和结束索引定义。让我们看一下提取基于索引的子字符串的场景。

假设我们有一个输入字符串“ 0123Linux9 ”,我们想要从索引位置 4 到 8 中提取子字符串。那么,预期的结果将是“ Linux ”。

接下来,让我们看一个基于模式的子字符串的示例。

例如,我们有一个输入字符串“ Eric,Male,28,USA ”。它是一串以逗号分隔的值(Name,Gender,Age,Country)。

现在,假设我们要提取第三个字段28,即Eric的年龄。在这种情况下,我们无法预测目标子字符串的起始索引,因为NameGender具有动态长度。因此,实现将不同于基于索引的提取。

在本文中,我们将介绍一些在 Linux 命令行中提取子字符串的常用方法。当然,我们将涵盖这两种提取类型。

3. 提取基于索引的子字符串

首先,让我们看看如何提取基于索引的子字符串。我们将介绍四种方法来做到这一点:

接下来,我们将看到它们的实际应用。

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: ”中删除直到输入字符串的结尾

在执行这两个替换之后,我们将得到预期的结果。我们需要做的就是打印出来。