Shell编程从入门到精通-第三章 数据类型与运算

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脚本,为后续的文本处理和脚本开发打下坚实基础。

更多内容,欢迎访问南徽玉的个人博客

相关推荐
Nanhuiyu7 小时前
Shell编程从入门到精通-第五章 流程控制
shell编程
Nanhuiyu8 小时前
Shell编程从入门到精通-第七章 文本三剑客
shell编程
Nanhuiyu11 小时前
Shell编程从入门到精通-第六章 函数
shell编程
Nanhuiyu2 天前
Shell编程从入门到精通-第四章 条件判断
shell编程
Nanhuiyu2 天前
Shell编程从入门到精通-第一章 Shell简介与环境配置
shell编程
予枫的编程笔记12 天前
【Linux进阶篇】从基础到实战:grep高亮、sed流编辑、awk分析,全场景覆盖
linux·sed·grep·awk·shell编程·文本处理三剑客·管道命令
vortex513 天前
解密UUOC:Shell编程中“无用的cat使用”详解
linux·shell编程
燃于AC之乐18 天前
【Linux系统编程】Shell解释器完全实现:从命令解析、环境变量管理到内建命令的全面解析
linux·操作系统·命令行工具·进程控制·shell编程
PyHaVolask1 个月前
Linux零基础入门:输入输出重定向完全指南
linux运维·shell编程·输入输出流·linux重定向·命令行技巧