十一,Shell

198 阅读7分钟

一,shell介绍

Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序。

用户可以用Shell来启动,挂起,停止,编写一些程序。

image

二,Shell脚本执行方式

2.1 格式要求

  1. 脚本以 #!/bin/bash 开头
  2. 脚本需要有可执行权限
  3. 一般shell脚本文件以 .sh 为后缀
  4. 在shell中用 # 表示注释
  5. 多行注释为 起始处 :<<! 结尾处 !

创建的shell脚本文件即使是root权限也不会给x执行权限,需要自己增加

2.2 常用执行方式

运行方式

  • 直接执行(./script.sh
  • 通过解释器运行(sh script.sh)。

三,变量

在 Shell 脚本中,变量是存储数据的核心工具,可分为 环境变量局部变量特殊变量数组变量 等类型。

3.1 变量分类与作用域

类型作用域生命周期定义方式
局部变量当前 Shell 进程脚本或函数执行期间var=value
环境变量当前及子进程直到 Shell 会话结束export var=valuevar=value; export var
特殊变量全局脚本执行期间预定义(如 $0, $#, $? 等)
只读变量不可修改定义后不可更改readonly var=value
数组变量当前 Shell 进程脚本或函数执行期间array=(val1 val2)

3.2 变量的定义与使用

基本语法

  • 赋值变量名=值等号两侧不能有空格

  • 引用$变量名${变量名}(推荐后者,明确变量边界)

  • 删除unset 变量名

  • 示例

    name="Alice"         # 定义局部变量
    echo "Hello, $name"  # 输出:Hello, Alice
    echo "Hello, ${name}" # 更安全的写法
    unset name           # 删除变量
    

环境变量

  • 作用:传递到子进程(如脚本调用的其他程序)。

  • 定义方式

    export PATH="/usr/bin:$PATH"  # 直接导出
    # 或先赋值再导出
    JAVA_HOME="/opt/java"
    export JAVA_HOME
    

只读变量

  • 特性:定义后不可修改或删除。

    readonly PI=3.14
    PI=3.1415  # 报错:PI: readonly variable
    

特殊变量

  • Shell 预定义的特殊变量,用于获取脚本运行时的上下文信息:

    变量含义
    $0当前脚本的文件名
    $1, $2, ...脚本的参数($1 是第一个参数,以此类推),但是如果是10就需要${10}
    $#传递给脚本的参数个数
    $@所有参数列表(每个参数独立,如 "$@" 会保留参数中的空格)
    $*所有参数合并为一个字符串(如 "$*" 相当于 "$1 $2 ..."
    $?上一条命令的退出状态(0 表示成功,非 0 表示失败)
    $$当前 Shell 进程的 PID
    $!最后一个后台运行进程的 PID
  • 示例

    #!/bin/bash
    echo "脚本名:$0"         # 输出脚本文件名
    echo "第一个参数:$1"     # 输出传入的第一个参数
    echo "参数个数:$#"       # 输出参数总数
    echo "所有参数:$@"       # 输出参数列表(推荐使用)
    echo "上一条命令状态:$?" # 检查命令是否成功
    

数组变量

  1. 定义与访问

    # 定义数组
    fruits=("apple" "banana" "cherry")  # 索引数组
    declare -A user=([name]="Alice" [age]=30)  # 一个Map数组
    
    # 访问元素
    echo ${fruits[0]}    # 输出:apple(索引从0开始)
    echo ${user["name"]} # 输出:Alice
    
    #获取所有元素
    echo ${fruits[@]}    # 输出所有元素
    echo ${!user[@]}     # 输出所有键(关联数组)
    
  2. 修改数组

    fruits[1]="blueberry"  # 修改第二个元素
    fruits+=("orange")     # 追加元素
    unset fruits[2]        # 删除第三个元素
    

3.3 变量替换与字符串操作

变量替换:

语法说明
${var:-default}var 未定义,返回 default
${var:=default}var 未定义,赋值为 default 并返回
${var:+replacement}var 已定义,返回 replacement
${var:?error_msg}var 未定义,输出错误信息并终止脚本

示例:

echo ${name:-"Guest"}  # 若name未定义,输出Guest

字符串操作

语法说明
${#string}获取字符串长度
${string:start:length}截取子字符串(从 start 开始,长度 length
${string#pattern}删除最短匹配前缀
${string##pattern}删除最长匹配前缀
${string%pattern}删除最短匹配后缀
${string%%pattern}删除最长匹配后缀
${string/old/new}替换第一个匹配的 oldnew
${string//old/new}替换所有匹配的 oldnew

示例

path="/home/user/file.txt"
echo ${path#*/}      # 输出:home/user/file.txt(删除第一个/及之前内容)
echo ${path##*/}     # 输出:file.txt(删除最后一个/及之前内容)
echo ${path%.*}      # 输出:/home/user/file(删除最后一个.及之后内容)
echo ${path/file/doc} # 输出:/home/user/doc.txt

3.4 变量的引号使用

引号类型作用
无引号变量值中的空格会触发单词分割,通配符(*)会被扩展
双引号保留变量值中的空格和特殊字符($, \, ! 等),变量会被替换
单引号完全原样输出,变量和特殊字符不会被替换

示例

name="Alice Bob"
echo $name        # 输出:Alice Bob(正确,但若包含特殊字符可能出错)
echo "$name"      # 推荐写法,避免空格分割
echo '$name'      # 输出:$name(变量不会被替换)

3.5 注意事项

  1. 命名规则
    • 变量名由字母、数字、下划线组成,不能以数字开头。
    • 避免使用 Shell 关键字(如 if, then)。
  2. 空格问题
    • 赋值时等号两侧不能有空格:var=value(正确),var = value(错误)。
  3. 引号必要性
    • 处理含空格的字符串时,必须用双引号包裹变量:"$var"
  4. 作用域陷阱
    • 函数内未使用 local 定义的变量会污染全局作用域。

四,运算符

4.1 算术运算符

用于数值计算(需用 $(( ))expr)。

运算符说明示例
+加法echo $((5 + 3)) → 输出 8
-减法echo $((10 - 2)) → 输出 8
*乘法echo $((4 * 2)) → 输出 8
/除法echo $((16 / 2)) → 输出 8
%取模(取余)echo $((9 % 2)) → 输出 1
**幂运算echo $((2 ** 3)) → 输出 8

示例脚本

a=10
b=3
sum=$((a + b)) # sum=$(expr $a + $b)  ,不建议用expr因为expr中*需要用\*进行转义,而且变量间需要空格
echo "和为:$sum"  # 输出:和为:13

4.2 比较运算符

用于数值或字符串的条件判断(需在 [ ][[ ]] 中使用)。

运算符说明示例
-eq等于[ $a -eq $b ]
-ne不等于[ $a -ne $b ]
-gt大于[ $a -gt $b ]
-lt小于[ $a -lt $b ]
-ge大于等于[ $a -ge $b ]
-le小于等于[ $a -le $b ]

示例脚本

if [ $a -gt 5 ]; then
  echo "a 大于 5"
fi

字符串比较

运算符说明示例
=等于[ "$str1" = "$str2" ]
!=不等于[ "$str1" != "$str2" ]
-z空字符串[ -z "$str" ]
-n非空字符串[ -n "$str" ]

示例脚本

str1="hello"
str2="world"
if [ "$str1" != "$str2" ]; then
  echo "字符串不同"
fi

4.3 逻辑运算符

用于组合多个条件。

运算符说明示例
&&逻辑与[ 条件1 ] && [ 条件2 ]
``逻辑或`[ 条件1 ][ 条件2 ]`
!逻辑非[ ! 条件 ]

示例

if [ $a -gt 5 ] && [ $a -lt 15 ]; then
  echo "a 在 5 和 15 之间"
fi

4.4 字符串操作符

专用于字符串操作。

运算符说明示例
${#s}获取字符串长度echo ${#str} → 输出长度
${s:pos}截取子字符串(从位置 pos 开始)echo ${str:2} → 从第3字符开始
${s:pos:len}截取子字符串(从 pos 开始,长度 len)echo ${str:1:3} → 截取3字符

示例

str="abcdef"
echo "${str:2}"      # 输出:cdef
echo "${str:1:3}"    # 输出:bcd

4.5 文件测试运算符

用于检查文件/目录属性。

运算符说明示例
-e文件/目录是否存在[ -e "/path/file" ]
-f是否为普通文件[ -f "/path/file" ]
-d是否为目录[ -d "/path/dir" ]
-r是否可读[ -r "/path/file" ]
-w是否可写[ -w "/path/file" ]
-x是否可执行[ -x "/path/file" ]
-s文件大小是否大于0字节[ -s "/path/file" ]

示例

file="/tmp/test.txt"
if [ -f "$file" ]; then
  echo "$file 是普通文件"
fi

4.6 赋值运算符

用于变量赋值和运算简化。

运算符说明示例(假设初始值 a=5
=赋值a=10 → a 变为 10
+=追加字符串str="hi"; str+=" there" → "hi there"
+=数值累加((a += 3)) → a 变为 8

示例

num=5
((num *= 2))  # num 变为 10
echo $num

五,条件判断

5.1 单分支

if [ 判断条件 ]
then
	条件为true执行的语句
fi

5.2 双分支

if [ 判断条件 ]
then
	条件为true执行的语句
else
	条件为flase执行的语句
fi

简写:

[ 判断条件 ] && 条件为true执行语句

注意点:

  1. 判断条件前后要有空格
  2. if 和条件之间需要空格
  3. [ ]返回false(有空格!)
  4. [ wwadwa ]返回true
  5. 0为true ,>1 为false

5.3 多分支

if [ 条件判断 ]
then
	代码
elif [ 条件判断 ]
then
	代码
...
fi

5.4 流程控制(case)

基本语法:

case $变量名 in
"值1")
	代码1
;;
"值2")
	代码2
;;
.....
*)
	最终情况
;;
esac

5.5 常用判断条件

在前面加上 ! 即为否定,比如 ! -f 文件不存在

字符串之间比较

if [ 字符串1 = 字符串2 ]

两个整数的比较

if [ 1 -lt 2 ]
符号描述
-lt小于
-le小于等于
-eq等于
-gt大于
-ge大于等于
-ne不等于

按照文件权限判断

if [ 选项 文件路径 ]
符号描述
-r有读的权限
-w有写的权限
-x有执行的权限

按照文件类型进行判断

if [ 符号 文件路径 ]
符号描述
-f存在文件
-e存在路径
-d存在目录

示例:

if [ $1 -gt 100 ]; 
then
  echo "参数大于100"
elif [ -f "/path/to/file" ]; 
then
  echo "文件存在"
else
  echo "其他情况"
fi

六,循环

6.1 for循环

基本语法:遍历列表或范围

for 变量 in 列表; 
do
  命令
done

典型应用

  • 遍历数字范围

    for i in {1..5}; 
    do
      echo "数字:$i"
    done
    # 输出:数字1到5
    
  • 遍历文件/目录

    for file in *.txt; 
    do
      echo "处理文件:$file"
    done
    
  • 遍历数组

    fruits=("apple" "banana" "cherry")
    for fruit in "${fruits[@]}"; 
    do
      echo "水果:$fruit"
    done
    
  • C 风格循环(仅限 Bash):

    for ((i=0; i<5; i++));
    do
      echo "计数器:$i"
    done
    

6.2 while循环

基本语法:

while [ 条件 ]; 
do
  命令
done

典型应用

  • 读取文件内容

    while read line; 
    do
      echo "行内容:$line"
    done < file.txt
    
  • 计数器控制

    count=0
    while [ $count -lt 5 ]; 
    do
      echo "计数:$count"
      ((count++))
    done
    
  • 无限循环

    while true; 
    do
      echo "按 Ctrl+C 终止"
      sleep 1
    done
    

6.3 until 循环

基本语法:条件为假时持续执行(与 while 逻辑相反):

until [ 条件 ]; 
do
  命令
done

典型应用

  • 等待条件满足

    until ping -c1 example.com &>/dev/null; 
    do
      echo "等待网络连接..."
      sleep 1
    done
    echo "连接成功!"
    

    &>/dev/null

    • 作用:将命令的 所有输出(包括标准输出和错误输出)重定向到黑洞(即不显示任何信息)。
      • >:重定向。
      • /dev/null:Linux 中的虚拟设备,丢弃所有写入的数据。
      • &>:同时重定向标准输出(stdout)和错误输出(stderr)。
    • 效果:让命令静默执行,不在终端显示结果。

6.4 循环控制语句

关键字作用示例
break立即退出循环if [条件]; then break; fi
continue跳过当前迭代,进入下一次循环if [条件]; then continue; fi
exit终止整个脚本if [错误]; then exit 1; fi

示例

for i in {1..10}; 
do
  if [ $i -eq 5 ]; 
  then
    break     # 当i=5时退出循环
  fi
  echo "当前值:$i"
done
# 输出:1 2 3 4

七,读取控制台输入内容

基本语法

read (选项) (参数) 接受值得变量

选项

  • -p 指定读取值时的提示信息
  • -t 指定读取值的等待时间,如果未在规定时间输入就不等待

举例

read -p "请输入一个值num=" NUM

其中NUM1是接受从控制台输入值的变量

-p 后面的内容是从控制台等待读取值的时候的提示信息

这个相当于scanf("请输入一个值num=%d",$NUM1)

read -t 10 NUM2

等待10s如果10后没有获取到数值就停止等待

结合起来写法

read -p 参数 -t 参数 接受值的变量

八,函数

在 Shell 脚本中,函数(Function)是将一组命令封装为可重复调用的代码块,用于提高代码复用性和逻辑模块化。以下是 Shell 函数的完整指南:


8.1 函数定义与调用

定义语法

# 方式1:标准定义
function_name() {
  命令
}

# 方式2(仅限 Bash):使用 `function` 关键字
function function_name {
  命令
}

调用函数

直接通过函数名调用,无需括号

function_name

示例

# 定义函数
say_hello() {
  echo "Hello, $1!"
}

# 调用函数并传参
say_hello "Alice"  # 输出:Hello, Alice!

8.2 函数参数

传递参数

函数内通过 $1, $2, ..., $n 接收参数:

sum() {
  result=$(( $1 + $2 ))
  echo "和为:$result"
}

sum 5 3  # 输出:和为:8

特殊参数

变量含义
$#函数接收的参数个数
$@所有参数的列表(数组)
$*所有参数合并为字符串

示例

show_args() {
  echo "参数个数:$#"
  echo "参数列表:$@"
}

show_args "A" "B" "C"
# 输出:
# 参数个数:3
# 参数列表:A B C

8.3 返回值与退出状态

return 语句

  • 函数默认返回最后一条命令的退出状态码(0 表示成功,非 0 表示失败)。
  • 使用 return 显式返回状态码(范围:0~255)。
check_file() {
  if [ -f "$1" ]; then
    return 0  # 文件存在 → 成功
  else
    return 1  # 文件不存在 → 失败
  fi
}

check_file "test.txt"
echo $?  # 输出返回值(0 或 1)

返回数据

  • 通过变量传递:使用全局变量存储结果。
  • 通过 echo 输出:结合命令替换($(...))捕获输出。
# 方式1:全局变量
result=""
calculate() {
  result=$(( $1 * 2 ))
}
calculate 5
echo $result  # 输出:10

# 方式2:echo 输出
double() {
  echo $(( $1 * 2 ))
}
value=$(double 5)
echo $value  # 输出:10

8.4 变量作用域

  • 默认全局变量:函数内外定义的变量均为全局变量。
  • 局部变量:使用 local 关键字定义仅在函数内有效的变量。
demo_scope() {
  local local_var="内部变量"
  global_var="全局变量"
}

demo_scope
echo $global_var  # 输出:全局变量
echo $local_var   # 输出:空(变量不存在)

8.5 递归函数

函数可以调用自身,但需注意终止条件以避免无限递归。

factorial() {
  if [ $1 -le 1 ]; then
    echo 1
  else
    local prev=$(factorial $(( $1 - 1 )))
    echo $(( $1 * prev ))
  fi
}

echo "5的阶乘:$(factorial 5)"  # 输出:120

8.6 函数库与导入

将常用函数封装为独立文件(如 utils.sh),通过 source 导入使用:

# utils.sh
log() {
  echo "[$(date)] $1"
}

# main.sh
source utils.sh
log "任务开始"  # 输出带时间戳的日志

8.7 实用技巧

函数别名

使用 alias 为常用函数创建快捷命令:

# 定义函数
docker_clean() {
  docker system prune -af
}

# 设置别名(可选)
alias dclean="docker_clean"

调试函数

  • 显示执行过程

    set -x  # 开启调试
    my_function
    set +x  # 关闭调试
    
  • 逐行调试

    bash -x script.sh
    

错误处理

使用 trap 捕获错误信号:

handle_error() {
  echo "错误发生在第 $LINENO 行"
  exit 1
}

trap 'handle_error' ERR

8.8 实际应用示例

文件备份函数

backup_file() {
  local src="$1"
  local dest="${src}.bak_$(date +%Y%m%d)"
  if cp "$src" "$dest"; then
    echo "备份成功:$dest"
  else
    echo "备份失败!" >&2
    return 1
  fi
}

backup_file "data.txt"

用户输入验证

get_input() {
  read -p "请输入用户名(长度≥3):" username
  if [ ${#username} -lt 3 ]; then
    echo "用户名太短!" >&2
    return 1
  fi
  echo "$username"
}

valid_name=$(get_input) || exit 1

8.9 注意事项

  1. 避免函数名冲突:函数名不要与系统命令重复。
  2. 参数校验:在函数内检查参数合法性(如 if [ $# -lt 1 ]; then ...)。
  3. 性能优化:避免在循环中频繁调用复杂函数。