当前位置: 首页 > news >正文

shell编程从0基础--进阶 1

第一部分:Shell基础概念

什么是Shell?

Shell是操作系统的命令解释器,它连接用户和操作系统内核。简单说,就是你输入命令,它帮你执行。

常见的Shell类型

  • Bash:最常用,Linux默认
  • Zsh:功能更强大
  • Sh:最基础的Shell

一、脚本基础结构命令

1. 脚本声明(Shebang)

命令格式:

#!/bin/bash
#!/bin/bash -x
# 上面这行同时启用了调试模式(-x选项)
echo "这行会显示执行过程"#!/usr/bin/env bash
# 这种写法更灵活,能适应不同系统的bash位置
echo "适用于多种Linux发行版"#!/bin/sh
# 使用POSIX兼容模式(更严格,确保跨平台兼容性)
# 注意:某些bash特性不可用

说明:

  • 必须是脚本第一行
  • 指定解释器路径,告诉系统用什么程序执行此脚本
  • 常见解释器:/bin/bash, /bin/sh, /usr/bin/env bash

2. 注释规范

完整脚本示例:

#!/bin/bash# 这是一个注释echo "Hello from my first script!"
# 单行注释: '
多行注释
可以包含任意内容
'<<COMMENT
另一种多行注释方式 (这种有时会报错)
COMMENT
#字符串赋值
name="zz-zjx"
greeting="Hello,$name"#数字赋值
<<222 count=10
max_rerties=3
dsd
#
ds
222
echo $greeting#222 可以换成任意配对字符

3. 严格模式设置

命令格式:

set -euo pipefail

说明:

  • -e:命令失败时立即退出脚本
  • -u:引用未定义变量时报错
  • -o pipefail:管道中任一命令失败则整个管道失败

脚本示例:

#!/bin/bashset -euo pipefail# 如果前面的命令失败,脚本会在这里停止cp source.txt destination.txtecho "文件复制成功!"

二、变量操作命令

1. 变量定义与赋值

命令格式:

# 基本赋值
变量名=值# 命令替换赋值
变量名=$(命令)
变量名=`命令`  # 旧式语法,不推荐嵌套使用# 算术赋值
let "变量名=表达式"
(( 变量名=表达式 ))# 声明特定类型变量
declare [-选项] 变量名=值
typeset [-选项] 变量名=值  # 与declare等效

关键规则:

  • 等号两边不能有空格
  • 值可以是字符串、数字或命令输出
  • 变量名区分大小写
  • declare/typeset选项:

  • 常用选项详解
    选项含义示例说明
    -a声明为普通数组(索引数组)declare -a fruits=("apple" "banana")支持通过数字索引访问元素(如 ${fruits[0]}
    -A声明为关联数组(键值对)declare -A user=( ["name"]="Alice" ["age"]=25 )支持通过字符串键访问元素(如 ${user["name"]}
    -i声明为整数型变量declare -i count=10强制变量只能存储整数,赋值时会自动转换(如 count="20abc" 会被转为 20
    -r声明为只读变量declare -r PI=3.14变量不可修改或删除(类似 readonly
    -x声明为环境变量declare -x PATH="/usr/local/bin:$PATH"等效于 export,变量对子进程可见
    -p显示变量的属性和值declare -p count输出变量的类型、值和属性(如 declare -i count="10"
    -f显示函数定义declare -f my_function列出已定义的函数及其代码
    -F仅显示函数名declare -F不显示函数体,仅列出函数名称
    -g在函数中声明全局变量bar() { declare -g global_var=100; }在函数内部声明的变量为全局作用域
    +取消属性declare +i count移除变量的 -i 属性(恢复为字符串类型)
declare 与直接赋值的区别
方式示例特点
直接赋值var="hello"简单赋值,变量默认为字符串类型
declaredeclare -i var=10可设置变量类型(如整数)、只读属性、数组类型等
对比var=10 vs declare -i var=10直接赋值的 var 是字符串,declare -i 的 var 是整数

脚本示例:

#!/bin/bash# 字符串赋值
name="zz-zjx"
age=33
is_student=false
greeting="Hello, $name"# 数字赋值
count=10
max_retries=3# 命令替换赋值
current_date=$(date +"%Y-%m-%d")
ip_address=$(hostname -I)# 算术赋值
let "x = 5 + 3 * 2"
(( y = x ** 2 ))  # 幂运算
(( z = 10 % 3 ))  # 取模# 声明特定类型变量
declare -i counter=0  # 整数,自动进行算术运算
counter="10 + 5"      # 会计算为15,而不是字符串
echo "counter: $counter"  # 输出15declare -r PI=3.14159  # 只读变量,不可修改
# PI=3.14  # 这行会报错declare -l lower_case="HELLO"  # 自动转为小写
echo "小写: $lower_case"  # 输出hellodeclare -u upper_case="world"  # 自动转为大写
echo "大写: $upper_case"  # 输出WORLD# 数组声明
declare -a fruits=("苹果" "香蕉" "橙子")  # 索引数组
declare -A person=([name]="张三" [age]=30 [city]="北京")  # 关联数组# 导出变量(成为环境变量)
declare -x API_KEY="secret_key_123"echo "$greeting"
echo "当前日期: $current_date"
echo "IP地址: $ip_address"
echo "数字相乘": $(($count*$max_retries))

2. 变量引用与参数扩展(高级技巧)

命令格式:

${变量名[修饰符]}

参数扩展修饰符:

${var}

基本引用

echo ${name}

${var=default}

变量为空时使用默认值

echo ${user=root}

${var:=default}

变量未设置或为空时赋值并使用

echo ${path:=/usr/bin}

${var:-default}

变量为空时使用默认值(不赋值)

echo ${name:-匿名}

${var:+value}

变量设置且非空时使用value

echo ${name:+已设置}

${var:?message}

变量为空时显示错误并退出

echo ${file:?必须指定文件}

${var#pattern}

从开头删除最短匹配

echo ${path#*/}

${var##pattern}

从开头删除最长匹配

echo ${path##*/}

${var%pattern}

从结尾删除最短匹配

echo ${file%.txt}

${var%%pattern}

从结尾删除最长匹配

echo ${file%%.*}

${var:offset}

从offset开始的子字符串

echo ${text:5}

${var:offset:length}

指定长度的子字符串

echo ${text:5:10}

${var/pattern/replacement}

替换第一个匹配

echo ${text/hello/hi}

${var//pattern/replacement}

替换所有匹配

echo ${text// /_}

${#var}

变量长度

echo ${#name}

${!prefix*}

匹配前缀的所有变量名

echo ${!USER*}

${!name[@]}

数组所有索引

echo ${!fruits[@]}

详细示例:

#!/bin/bash# 基本变量
filename="document.txt"
path="/home/user/documents/report.pdf"
text="Hello World, welcome to Shell scripting!"# 默认值
user="${USERNAME:-匿名用户}"
echo "用户: $user"# 必需变量检查
#: ${CONFIG_FILE?"错误:必须设置CONFIG_FILE环境变量"}# 字符串截取
echo "文件名: ${filename##*/}"       # 输出: document.txt                                                               echo "扩展名: ${filename%.*}"        # 输出: document                                                                   echo "目录: ${path%/*}"              # 输出: /home/user/documents                                                       echo "目录: ${path%%/*}"              # 输出:                                                                           echo "基本名: ${path##*/}"           # 输出: report.pdf                                                                 echo "基本名: ${path#*/}"           # 输出:  home/user/documents/report.pdf# 子字符串
echo "从第6个字符开始: ${text:6}"    # 输出: World, welcome to Shell scripting!
echo "5个字符: ${text:6:5}"          # 输出: World# 字符串替换
echo "替换第一个空格: ${text/ /_}"    # 输出: Hello_World, welcome to Shell scripting!
echo "替换所有空格: ${text// /_}"     # 输出: Hello_World,_welcome_to_Shell_scripting!# 大小写转换
echo "大写: ${text^^}"               # 输出: HELLO WORLD, WELCOME TO SHELL SCRIPTING!
echo "小写: ${text,,}"               # 输出: hello world, welcome to shell scripting!
echo "首字母大写: ${text^}"          # 输出: Hello World, welcome to Shell scripting!
echo "首字母小写: ${text,}"          # 输出: hello World, welcome to Shell scripting!# 数组索引
fruits=("苹果" "香蕉" "橙子")
echo "所有索引: ${!fruits[@]}"       # 输出: 0 1 2
echo "数组长度: ${#fruits[@]}"       # 输出: 3# 关联数组
declare -A person=([name]="zz-zjx" [age]=30)
echo "所有键: ${!person[@]}"         # 输出: name age
echo "值的数量: ${#person[@]}"       # 输出: 2

三、条件判断(深度详解)

1. test / [ ] 命令(全面参数)

命令格式:

test 表达式
# 或
[ 表达式 ]

文件测试操作符:

-a file

文件存在(已过时,用-e代替)

-b file

文件存在且为块设备

-c file

文件存在且为字符设备

-d file

文件存在且为目录

-e file

文件存在

-f file

文件存在且为普通文件

-g file

文件存在且设置了组ID位

-h file

文件存在且为符号链接(-L更标准)

-k file

文件存在且设置了"sticky bit"

-p file

文件存在且为命名管道(FIFO)

-r file

文件存在且可读

-s file

文件存在且大小不为零

-t fd

文件描述符fd已打开并关联到终端

-u file

文件存在且设置了setuid位

-w file

文件存在且可写

-x file

文件存在且可执行

-O file

文件存在且属于当前用户

-G file

文件存在且属于当前用户组

-L file

文件存在且为符号链接

-S file

文件存在且为套接字

-N file

文件存在且自上次读取后已修改

字符串测试操作符:

-z str

字符串长度为零

-n str

字符串长度不为零

str1 = str2

字符串相等

str1 == str2

字符串相等(同上,部分shell扩展)(2个等号)

str1 != str2

字符串不相等

str1 < str2

按字典顺序str1在str2前

str1 > str2

按字典顺序str1在str2后

数值测试操作符:

arg1 -eq arg2

等于

arg1 -ne arg2

不等于

arg1 -lt arg2

小于

arg1 -le arg2

小于等于

arg1 -gt arg2

大于

arg1 -ge arg2

大于等于

组合测试:

! expr

逻辑非

expr1 -a expr2

逻辑与(已过时,用&&代替)

expr1 -o expr2

逻辑或(已过时,用 ||代替)

( expr )

将expr作为子表达式

详细脚本示例:

#!/bin/bash# 文件测试
file="/etc/passwd"
if [ -f "$file" ] && [ -r "$file" ]; thenecho "$file 是可读的普通文件"if [ -s "$file" ]; thenecho "$file 大小不为零"fiif [ -O "$file" ]; thenecho "$file 属于当前用户"fi
fi# 更复杂的文件测试
if [ -d "/var/log" ] && [ ! -w "/var/log" ]; thenecho "/var/log 是目录但不可写"
fi# 字符串测试
name="张三"
if [ -z "$name" ]; thenecho "名字为空"
elif [ "$name" = "张三" ]; thenecho "你好,张三!"# 字典顺序比较if [ "$name" \< "李四" ]; thenecho "张三在字典顺序上位于李四之前"fi
fi# 数值测试
age=25
if [ $age -ge 18 ] && [ $age -lt 65 ]; thenecho "你是工作年龄"
elif [ $age -ge 65 ]; thenecho "你是退休年龄"
elseecho "你是未成年人"
fi# 组合测试
if [ -f "config.txt" ] && { [ -r "config.txt" ] || [ -w "config.txt" ] ; }; thenecho "config.txt 是可读或可写的文件"
fi# 测试文件修改时间
if [ file1 -nt file2 ]; thenecho "file1 比 file2 新"
elif [ file1 -ot file2 ]; thenecho "file1 比 file2 旧"
fi

2. [[ ]] 增强型条件测试(Bash特有)

[[ 表达式 ]]

扩展特性:

==

模式匹配(支持通配符)

[[ $name == J* ]]

=~

正则表达式匹配

[[ $email =~ ^[a-z]+@[a-z]+\.[a-z]+$ ]]

&&

逻辑与

[[ $a -gt 0 && $a -lt 10 ]]

||

逻辑或[[ $a -gt 0 || $a -lt 10 ]]

!

逻辑非

[[ ! -f "$file" ]]

(...)

分组

[[ ( $a -gt 0 ) && ( $b -lt 10 ) ]]

Shell 中通过 [ ](即 test 命令)支持三种文件时间比较:

操作符含义英文全称
-ntnewer than:比……更新(修改时间更晚)newer than
-otolder than:比……更旧(修改时间更早)older than
-efequal file:两个文件指向同一个 inode(硬链接)equal file

详细脚本示例:

#!/bin/bash                                                                                                                                                                                                                                     # 文件测试                                                                                                              file="/etc/passwd"                                                                                                      if [ -f "$file" ] && [ -r "$file" ]; then                                                                                   echo "$file 是可读的普通文件"                                                                                                                                                                                                                   if [ -s "$file" ]; then                                                                                                     echo "$file 大小不为零"                                                                                             fi                                                                                                                                                                                                                                              if [ -O "$file" ]; then                                                                                                     echo "$file 属于当前用户"                                                                                           fi                                                                                                                  fi                                                                                                                                                                                                                                              # 更复杂的文件测试                                                                                                      if [ -d "/var/log" ] && [ ! -w "/var/log" ]; then                                                                           echo "/var/log 是目录但不可写"                                                                                      fi# 字符串测试
name="张三"
if [ -z "$name" ]; thenecho "名字为空"
elif [ "$name" = "张三" ]; thenecho "你好,张三!"# 字典顺序比较 if [ "$name" \< "李四" ]; thenecho "张三在字典顺序上位于李四之前"fi
fi# 数值测试
age1=25if [ $age1 -ge 18 ] && [ $age1 -lt 65 ]; thenecho "你是工作年龄"
elif [ $age1 -ge 65 ]; thenecho "你是退休年龄"
elseecho "你是未成年人"
fi# 组合测试
if [[ -f "config.txt" && ( -r "config.txt" || -w "config.txt" ) ]]; thenecho "config.txt 是可读或可写的文件"
fi# 测试文件修改时间
if [ config.txt -nt config_new.txt ]; thenecho "file1 比 file2 新"
elif [ config.txt -ot config_new.txt ]; thenecho "file1 比 file2 旧"
fi    

3. case 语句(高级用法)

命令格式:

case 变量 in模式1 | 模式2)# 匹配模式1或模式2时执行;;模式*)# 通配符匹配;;*)# 默认情况;;
esac

模式匹配规则:

  • |:表示"或"关系
  • *:匹配任意字符(包括空)
  • ?:匹配单个字符
  • [...]:匹配括号内的任意一个字符
  • [a-z]:匹配a到z之间的任意一个字符
  • !(pattern):不匹配指定模式(需要开启extglob)
  • @(pattern):匹配指定模式之一(需要开启extglob)
  • *(pattern):匹配零个或多个指定模式(需要开启extglob)
  • +(pattern):匹配一个或多个指定模式(需要开启extglob)
  • ?(pattern):匹配零个或一个指定模式(需要开启extglob)
#!/bin/bash                                                                                                                                                                                                                                                                                                                                                                                                                       # 基本case语句                                                                                                                                                                                                   read -p "请选择操作 (start/stop/restart/status): " action                                                                                                                                                                                                                                                                                                                                                                         case $action in                                                                                                                                                                                                      start | begin)                                                                                                                                                                                                       echo "正在启动服务..."                                                                                                                                                                                           ;;                                                                                                                                                                                                           stop | end)                                                                                                                                                                                                          echo "正在停止服务..."                                                                                                                                                                                           ;;                                                                                                                                                                                                           restart | reload)                                                                                                                                                                                                    echo "正在重启服务..."                                                                                                                                                                                           ;;                                                                                                                                                                                                           status | info)                                                                                                                                                                                                       echo "正在检查服务状态..."                                                                                                                                                                                       ;;                                                                                                                                                                                                           *)                                                                                                                                                                                                                   echo "错误:未知操作 '$action'" >&2                                                                                                                                                                              echo "可用操作: start, stop, restart, status" >&2                                                                                                                                                                exit 1                                                                                                                                                                                                           ;;                                                                                                                                                                                                       esac                                                                                                                                                                                                                                                                                                                                                                                                                              # 通配符匹配                                                                                                                                                                                                     read -p "请输入文件名: " filename                                                                                                                                                                                                                                                                                                                                                                                                 case $filename in                                                                                                                                                                                                    *.txt)                                                                                                                                                                                                               echo "这是一个文本文件"                                                                                                                                                                                          ;;                                                                                                                                                                                                           *.jpg | *.png | *.gif)                                                                                                                                                                                               echo "这是一个图片文件"                                                                                                                                                                                          ;;                                                                                                                                                                                                           *.tar | *.tar.gz | *.tgz | *.zip)                                                                                                                                                                                    echo "这是一个压缩文件"                                                                                                                                                                                          ;;                                                                                                                                                                                                           Makefile | makefile)                                                                                                                                                                                                 echo "这是一个Makefile"                                                                                                                                                                                          ;;*)echo "未知文件类型";;
esac# 高级模式匹配(需要开启extglob)
shopt -s extglobread -p "请输入数字: " number  
case $number in+([0-9]))  # 匹配一个或多个数字echo "这是一个正整数: $number";;-+([0-9])) # 匹配负整数echo "这是一个负整数: $number";;+([0-9]).+([0-9])) # 匹配浮点数echo "这是一个浮点数: $number";;*)echo "这不是一个有效的数字";;
esac# 复杂模式匹配
read -p "请输入命令: " commandcase $command in"git commit*" | "git push*" | "git pull*")echo "这是一个git操作";;"docker run*" | "docker start*" | "docker stop*")echo "这是一个docker操作";;"[sS]udo *")echo "这是一个需要sudo权限的操作";;*)echo "普通命令";;
esac                       
对比:1>&2 vs 2>&1
对比项1>&22>&1
方向stdout → stderrstderr → stdout
目的让“正常输出”变成“错误输出”让“错误输出”变成“正常输出”
常见场景脚本中输出错误提示合并日志、管道处理
示例echo "error" 1>&2cmd > log 2>&1
口诀“1 进 2”“2 进 1”
其他常见重定向组合
写法含义示例
> file1>file 的简写,stdout 写入文件ls > list.txt
2> filestderr 写入文件(覆盖)cmd 2> error.log
2>> filestderr 写入文件(追加)cmd 2>> error.log
&> fileBash 特有:等价于 >file 2>&1,合并 stdout 和 stderrcmd &> log.txt
>/dev/null丢弃 stdoutcmd > /dev/null
2>/dev/null丢弃 stderrcmd 2>/dev/null
&>/dev/null丢弃所有输出(stdout + stderr)cmd &>/dev/null

写法含义使用场景
1>&2stdout → stderr脚本中输出错误信息
2>&1stderr → stdout合并日志、管道处理
&>filestdout + stderr → file简化合并重定向(Bash)
>/dev/null丢弃 stdout静默执行
2>/dev/null丢弃 stderr忽略错误
case 的优势 vs if-elif
特性caseif-elif
可读性✅ 高(清晰的分支)❌ 多个 elif 易混乱
模式匹配✅ 支持 *?、`` 等
性能✅ 通常更快❌ 多次调用 [ ]
灵活性❌ 仅字符串匹配✅ 可做数值、文件判断等

💡 推荐:当你要对一个变量做多种字符串模式判断时,优先使用 case

四、数组(深度详解)

# 索引数组
declare -a array_name=(元素1 元素2 ...)
array_name[索引]=值# 关联数组(Bash 4.0+)
declare -A array_name=([键1]=值1 [键2]=值2 ...)
array_name[键]=值# 数组操作
${array[@]}      # 所有元素
${!array[@]}     # 所有索引(关联数组为键)
${#array[@]}     # 数组长度
${array[索引]}    # 特定元素
${array[@]:offset} # 从offset开始的子数组
${array[@]:offset:length} # 指定长度的子数组

详细脚本示例:

#!/bin/bash# 索引数组定义
fruits=("苹果" "香蕉" "橙子" "葡萄")
declare -a vegetables=("胡萝卜" "西兰花" "土豆")
#大型脚本一般用下面这种 数组定义,写法不同而已# 关联数组定义(Bash 4.0+)
declare -A capitals=([China]="北京" [USA]="华盛顿" [Japan]="东京")
declare -A user_info=([name]="张三" [age]=30 [email]="zhangsan@example.com")
# 或者分开写 declare -A capitals       capitals["China"]="Beijing"
# 显示数组信息
echo "水果数组:"
echo "  全部元素: ${fruits[@]}"  # 输出   全部元素: 苹果 香蕉 橙子 葡萄
echo "  元素数量/数组长度: ${#fruits[@]}"
echo "  索引: ${!fruits[@]}" # ${!arr[@]}:获取所有索引(编号) 普通数组:0 1 2 3 关联数组 :China USA Japan
echo "  第二个元素: ${fruits[1]}"  # 索引从0开始
echo "  最后一个元素: ${fruits[-1]}"echo -e "\n首都关联数组:"
echo "  全部键: ${!capitals[@]}" # 输出(key) China USA Japan
echo "  键的数量: ${#capitals[@]}" #输出3
echo "  中国的首都: ${capitals[China]}" # 输出 北京
echo "  所有值: ${capitals[@]}" #输出 (value) 北京 华盛顿 东京# 数组操作
echo -e "\n数组操作:"
# 修改元素
fruits[2]="橘子"
echo "修改后的水果: ${fruits[@]}" # 数组是 0开始 0 1 2 所以对应实际第3个# 添加元素
fruits+=("西瓜") #注意千万不要写成 declare -a fruits+=("西瓜")  这表示重新赋值而不是追加
vegetables+=("番茄" "黄瓜")
echo "添加后的水果: ${fruits[@]}"
echo "添加后的蔬菜: ${vegetables[@]}"# 删除元素
unset fruits[1]  # 删除香蕉
echo "删除后的水果: ${fruits[@]}"
echo "删除后的索引: ${!fruits[@]}"# 子数组
echo "子数组 (索引1-2): ${fruits[@]:1:2}" # 如果没有删除和改写就是香蕉橙子 ,加上以上就是 橘子 葡萄# 数组遍历
echo -e "\n遍历水果数组:"
for fruit in "${fruits[@]}"; doecho "  - $fruit"
doneecho -e "\n带索引遍历水果数组:"
for i in "${!fruits[@]}"; doecho "  [$i] ${fruits[$i]}"
doneecho -e "\n遍历首都关联数组:"
for country in "${!capitals[@]}"; doecho "  $country: ${capitals[$country]}"
done
: ' 遍历水果数组:- 苹果- 橘子- 葡萄- 西瓜带索引遍历水果数组:[0] 苹果[2] 橘子[3] 葡萄[4] 西瓜遍历首都关联数组:Japan: 东京China: 北京USA: 华盛顿
'# 数组排序
echo -e "\n排序数组:"
sorted_fruits=($(printf '%s\n' "${fruits[@]}" | sort))
echo "  按字母排序: ${sorted_fruits[@]}" #输出 按字母排序: 橘子 苹果 葡萄 西瓜# 数组去重
echo -e "\n数组去重:"
duplicates=("a" "b" "a" "c" "b")
declare -A temp
for item in "${duplicates[@]}"; dotemp["$item"]=1
done
unique=("${!temp[@]}") #普通数组定义
echo "  原始: ${duplicates[@]}"
echo "  去重: ${unique[@]}"
: ' temp["$item"]=1
将 item 的值作为 键(key) 存入 temp 数组,值设为 1(任意值都行,这里只是占位)。
因为关联数组的键是唯一的,所以重复的值不会被重复添加。
🔍 举个例子:第一次 item="a" → temp["a"]=1
第二次 item="b" → temp["b"]=1
第三次 item="a" → temp["a"]=1(已存在,覆盖,但不影响“唯一性”)
第四次 item="c" → temp["c"]=1
第五次 item="b" → temp["b"]=1(已存在)
最终,temp 数组的键就是:a, b, c —— 自动去重!'# 数组合并
echo -e "\n数组合并:"
combined=("${fruits[@]}" "${vegetables[@]}")
echo "  合并结果: ${combined[@]}"
# 输出 苹果 橘子 葡萄 西瓜 胡萝卜 西兰花 土豆 番茄 黄瓜# 数组转字符串
echo -e "\n数组转字符串:"
joined=$(IFS=,; echo "${fruits[*]}")
echo "  逗号分隔: $joined"
: 'IFS=,
IFS:Internal Field Separator(内部字段分隔符),Bash 用来决定如何“连接”或“分割”字符串。
默认 IFS 包含空格、制表符、换行符。
这里临时设置 IFS=,,表示“用逗号作为分隔符”。
⚠️ IFS=, 只在当前命令中生效(因为写在 ; 前面),不会影响后续代码。✅ ;
分号,表示命令分隔。
✅ echo "${fruits[*]}"
"${fruits[*]}":把数组所有元素合并成 一个字符串。
Bash 会自动使用当前 IFS 的值作为分隔符来连接元素。
📌 关键区别:"${fruits[@]}":保持元素分离(用于遍历)
"${fruits[*]}":合并成一个字符串(用于连接)
✅ joined=$( ... )
使用 $() 捕获命令输出,把结果赋值给变量 joined。
# 字符串转数组
echo -e "\n字符串转数组:"
IFS=, read -r -a split_array <<< "red,green,blue"
echo "  分割结果: ${split_array[@]}"
输出   逗号分隔: 苹果,橘子,葡萄,西瓜
'# 多维数组模拟
echo -e "\n模拟多维数组:"
declare -A matrix
matrix["0,0"]=1
matrix["0,1"]=2
matrix["1,0"]=3
matrix["1,1"]=4echo "  矩阵元素:"
echo "    [0,0]: ${matrix["0,0"]}"
echo "    [0,1]: ${matrix["0,1"]}"
echo "    [1,0]: ${matrix["1,0"]}"
echo "    [1,1]: ${matrix["1,1"]}"
: ' 输出 模拟多维数组:矩阵元素:[0,0]: 1[0,1]: 2[1,0]: 3[1,1]: 4
或者
# 声明关联数组
declare -A config# 赋值
config["prod,db"]="192.168.1.100"
config["dev,web"]="localhost"# 输出
echo "配置信息:"
echo "  生产数据库: ${config["prod,db"]}"
echo "  开发Web服务: ${config["dev,web"]}"echo "全部配置:"
for key in "${!config[@]}"; doecho "  $key = ${config[$key]}"
done输出:配置信息:生产数据库: 192.168.1.100开发Web服务: localhost
全部配置:prod,db = 192.168.1.100dev,web = localhost
'
# 数组作为函数参数
process_array() {local -n arr_ref=$1  # 使用nameref(Bash 4.3+)echo "  处理数组: ${arr_ref[@]}"# 修改原始数组arr_ref[0]="修改后的值"
}echo -e "\n数组作为函数参数:"
echo "  原始数组: ${fruits[@]}"
process_array fruits
echo "  修改后: ${fruits[@]}"
: '原始数组: 苹果 橘子 葡萄 西瓜处理数组: 苹果 橘子 葡萄 西瓜修改后: 修改后的值 橘子 葡萄 西瓜
'
# 旧版Bash的数组传递方法
process_array_old() {# 通过eval处理eval "local temp=(\"\${$1[@]}\")"echo "  处理数组: ${temp[@]}"# 无法直接修改原始数组
}# 数组序列化与反序列化
serialize_array() {local -n arr=$1local IFS="|"echo "${arr[*]}"
}deserialize_array() {local serialized=$1IFS="|" read -r -a "$2" <<< "$serialized"
}echo -e "\n数组序列化:"
serialized=$(serialize_array fruits)
echo "  序列化: $serialized"
declare -a deserialized
deserialize_array "$serialized" deserialized
echo "  反序列化: ${deserialized[@]}"
: '
数组序列化:序列化: 修改后的值|橘子|葡萄|西瓜反序列化: 修改后的值 橘子 葡萄 西瓜1. local serialized=$1
$1 是传进来的序列化字符串,比如 "苹果|香蕉|橙子"
2. IFS="|" read -r -a "$2" <<< "$serialized"
这是一行非常关键的命令,拆解:✅ IFS="|"
临时设置分隔符为 |,用于分割字符串。
✅ read
Bash 内置命令,用于读取输入。
✅ -r
禁用反斜杠转义(安全选项,建议总是加)。
✅ -a "$2"
-a:表示读入数组
"$2":第二个参数,是要存入的数组名(比如 deserialized)
注意:是 "$2"(带引号),因为它是变量名
✅ <<< "$serialized"
Here String:把 $serialized 字符串作为 read 的输入
'

五、循环控制(深度详解)

1. for 循环(全面用法)

命令格式:

# 列表形式
for 变量 in 列表; do# 循环体
done# C语言风格
for ((初始化; 条件; 步进)); do# 循环体
done# 读取命令输出
for 变量 in $(命令); do# 循环体
done# 读取管道输出
命令 | while IFS= read -r 变量; do# 循环体
done

详细脚本示例:

#!/bin/bash# 基本列表循环
echo "基本列表循环:"
for color in 红色 绿色 蓝色
doecho "  - $color"                                                                                                                                                                                            done                                                                                                                                                                                                                                                                                                                                                                                                                              # 文件通配循环                                                                                                                                                                                                   echo -e "\n处理所有txt文件:"                                                                                                                                                                                     for file in *.txt                                                                                                                                                                                                do                                                                                                                                                                                                                   if [ -f "$file" ]; then                                                                                                                                                                                              echo "  $file (大小: $(wc -c < "$file") 字节)"                                                                                                                                                               fi                                                                                                                                                                                                           done                                                                                                                                                                                                                                                                                                                                                                                                                              # 范围循环                                                                                                                                                                                                       echo -e "\n数字范围循环:"                                                                                                                                                                                        for i in {1..5}                                                                                                                                                                                                  do                                                                                                                                                                                                                   echo "  $i"                                                                                                                                                                                                  done                                                                                                                                                                                                                                                                                                                                                                                                                              # 带步长的范围                                                                                                                                                                                                   echo -e "\n带步长的范围:"                                                                                                                                                                                        for i in {1..10..2}                                                                                                                                                                                              do  # 从1到10,步长2                                                                                                                                                                                                 echo "  $i"                                                                                                                                                                                                  done                                                                                                                                                                                                                                                                                                                                                                                                                              # C语言风格循环                                                                                                                                                                                                  echo -e "\nC语言风格循环:"                                                                                                                                                                                       for ((i=0, j=10; i<10; i++, j--))                                                                                                                                                                                do                                                                                                                                                                                                                   echo "  i=$i, j=$j"                                                                                                                                                                                          done                                                                                                                                                                                                                                                                                                                                                                                                                              # 处理命令输出                                                                                                                                                                                                   echo -e "\n处理命令输出:"
for user in $(cut -d: -f1 /etc/passwd | head -n 5)
doecho "  用户: $user"
done# 读取文件行(正确处理空格和特殊字符)
echo -e "\n安全读取文件行:"
while IFS= read -r line
do   echo "  $line"
done < <(head -n 3 /etc/passwd)                                                                                                                                                                                                                                                                                                                                                                                                   # 处理数组                                                                                                                                                                                                       fruits=("苹果" "香蕉" "橙子" "葡萄")                                                                                                                                                                             echo -e "\n处理数组:"                                                                                                                                                                                            for ((i=0; i<${#fruits[@]}; i++))
doecho "  索引 $i: ${fruits[$i]}"
done# 处理关联数组
declare -A capitals=([China]="北京" [USA]="华盛顿" [Japan]="东京")
echo -e "\n处理关联数组:"
for country in "${!capitals[@]}"
doecho "  $country 的首都是 ${capitals[$country]}"
done# 多变量循环
<<注释 echo -e "\n多变量循环:"
for i in {1..3}; j in {a..c}; doecho "  $i - $j"
done 2>/dev/null || echo "  注意:Bash不支持多变量列表循环,上面的示例会出错"
注释# 正确的多变量处理方法
echo -e "\n正确的多变量处理:"
countries=("China" "USA" "Japan")
capitals=("北京" "华盛顿" "东京")
for ((i=0; i<${#countries[@]}; i++))
doecho "  ${countries[$i]} - ${capitals[$i]}"
done

2. while / until 循环(高级用法)

命令格式:

# while循环
while [ 条件 ]; do# 条件为真时执行
done# until循环
until [ 条件 ]; do# 条件为假时执行
done# 读取文件的标准方式
while IFS= read -r line; do# 处理每一行
done < 文件# 从命令输出读取
命令 | while IFS= read -r line; do# 处理每一行
done# 处理多个文件描述符
exec 3< file1 4< file2
while IFS= read -r -u 3 line1 && IFS= read -r -u 4 line2; do# 同时处理两个文件
done

详细脚本示例:

#!/bin/bash# 简单while循环
echo "简单while循环:"
count=1
while [ $count -le 5 ]; doecho "  $count"((count++))
done# 简单until循环
echo -e "\n简单until循环:"
count=1
until [ $count -gt 5 ]; doecho "  $count"((count++))
done# 读取文件(安全方式,保留空格和特殊字符)
echo -e "\n安全读取文件(保留空格):"
while IFS= read -r line; doecho "  $line"
done < <(echo -e "第一行\n 第二行  \n第三行")# 读取文件(带行号)
echo -e "\n带行号读取文件:"
line_num=1
while IFS= read -r line; doprintf "  %3d: %s\n" $line_num "$line"((line_num++))
done < <(head -n 5 /etc/passwd)# 从命令输出读取
echo -e "\n从命令输出读取:"
ps aux | while IFS= read -r -a fields; doif [ "${fields[0]}" = "$(whoami)" ]; thenecho "  $(printf "%-10s %6s %s" "${fields[0]}" "${fields[2]}" "${fields[10]}")"fi
done# 处理多个文件描述符
echo -e "\n同时处理两个文件:"
exec 3< <(echo -e "A\nB\nC") 4< <(echo -e "1\n2\n3")
while IFS= read -r -u 3 line1 && IFS= read -r -u 4 line2; doecho "  $line1 - $line2"
done
exec 3<&- 4<&-  # 关闭文件描述符# 无限循环与用户交互
echo -e "\n用户交互循环 (输入'exit'退出):"
while true; doread -rp "> " inputcase $input inexit|quit)break;;help)echo "  可用命令: help, echo [文本], exit";;echo*)# 提取echo后的文本text="${input#echo }"echo "  $text";;*)echo "  未知命令: $input";;esac
done# 处理超时
echo -e "\n带超时的循环:"
start_time=$(date +%s)
timeout=5  # 5秒超时while true; docurrent_time=$(date +%s)elapsed=$((current_time - start_time))if [ $elapsed -ge $timeout ]; thenecho "  超时 ($timeout秒)"breakfiecho "  运行中... ($elapsed/$timeout秒)"sleep 1
done# 从here文档读取
echo -e "\n从here文档读取:"
while IFS= read -r line; doecho "  $line"
done <<EOF
这是here文档的第一行
这是第二行
包含特殊字符: \$ & * |
EOF

3. 循环控制命令(高级技巧)

命令格式:

break [n]     # 退出循环(n表示退出n层循环)
continue [n]  # 跳过当前迭代,继续下一次循环
return [n]    # 从函数返回(n为返回状态)
exit [n]      # 退出脚本(n为退出状态)
#!/bin/bash# 多层循环中的break
echo "多层循环中的break:"
for i in {1..3}; doecho "外层循环 $i:"for j in {A..C}; dofor k in {x,y,z}; doif [ "$k" = "y" ]; thenecho "  跳出两层循环 (i=$i, j=$j, k=$k)"break 2  # 跳出两层循环fiecho "    ($i, $j, $k)"donedone
done# 多层循环中的continue
echo -e "\n多层循环中的continue:"
for i in {1..3}; doecho "外层循环 $i:"for j in {A..C}; dofor k in {x,y,z,p}; doif [ "$k" = "y" ]; thenecho "  跳过内层当前迭代 (i=$i, j=$j, k=$k)"continue 2  # 跳过两层循环的当前迭代fiecho "    ($i, $j, $k)"donedone
done
: '
continue 2 不是“跳过 y 继续 z 和 p”,而是“看到 y 就把整个 j=A 这一轮直接作废”,所以 z 和 p 还没来得及出场,舞台就被关灯了!
continue 3 的意思是:“看到 k=y,就立刻放弃当前 i 的所有工作,直接进入下一个 i”。所以每个 i 只能完成 j=A, k=x,然后就被 k=y 触发跳过,z, p, j=B 全部不会执行。多层循环中的continue:
外层循环 1:(1, A, x)跳过内层当前迭代 (i=1, j=A, k=y)(1, B, x)跳过内层当前迭代 (i=1, j=B, k=y)(1, C, x)跳过内层当前迭代 (i=1, j=C, k=y)
外层循环 2:(2, A, x)跳过内层当前迭代 (i=2, j=A, k=y)(2, B, x)跳过内层当前迭代 (i=2, j=B, k=y)(2, C, x)跳过内层当前迭代 (i=2, j=C, k=y)
外层循环 3:(3, A, x)跳过内层当前迭代 (i=3, j=A, k=y)(3, B, x)跳过内层当前迭代 (i=3, j=B, k=y)(3, C, x)跳过内层当前迭代 (i=3, j=C, k=y)带状态返回的循环:
处理 item1...成功: item1
处理 item2...成功: item2
处理 item3...成功: item3
处理 item4...成功: item4
所有项目处理成功
'
# 带状态返回的循环
echo -e "\n带状态返回的循环:"
process_items() {local success_count=0local error_count=0for item in "$@"; doecho "处理 $item..."if (( RANDOM % 2 == 0 )); thenecho "  成功: $item"((success_count++))elseecho "  失败: $item" >&2((error_count++))fidoneif [ $error_count -eq 0 ]; thenreturn 0  # 全部成功elif [ $error_count -lt $success_count ]; thenreturn 1  # 部分成功elsereturn 2  # 大部分失败fi
}# 调用并检查状态process_items item1 item2 item3 item4
result=$?if [ $result -eq 0 ]; thenecho "所有项目处理成功"
elif [ $result -eq 1 ]; thenecho "部分项目处理成功"
elseecho "大部分项目处理失败"
fi# 退出脚本的不同状态
echo -e "\n脚本退出状态:"
check_prerequisites() {# 检查必要条件if ! command -v curl &> /dev/null; thenecho "错误:缺少curl命令" >&2exit 127  # 命令未找到的标准退出码fiif [ ! -w /tmp ]; thenecho "错误:/tmp目录不可写" >&2exit 2  # 权限错误fi
}check_prerequisites
echo "所有先决条件满足,继续执行..."


文章转载自:

http://1CHodRFn.qcfgd.cn
http://tHRKJhxx.qcfgd.cn
http://lFBxhDoB.qcfgd.cn
http://MNEwg5xU.qcfgd.cn
http://eVqT9dCq.qcfgd.cn
http://Hyk545Rq.qcfgd.cn
http://ZuAPrC5t.qcfgd.cn
http://Gi1trj4i.qcfgd.cn
http://w5zOT7KF.qcfgd.cn
http://VnHEwtQ0.qcfgd.cn
http://VedFtHmI.qcfgd.cn
http://US6Qq8BD.qcfgd.cn
http://AYKHjj9O.qcfgd.cn
http://eJXspjpX.qcfgd.cn
http://skzaNzcv.qcfgd.cn
http://65WlFWkb.qcfgd.cn
http://lwRSklOp.qcfgd.cn
http://PMyyYuzW.qcfgd.cn
http://O9Geu1oV.qcfgd.cn
http://pN36Zq10.qcfgd.cn
http://9SsU0mqY.qcfgd.cn
http://Q5u7aqqh.qcfgd.cn
http://Lgep4x5D.qcfgd.cn
http://g548Ch7S.qcfgd.cn
http://BOfT1tcd.qcfgd.cn
http://O6Tnic0o.qcfgd.cn
http://pT5Upqtw.qcfgd.cn
http://LPWHgLhj.qcfgd.cn
http://ruQ8xoRh.qcfgd.cn
http://KJhW71Ke.qcfgd.cn
http://www.dtcms.com/a/365962.html

相关文章:

  • 如何高效记单词之:抓住首字母——以find、fund、fond、font为例
  • Linux `epoll` 机制的入口——`epoll_create`函数
  • Java并发编程中的CountDownLatch与CompletableFuture:同步与异步的完美搭档
  • 驱动增长的双引擎:付费搜索与自然搜索的终极平衡策略
  • Loot模板系统
  • helm应该安装在哪些节点
  • ABAQUS多尺度纤维增强混凝土二维建模
  • 微信小程序-day3
  • 【mac】macOS上的实用Log用法
  • 使用Navicat去批量传输数据库的表结构
  • fastlio配置与过程中遇到的问题
  • 51单片机----LED与数码管模块
  • C 语言标准输入输出库:`stdio.h` 的使用详解
  • 【WPS】WPSPPT 快速抠背景
  • Python学习笔记--使用Django修改和删除数据
  • 52.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--解决客户端调用接口404问题
  • 硬件:51单片机的按键、中断、定时器、PWM及蜂鸣器
  • Spring Boot HTTP状态码详解
  • 性能测试-jmeter8-脚本录制
  • 揭秘23种设计模式的艺术与技巧
  • < 自用文 主机 USC 记录:> 发现正在被攻击 后的自救
  • Protocol Buffers:数据世界的秘语之书,手把手教学环境搭建
  • mysql高级进阶(存储过程)
  • 认识HTML
  • CDN的工作原理是什么?为什么要用高防 CDN?
  • 数据结构:双向链表
  • 分割回文串手绘图
  • 电脑城老板不会告诉你的装机秘籍:建造者模式让你的代码高配起飞!
  • @Autowired原理(三)
  • 【Qt中信号槽连接connect有接收者和无接收者的区别】