Shell脚本虽然是一门脚本语言,但它同样提供了丰富的数据处理能力。本章将深入介绍Shell中的字符串处理、数组操作以及算术运算,帮助你掌握Shell脚本的核心数据处理技能。
3.1 字符串处理
字符串是Shell脚本中最常用的数据类型,Shell提供了丰富的字符串操作功能。
3.1.1 字符串拼接
Shell中的字符串拼接非常直接,将变量直接相邻放置即可:
bash
# 字符串拼接
str1="Hello"
str2="World"
# 直接拼接
str3=$str1$str2
echo $str3 # 输出:HelloWorld
# 使用双引号拼接
str4="${str1} ${str2}"
echo $str4 # 输出:Hello World
# 拼接变量和常量
str5="${str1}Shell"
echo $str5 # 输出:HelloShell
3.1.2 字符串截取
Shell提供了多种字符串截取方式:
bash
str="HelloShellWorld"
# 1. 从左边开始截取(#号是去掉左边,最短匹配)
echo ${str#Hello} # 输出:ShellWorld(去掉最短的"Hello")
echo ${str#*l} # 输出:loShellWorld(去掉第一个"l"及其左边的内容)
# 2. 从左边开始截取(##号是去掉左边,最长匹配)
echo ${str##Hello} # 输出:ShellWorld(和上面一样,因为开头就是Hello)
echo ${str##*l} # 输出:d(去掉最长的"l"及其左边的内容)
# 3. 从右边开始截取(%号是去掉右边,最短匹配)
echo ${str%World} # 输出:HelloShell(去掉最短的"World")
echo ${str%l*} # 输出:HelloShellWor(去掉第一个"l"及其右边的内容)
# 4. 从右边开始截取(%%号是去掉右边,最长匹配)
echo ${str%%l*} # 输出:Hel(去掉最长的"l"及其右边的内容)
# 5. 提取指定位置的字符
echo ${str:0} # 输出:HelloShellWorld(从第0个字符开始到最后)
echo ${str:1} # 输出:elloShellWorld(从第1个字符开始到最后)
echo ${str:0:5} # 输出:Hello(从第0个字符开始,截取5个字符)
echo ${str:5:5} # 输出:Shell(从第5个字符开始,截取5个字符)
# 6. 从右边开始计数截取
echo ${str:0-5} # 输出:World(从右边第5个字符开始到最后)
echo ${str:0-5:5} # 输出:World(从右边第5个字符开始,截取5个字符)
echo ${str: -5} # 输出:World(注意:冒号后有空格)
3.1.3 字符串替换
bash
str="HelloShellWorld"
# 1. 替换第一个匹配的字符串
echo ${str/Hello/Hi} # 输出:HiShellWorld
# 2. 替换所有匹配的字符串
echo ${str//l/L} # 输出:HeLLoSheLLWorLD
# 3. 替换开头匹配的字符串
echo ${str/#Hello/Hi} # 输出:HiShellWorld
# 4. 替换结尾匹配的字符串
echo ${str/%World/Shell} # 输出:HelloShellShell
# 5. 删除匹配的第一个字符串
echo ${str/Hello/} # 输出:ShellWorld
# 6. 删除所有匹配的字符串
echo ${str//l/} # 输出:HeoSheWorD
3.1.4 获取字符串长度
bash
str="HelloShell"
# 方法1:使用 #{}
echo ${#str} # 输出:10
# 方法2:使用 expr
length=$(expr length "$str")
echo $length # 输出:10
# 方法3:使用 wc
length=$(echo -n "$str" | wc -c)
echo $length # 输出:10
3.1.5 字符串大小写转换(Bash 4.0+)
bash
str="HelloShell"
# 转为小写
echo ${str,,} # 输出:helloshell
# 转为大写
echo ${str^^} # 输出:HELLOSHELL
# 首个字符转为大写
echo ${str^} # 输出:HelloShell
# 首个字符转为小写
echo ${str,} # 输出:hELLO SHELL(仅第一个字符)
3.1.6 字符串高级操作技巧
在实际开发中,字符串处理还有很多实用技巧:
bash
# 1. 去除字符串首尾空白
str=" Hello World "
echo "${str}" # 输出: Hello World
echo "${str#"${str%%[![:space:]]*}"}" # 去除开头空白
echo "${str%"${str##*[![:space:]]}"}" # 去除结尾空白
# 2. 字符串切片的高级用法
str="HelloShellWorld"
# 从指定位置截取到末尾
echo ${str:5} # 输出:ShellWorld
# 负数索引(从右边计数)
echo ${str:-5} # 输出:World(如果变量未定义或为空,返回默认值)
# 3. 使用变量作为索引
str="HelloShell"
index=3
echo ${str:index:2} # 输出:lo
# 4. 字符串模式匹配替换
url="http://www.example.com/path/to/page"
# 去掉开头匹配的最短模式
echo ${url#*:} # 输出://www.example.com/path/to/page
# 去掉开头匹配的最长模式
echo ${url#*/} # 输出:/www.example.com/path/to/page
# 去掉结尾匹配的最短模式
echo ${url%/*} # 输出:http://www.example.com/path/to
# 去掉结尾匹配的最长模式
echo ${url%%/*} # 输出:http:
# 5. 计算字符在字符串中出现的次数
str="HelloWorld"
count=${//[^l]/} # 移除所有非l字符
echo ${#count} # 输出:3(l出现了3次)
3.2 数组基础
Shell支持一维数组和关联数组两种类型。
3.2.1 一维数组
数组定义与初始化
bash
# 方式1:直接定义
arr1=(one two three four)
# 方式2:按索引定义
arr2[0]="zero"
arr2[1]="one"
arr2[2]="two"
# 方式3:混合定义
arr3=(apple [5]="banana" cherry)
# 索引:0->apple, 5->banana, 6->cherry
# 方式4:从命令输出创建数组
files=($(ls *.sh 2>/dev/null)) # 当前目录的所有.sh文件
# 方式5:使用 declare 创建
declare -a fruits=("apple" "banana" "cherry")
数组基本操作
bash
# 定义数组
fruits=("apple" "banana" "cherry" "date")
# 获取数组长度
echo ${#fruits[@]} # 输出:4
echo ${#fruits[*]} # 输出:4
# 获取某个元素的长度
echo ${#fruits[0]} # 输出:5(apple的长度)
# 访问数组元素
echo ${fruits[0]} # 输出:apple
echo ${fruits[2]} # 输出:cherry
# 访问所有元素
echo ${fruits[@]} # 输出:apple banana cherry date
echo ${fruits[*]} # 输出:apple banana cherry date
# 遍历数组(for循环)
echo "=== 遍历数组 ==="
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# 遍历数组索引
echo "=== 遍历索引 ==="
for i in "${!fruits[@]}"; do
echo "Index $i: ${fruits[$i]}"
done
# 数组切片
echo ${fruits[@]:1:2} # 输出:banana cherry(从索引1开始,取2个元素)
# 数组追加元素
fruits+=(elderberry)
echo ${fruits[@]} # 输出:apple banana cherry date elderberry
# 数组拼接
more_fruits=("fig" "grape")
combined=("${fruits[@]}" "${more_fruits[@]}")
echo ${combined[@]} # 输出:apple banana cherry date elderberry fig grape
数组的高级操作
bash
# 1. 删除数组元素
arr=(one two three four)
unset arr[1] # 删除索引1的元素
echo ${arr[@]} # 输出:one three four
# 2. 删除整个数组
unset arr
# 3. 数组去重
arr=(1 2 3 2 1 4 3 5)
unique=($(echo "${arr[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
echo ${unique[@]} # 输出:1 2 3 4 5
# 4. 数组反转
arr=(1 2 3 4 5)
reversed=($(echo "${arr[@]}" | tr ' ' '\n' | tac | tr '\n' ' '))
echo ${reversed[@]} # 输出:5 4 3 2 1
# 5. 数组元素排序
arr=(banana apple cherry)
sorted=($(echo "${arr[@]}" | tr ' ' '\n' | sort | tr '\n' ' '))
echo ${sorted[@]} # 输出:apple banana cherry
# 6. 数组元素随机打乱
arr=(1 2 3 4 5)
shuffled=($(echo "${arr[@]}" | tr ' ' '\n' | shuf | tr '\n' ' '))
echo ${shuffled[@]} # 随机顺序输出
# 7. 查找元素索引
arr=(apple banana cherry)
for i in "${!arr[@]}"; do
if [[ "${arr[$i]}" == "banana" ]]; then
echo "banana的索引: $i"
fi
done
3.2.2 关联数组(Bash 4.0+)
关联数组使用字符串作为索引,类似其他语言中的字典或哈希表。
关联数组定义与初始化
bash
# 定义关联数组(必须先声明)
declare -A capitals
# 方式1:逐个赋值
capitals["China"]="Beijing"
capitals["Japan"]="Tokyo"
capitals["USA"]="Washington"
# 方式2:一次性赋值
declare -A countries=(
[China]="Beijing"
[Japan]="Tokyo"
[USA]="Washington"
[France]="Paris"
)
# 方式3:使用 += 追加
capitals+=([UK]="London")
关联数组基本操作
bash
declare -A capitals=(
[China]="Beijing"
[Japan]="Tokyo"
[USA]="Washington"
[France]="Paris"
)
# 获取所有索引(键)
echo ${!capitals[@]} # 输出:China Japan USA France
# 获取所有值
echo ${capitals[@]} # 输出:Beijing Tokyo Washington Paris
# 获取数组长度
echo ${#capitals[@]} # 输出:4
# 访问单个元素
echo ${capitals[China]} # 输出:Beijing
# 遍历关联数组
echo "=== 遍历关联数组 ==="
for country in "${!capitals[@]}"; do
echo "$country 的首都是 ${capitals[$country]}"
done
# 判断键是否存在
if [[ -v capitals[China] ]]; then
echo "China 存在于数组中"
fi
关联数组的高级操作
bash
# 1. 关联数组的增删改查
declare -A user_info
# 添加/更新
user_info[name]="张三"
user_info[age]="25"
user_info[city]="北京"
# 查找
if [[ -v user_info[name] ]]; then
echo "姓名: ${user_info[name]}"
fi
# 删除
unset user_info[age]
# 遍历(按键排序)
echo "=== 按键排序遍历 ==="
for key in $(echo "${!user_info[@]}" | tr ' ' '\n' | sort); do
echo "$key: ${user_info[$key]}"
done
# 2. 关联数组的大小
echo "数组大小: ${#user_info[@]}"
# 3. 关联数组的键值对数量
keys=("${!user_info[@]}")
echo "键值对数量: ${#keys[@]}"
3.3 算术运算
Shell本身不支持直接的数学运算,需要借助外部命令或特殊语法。
3.3.1 使用 expr 命令
expr 是Shell中最传统的算术运算方式:
bash
# 基本运算
expr 10 + 5 # 输出:15(注意:+号两侧需要空格)
expr 10 - 5 # 输出:5
expr 10 \* 5 # 输出:50(*号需要转义)
expr 10 / 5 # 输出:2
expr 10 % 5 # 输出:0
# 变量运算
a=10
b=5
expr $a + $b # 输出:15
expr $a - $b # 输出:5
expr $a \* $b # 输出:50
expr $a / $b # 输出:2
expr $a % $b # 输出:0
# 自增自减
i=1
i=$(expr $i + 1) # i变为2
echo $i
# 字符串操作(expr的额外功能)
expr index "hello" "l" # 输出:3(查找字符l首次出现的位置)
expr substr "hello" 2 3 # 输出:ell(从位置2开始截取3个字符)
expr length "hello" # 输出:5
# 注意事项:expr 会启动新进程,效率较低
3.3.2 使用 $(()) 语法(推荐)
$(( )) 是现代Shell中推荐使用的算术运算方式,效率更高:
bash
# 基本运算
echo $((10 + 5)) # 输出:15
echo $((10 - 5)) # 输出:5
echo $((10 * 5)) # 输出:50
echo $((10 / 5)) # 输出:2
echo $((10 % 5)) # 输出:0
# 变量运算
a=10
b=5
echo $((a + b)) # 输出:15
echo $((a - b)) # 输出:5
echo $((a * b)) # 输出:50
echo $((a / b)) # 输出:2
echo $((a % b)) # 输出:0
# 复合运算
result=$((a + b * 2))
echo $result # 输出:20
# 自增自减
i=1
echo $((i++)) # 输出:1(先返回值,再自增)
echo $i # 输出:2
echo $((++i)) # 输出:3(先自增,再返回值)
echo $i # 输出:3
j=5
echo $((j--)) # 输出:5(先返回值,再自减)
echo $j # 输出:4
echo $((--j)) # 输出:3(先自减,再返回值)
echo $j # 输出:3
# 简写形式
a=10
a=$((a + 5)) # a=15
a=$((a * 2)) # a=30
# 位运算
echo $((5 & 3)) # 输出:1(按位与)
echo $((5 | 3)) # 输出:7(按位或)
echo $((5 ^ 3)) # 输出:6(按位异或)
echo $((5 << 1)) # 输出:10(左移1位)
echo $((5 >> 1)) # 输出:2(右移1位)
echo $((~5)) # 输出:-6(按位取反)
# 三元运算(条件表达式)
a=10
b=20
max=$((a > b ? a : b))
echo $max # 输出:20
# 进制转换
echo $((16#FF)) # 输出:255(十六进制转十进制)
echo $((8#77)) # 输出:63(八进制转十进制)
echo $((2#1010)) # 输出:10(二进制转十进制)
# 十进制转其他进制(使用printf)
printf '%x\n' 255 # 输出:ff
printf '%o\n' 63 # 输出:77
printf '%b\n' 10 # 输出:1010
3.3.3 使用 let 命令
let 用于执行算术运算,常用于变量赋值:
bash
# 基本运算
let a=10+5
echo $a # 输出:15
let b=10-5
echo $b # 输出:5
let c=10*5
echo $c # 输出:50
# 变量运算
x=10
let y=5
let z=x+y
echo $z # 输出:15
# 自增自减
i=1
let i++
echo $i # 输出:2
let ++i
echo $i # 输出:3
# 复合赋值
let a=10
let a+=5 # a=a+5 = 15
let a*=2 # a=a*2 = 30
echo $a # 输出:30
# 注意事项:let 中变量名不需要加 $
let "a = 10 + 5"
echo $a # 输出:15
# 多个表达式
let "x = 10" "y = 20" "z = x + y"
echo "x=$x, y=$y, z=$z" # 输出:x=10, y=20, z=30
3.3.4 使用 bc 命令(高精度计算)
bc 是用于高精度计算的计算器,支持浮点数和数学函数:
bash
# 基本浮点运算
echo "10.5 + 5.2" | bc # 输出:15.7
echo "10.5 - 5.2" | bc # 输出:5.3
echo "10.5 * 5.2" | bc # 输出:54.60
echo "10.5 / 5.2" | bc # 输出:2
echo "scale=2; 10.5 / 5.2" | bc # 输出:2.01(设置小数位数)
# 使用变量
a=10.5
b=5.2
echo "$a + $b" | bc # 输出:15.7
# 高级数学运算
echo "scale=10; sqrt(2)" | bc # 输出:1.4142135623
echo "scale=5; 2^10" | bc # 输出:1024.00000
# 三角函数(需要 -l 选项)
echo "s(1)" | bc -l # 输出:0.8414709848(sin(1)弧度)
echo "c(1)" | bc -l # 输出:0.5403023058(cos(1)弧度)
echo "a(1)" | bc -l # 输出:0.7853981634(atan(1)弧度)
# 指数和对数
echo "e(2)" | bc -l # 输出:7.3890560989(e^2)
echo "l(10)" | bc -l # 输出:2.3025850930(ln(10))
# 条件判断(bc返回0或1)
echo "10 > 5" | bc # 输出:1
echo "10 == 10" | bc # 输出:1
echo "3.14 > 3" | bc # 输出:1
# 复杂计算示例:计算圆的面积
radius=5
area=$(echo "scale=5; 3.14159 * $radius * $radius" | bc)
echo "半径为 $radius 的圆面积: $area" # 输出:78.53975
# 批量计算(使用EOF)
result=$(bc <<< "scale=2
area = 3.14159 * 5 * 5
area")
echo $result # 输出:78.54
3.3.5 使用 awk 进行数学运算
awk 也是进行数学计算的好工具,特别适合处理列数据:
bash
# 基本运算
awk 'BEGIN {print 10 + 5}' # 输出:15
awk 'BEGIN {print 10 - 5}' # 输出:5
awk 'BEGIN {print 10 * 5}' # 输出:50
awk 'BEGIN {print 10 / 5}' # 输出:2
# 浮点运算(awk默认支持)
awk 'BEGIN {print 10.5 + 5.2}' # 输出:15.7
# 复杂表达式
awk 'BEGIN {print sqrt(16)}' # 输出:4
awk 'BEGIN {print exp(1)}' # 输出:2.71828
awk 'BEGIN {print log(10)}' # 输出:2.30259
# 处理文件列数据
# 文件 data.txt 内容:
# 100 200
# 150 250
# 200 300
awk '{sum1+=$1; sum2+=$2} END {print sum1, sum2}' data.txt
# 输出:450 750
# 计算平均值
awk '{sum+=$1} END {print sum/NR}' data.txt
# 输出:150(平均值)
3.3.6 算术运算对比
| 方法 | 整数运算 | 浮点运算 | 特点 |
|---|---|---|---|
expr |
✅ | ❌ | 传统方式,需转义*号,启动新进程 |
$(( )) |
✅ | ❌ | 推荐方式,效率高,语法简洁 |
let |
✅ | ❌ | 适合变量赋值,自增自减 |
bc |
✅ | ✅ | 支持高精度和浮点,可调用数学函数 |
awk |
✅ | ✅ | 适合处理列数据,浮点计算方便 |
3.4 综合示例
下面是一个综合运用本章知识点的示例脚本:
bash
#!/bin/bash
# ============ 字符串处理示例 ============
echo "=== 字符串处理示例 ==="
str="HelloShellWorld"
# 截取
echo "原字符串: $str"
echo "截取前5个字符: ${str:0:5}"
echo "截取后5个字符: ${str: -5}"
# 替换
echo "替换Hello为Hi: ${str/Hello/Hi}"
# 长度
echo "字符串长度: ${#str}"
# 大小写转换
echo "转为大写: ${str^^}"
echo "转为小写: ${str,,}"
# ============ 数组示例 ============
echo -e "\n=== 数组示例 ==="
# 定义数组
fruits=("apple" "banana" "cherry" "date")
echo "数组元素: ${fruits[@]}"
echo "数组长度: ${#fruits[@]}"
# 遍历数组
echo "遍历数组:"
for fruit in "${fruits[@]}"; do
echo " - $fruit"
done
# 数组切片
echo "切片[1:2]: ${fruits[@]:1:2}"
# ============ 关联数组示例 ============
echo -e "\n=== 关联数组示例 ==="
declare -A capitals=(
[China]="Beijing"
[Japan]="Tokyo"
[USA]="Washington"
)
echo "遍历关联数组:"
for country in "${!capitals[@]}"; do
echo " $country -> ${capitals[$country]}"
done
# ============ 算术运算示例 ============
echo -e "\n=== 算术运算示例 ==="
# 整数运算
a=10
b=3
echo "a=$a, b=$b"
echo "a + b = $((a + b))"
echo "a - b = $((a - b))"
echo "a * b = $((a * b))"
echo "a / b = $((a / b))"
echo "a % b = $((a % b))"
# 自增运算
echo "a++ = $((a++)), 现在 a=$a"
echo "++a = $((++a)), 现在 a=$a"
# 位运算
echo "5 & 3 = $((5 & 3))"
echo "5 | 3 = $((5 | 3))"
echo "5 ^ 3 = $((5 ^ 3))"
# 浮点运算(使用bc)
echo "浮点运算:"
x=10.5
y=2.5
result=$(echo "scale=2; $x * $y" | bc)
echo "$x * $y = $result"
# 计算圆的周长和面积
radius=5
pi=3.14159
circumference=$(echo "scale=4; 2 * $pi * $radius" | bc)
area=$(echo "scale=4; $pi * $radius * $radius" | bc)
echo "半径为 $radius 的圆:"
echo " 周长: $circumference"
echo " 面积: $area"
# ============ 实用函数示例 ============
echo -e "\n=== 实用函数示例 ==="
# 字符串去空格
trim() {
local var="$*"
echo "${var#"${var%%[![:space:]]*}"}"
echo "${var%"${var##*[![:space:]]}"}"
}
echo "去空格测试:"
trim " Hello World "
# 数组最大值
array_max() {
local arr=("$@")
local max=${arr[0]}
for item in "${arr[@]}"; do
((item > max)) && max=$item
done
echo $max
}
echo "数组最大值: $(array_max 5 2 8 1 9 3)"
# 数组求和
array_sum() {
local arr=("$@")
local sum=0
for item in "${arr[@]}"; do
sum=$((sum + item))
done
echo $sum
}
echo "数组求和: $(array_sum 1 2 3 4 5 6 7 8 9 10)"
3.5 本章小结
本章详细介绍了Shell脚本中的数据类型与运算操作:
-
字符串处理:
- 拼接:直接相邻放置或使用双引号
- 截取:
${str#}、${str%}、${str:start:len} - 替换:
${str/pattern/replacement}、${str//pattern/replacement} - 长度:
${#str} - 大小写转换:
${str,,}、${str^^} - 高级操作:去空白、模式匹配替换
-
数组操作:
- 一维数组:定义、遍历、切片、追加、删除、去重、反转
- 关联数组:使用字符串索引,适合键值对存储
- 数组高级操作:排序、随机打乱、查找索引
-
算术运算:
expr:传统方式,支持整数和字符串操作$(( )):推荐方式,语法简洁,支持位运算、三元运算、进制转换let:适合变量赋值和自增自减bc:支持高精度浮点运算和数学函数awk:适合处理列数据和浮点计算
掌握这些数据处理技能后,你将能够编写功能更强大的Shell脚本,为后续的文本处理和脚本开发打下坚实基础。
更多内容,欢迎访问南徽玉的个人博客