Contents

Bash中的字符串操作

1. 概述

Bash 是与 sh 兼容的 shell 和命令处理器,字符串操作是 shell 环境中最常见的任务之一。

在本教程中,我们将学习如何使用 Bash 对字符串进行操作。

2. 字符串变量声明与赋值

**Bash 没有类型系统,所有变量都是字符串。**但是,变量可以具有改变或约束其行为的属性,即使在处理字符串时也是如此。

2.1. 声明

一个简单的声明和赋值如下所示:

$ VAR1='Hello World'
$ VAR2=Hello

请注意,等号前后没有空格。**如果我们想为变量分配属性,我们可以使用declare 命令。**例如,-r标志将使其只读:

$ declare -r VAR1='Hello world'

现在,如果我们尝试为该变量分配其他值,我们将得到一个错误:

$ VAR1='Good morning Vietnam'
-bash: VAR1: readonly variable

2.2. read

我们可以使用read 命令要求用户输入

$ read -p 'Type your name and press enter: ' NAME
Type your name and press enter: Blogdemo
$ echo "Hello $NAME"
Hello Blogdemo

-p标志允许我们指定提示文本而无需输入额外的echo 命令。该命令的最后一个参数是变量的名称。如果我们在此处不指定名称,则默认为REPLY

3. 模式匹配和替换

3.1. 长度

我们可以在变量名之前的参数扩展 中使用哈希 (#) 运算符来访问字符串的长度:

$ NAME=Blogdemo
$ echo ${#NAME}
8

3.2. 子串

我们可以在参数扩展中使用冒号 (:) 运算符提取子字符串,提供子字符串的起始位置和可选的子字符串长度

$ NAME=Blogdemo
$ echo ${NAME:6}
ng
$ echo ${NAME:0:4}
Bael

3.2. 模式匹配

**Bash 有一个内置的简单模式匹配系统 。**它由几个通配符组成:

  • * - 匹配任意数量的字符
    • – 匹配一个或多个字符
  • [abc] – 只匹配给定的字符

例如,我们可以使用条件语句检查文件是否具有 .jpg扩展名:

$ if [[ "file.jpg" = *.jpg ]]; then echo "is jpg"; fi
is jpg

**还有一个扩展匹配系统,称为“扩展通配符”。**它使我们能够将通配符限制为特定模式:

  • *(pattern) - 匹配任意数量的模式出现
  • ?(pattern) – 匹配零次或一次出现的模式
  • +(pattern) – 匹配一个或多个出现的模式
  • !(pattern) – 否定模式,匹配任何不匹配模式的东西

必须使用*shopt * 命令打开扩展通配符。我们可以改进最后一个片段以匹配*.jpeg*扩展名:

$ shopt -s extglob
$ if [[ "file.jpg" = *.jp?(e)g ]]; then echo "is jpg"; fi
is jpg

如果我们需要更具表现力的模式语言,我们还可以使用带有不等于 (=~) 运算符的正则表达式:

$ if [[ "file.jpg" =~ .*\.jpe?g ]]; then echo "is jpg"; fi
is jpg

我们可以在这里使用扩展正则表达式 ,例如使用*-E标志调用grep时。 如果我们使用捕获组,它们将存储在BASH_REMATCH*数组变量中,以后可以访问。

3.3. 删除匹配的子字符串

**Bash 为我们提供了一种使用参数扩展从给定字符串中删除子字符串的机制。**它总是只删除一个匹配的子字符串。根据使用情况,它可以匹配最长或最短的子字符串,并从开头或结尾开始匹配。

**需要注意的是,它不会修改变量,只会返回修改后的值。**为了明确这一事实,我们将在示例中使用只读变量。

所以,让我们从文件名中删除扩展名。**为此,我们需要使用百分比 (%) 运算符从字符串末尾开始匹配。**单数运算符将匹配最短的子字符串,双精度将匹配最长的子字符串:

$ declare -r FILENAME="index.component.js"
$ echo ${FILENAME%.*}
index.component

因为我们使用了单数百分号,所以我们只匹配*.js*子字符串。如果我们想过滤掉我们要做的所有扩展:

$ declare -r FILENAME="index.component.js"
$ echo ${FILENAME%%.*}
index

我们也可以删除文件名,只留下扩展名。在这种情况下,我们需要使用哈希 (#) 运算符从头开始:

$ declare -r FILENAME="index.component.js"
$ echo ${FILENAME#*.}
component.js

与前面的示例类似,如果我们只想保留最后一个扩展名,我们需要使用双哈希:

$ declare -r FILENAME="index.component.js"
$ echo ${FILENAME##*.}
js

3.4. 替换匹配的子串

我们可以使用斜杠 (/) 运算符替换它,而不是仅仅删除子字符串。  单数运算符更改第一个匹配项,双精度运算符更改所有匹配项。两者都匹配最长的子字符串。

让我们编写代码来更改文件名,同时保持扩展名不变:

$ declare -r FILENAME="index.component.js"
$ echo ${FILENAME/*./index.}
index.js

4. 案例研究

让我们充分利用上述功能。我们将编写一个脚本来更新提供的文件中的所有旧版本字符串(例如 1.0.1 到 1.1.0),并保留旧文件的备份,并附上旧版本的名称。

我们将文件名和版本字符串作为参数传递给脚本 ,但我们将重新声明它们以使其余代码更具可读性。最后,我们将使用重定向 机制来保存修改后的内容。

#!/bin/sh
declare -r FILENAME=$1
declare -r OLD_VERSION=$2
declare -r NEW_VERSION=$3
declare -r BACKUP_FILENAME=${FILENAME%.*}'_'$OLD_VERSION'.'${FILENAME##*.}
declare -r CONTENT=`cat $FILENAME`
cp $FILENAME $BACKUP_FILENAME
echo "${CONTENT//$OLD_VERSION/$NEW_VERSION}" > $FILENAME