Bash 成为每个类 Unix 或基于 Unix 的操作系统的默认自动化语言。每个系统管理员、DevOps 工程师和程序员通常使用 Bash 来编写具有重复命令序列的 shell 脚本。Bash 脚本通常包含运行其他程序二进制文件的命令。在大多数情况下,我们可能需要在 shell 脚本中处理数据并创建逻辑流程。因此,我们经常不得不在我们的 shell 脚本中添加条件语句和文本操作语句。
传统的 Bash 脚本和过去使用旧 Bash 解释器版本的程序员通常使用awk、sed、tr和cut命令进行文本操作。这些是单独的程序。尽管这些文本处理程序提供了很好的功能,但它们会减慢您的 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
上述ver1和ver2变量在声明期间接收一个大小写属性,因此每当您为特定变量赋值时,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[@]} " # 包含当前目录内容