提升开发能力的5中 Bash 字符串操作方法

1,401 阅读5分钟

Bash 成为每个类 Unix 或基于 Unix 的操作系统的默认自动化语言。每个系统管理员、DevOps 工程师和程序员通常使用 Bash 来编写具有重复命令序列的 shell 脚本。Bash 脚本通常包含运行其他程序二进制文件的命令。在大多数情况下,我们可能需要在 shell 脚本中处理数据并创建逻辑流程。因此,我们经常不得不在我们的 shell 脚本中添加条件语句和文本操作语句。

传统的 Bash 脚本和过去使用旧 Bash 解释器版本的程序员通常使用awksedtrcut命令进行文本操作。这些是单独的程序。尽管这些文本处理程序提供了很好的功能,但它们会减慢您的 Bash 脚本,因为每个特定命令都有相当长的进程启动时间。现代 Bash 版本通过众所周知的参数扩展功能提供内置的文本处理功能。

在这个故事中,我将解释一些内置的字符串操作语法,您可以使用这些语法在 Bash 脚本中高效地处理文本。

子串提取和替换

子字符串指的是传染性片段或特定字符串的一部分。在各种脚本场景中,我们需要从字符串段中提取子串。例如,您可能只需要从包含扩展名的完整文件名中获取文件名段。此外,您可能需要用特定的字符串段替换子字符串(即,更改文件名的文件扩展名)。

通过提供字符位置和长度,子字符串提取非常容易:

#!/bin/bash

 str= "2023-10-12" 

echo  " ${str:5:2} "  # 10 
echo  " ${str::4} "  # 2023 
echo  "2022- ${str:5} ”  #2022-10-12

您甚至可以从右侧进行子串计算,如下所示:

#!/bin/bash

 str= "backup.sql" 

echo  "original ${str:(-4)} "  # original.sql

Bash 还为子字符串替换提供了高效的内置语法:

#!/bin/bash

 str= "obin-linux_x64_bin" 

echo  " ${str/x64/armhf} "  # obin-linux_armhf_bin 
echo  " ${str/bin/dist} "  # odist-linux_x64_bin 
echo  " ${str// bin/dist} "  # odist-linux_x64_dist

当您使用某些字符串(如文件名、路径等)时,您可能必须替换字符串前缀和后缀。用另一个扩展名替换文件扩展名就是一个很好的例子。看下面的例子:

#!/bin/bash

 str= "db_config_backup.zip" 

echo  " ${str/%.zip/.conf} "  # db_config_backup.conf 
echo  " ${str/#db/settings} "  # settings_config_backup.zip

在上面的子串替换例子中,我们使用了精确的子串段来进行匹配,但是你也可以使用通配符来使用子串的一部分,*如下所示:

#!/bin/bash

 str= "db_config_backup.zip" 

echo  " ${str/%.*/.bak} "  # db_config_backup.conf 
echo  " ${str/#*_/new} "  # newbackup.zip

如果您不知道要搜索的确切子字符串,上述方法很有用。

正则表达式匹配、提取和替换

许多 Unix 或 GNU/Linux 用户已经知道,可以使用grepandsed进行基于正则表达式的文本搜索。sed帮助我们进行正则表达式替换。您可以使用内置的 Bash 正则表达式功能来比这些外部二进制文件更快地处理文本。

您可以使用 if 条件和=~运算符执行正则表达式匹配,如以下代码片段所示:

#!/bin/bash

 str= "db_backup_2003.zip"

如果[[ $str =~ 200[0-5]+ ]]; 然后
    echo  "regex_matched" 
fi

如果需要,您还可以用内联条件替换 if 语句:

[[ $str =~ 200[0-5]+ ]] && echo "regex_matched"

一旦 Bash 解释器执行正则表达式匹配,它通常会将所有匹配项存储在BASH_REMATCHshell 变量中。该变量是一个只读数组,将匹配到的全部数据存放在第一个索引中。如果你使用子模式,Bash 会逐渐将这些匹配保留在其他索引中:

#!/bin/bash

 str= "db_backup_2003.zip"

如果[[ $str =~ (200[0-5])(.*)$ ]]; 然后
    echo  " ${BASH_REMATCH[0]} "  # 2003.zip 
    echo  " ${BASH_REMATCH[1]} "  # 2003 
    echo  " ${BASH_REMATCH[2]} "  # .zip 
fi

还记得我们在之前的子字符串匹配中使用了通配符吗?同样,可以在参数扩展中使用正则表达式定义,如以下示例所示:

#!/bin/bash

 str= "db_backup_2003.zip"
 re= "200[0-3].zip" 

echo  " ${str/$re/new} .bak"  # db_backup_new.bak

子串删除技术

在很多文本处理需求中,我们经常需要通过去除不需要的子串来预处理文本片段。例如,如果您提取带有v前缀和一些内部版本号的版本号并想要找到主要版本号,则必须删除一些子字符串。您可以使用相同的子字符串替换语法,但省略字符串删除的替换字符串参数,如下所示:

#!/bin/bash

 str= "ver5.02-2224.e2"

 ver= " ${str#ver} " 
echo  $ver  # 5.02-2224.e2

 maj= " ${ver/.*} " 
echo  $maj  # 5

在上面的示例中,我们使用了精确的子字符串和通配符来删除子字符串,但您也可以使用正则表达式。检查如何提取没有过多字符的干净版本号:

#!/bin/bash

 str= "ver5.02-2224_release"

 ver= " ${str//[a-z_]} " 
echo  $ver  # 5.02-2224

个案转换和基于个案的变量

即使是标准的 C 语言也提供了转换字符大小写的函数。几乎所有现代编程语言都提供了大小写转换的内置函数。作为一种命令语言,Bash 不提供大小写转换功能,但它通过参数扩展和变量声明为我们提供了大小写转换功能。

查看以下转换字母大小写的示例:

#!/bin/bash

 str= "你好 Bash!" 

lower= " ${str,,} "
 upper= " ${str^^} " 

echo  $lower  # 你好 bash!
echo  $upper  # 你好 BASH!

您还可以只将特定字符串的第一个字符大写或小写,如下所示:

#!/bin/bash

 ver1= "V2.0-release"
 ver2= "v4.0-release" 

echo  " ${ver1,} "  # v2.0-release 
echo  " ${ver2^} "  # V4.0 -发布

如果您需要使特定变量严格为大写或小写,则无需一直运行大小写转换函数。相反,您可以使用内置命令将案例属性添加到特定变量declare,如以下示例所示:

#!/bin/bash 

declare -l ver1 
declare -u ver2 

ver1= "V4.02.2"
 ver2= "v2.22.1" 

echo  $ver1  # v4.02.2 
echo  $ver2  #V2.22.1

上述ver1ver2变量在声明期间接收一个大小写属性,因此每当您为特定变量赋值时,Bash 都会根据变量属性转换文本大小写。

拆分字符串(字符串到数组的转换)

declareBash 允许您使用内置的定义索引和关联数组。大多数通用编程语言都split在字符串对象中或通过标准库函数(Go 的strings.Split函数)提供方法。您可以在 Bash 中使用多种方法拆分字符串并创建数组。例如,我们可以更改IFS为所需的分隔符并使用read内置的。或者,我们可以使用tr带有循环的命令并构造数组。或者,使用内置参数扩展是另一种方法。Bash 中有很多字符串拆分方法。

使用IFSandread是拆分字符串的最简单且无错误的方法之一:

#!/bin/bash

 str= "C,C++,JavaScript,Python,Bash"

 IFS= ','  read -ra arr <<< " $str " 

echo  " ${#arr[@]} "  # 5 
echo  " ${arr[0]} "  # C 
echo  " ${arr[4]} "  # Bash

上面的代码片段用作,分割分隔符,并使用read内置命令创建一个基于IFS.

即使有最简单的方法来处理没有 的拆分read,也要确保没有隐藏的问题。例如,下面的拆分实现非常简单,但是当您将*(扩展到当前目录的内容)作为元素并以空格作为分隔符时,它会中断:

#!/bin/bash 

# 警告:这段代码有几个隐藏的问题。

str= "C,Bash,*"

 arr=( ${str//,/ } ) 

echo  " ${#arr[@]} "  # 包含当前目录内容