Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的Shell编程详细知识点(含案例代码)(17)

Ubuntu 22.04中的Shell编程详细知识点(含案例代码)

一、Shell编程基础认知

1. 什么是Shell

Shell是Linux系统的命令解释器,是用户与内核之间的交互桥梁。Ubuntu 22.04默认使用bash(Bourne Again Shell),是最常用的Shell解释器。

2. 环境准备(Ubuntu 22.04)

Ubuntu 22.04默认已预装bash,无需额外安装。若需验证或安装:

bash 复制代码
# 验证bash是否安装
bash --version

# 若未安装,执行以下命令安装
sudo apt update && sudo apt install -y bash

3. 脚本编辑器选择

推荐使用vim(功能强大)或nano(简单易用),安装步骤:

bash 复制代码
# 安装vim
sudo apt install -y vim

# 安装nano(Ubuntu默认已预装,若缺失则执行)
sudo apt install -y nano

二、Shell编程基本步骤

1. 编写Shell脚本

核心要求:脚本第一行必须指定解释器(#!/bin/bash),称为"shebang"。

编写步骤:
  1. 打开编辑器创建脚本文件(后缀建议为.sh,便于识别)
  2. 写入脚本内容(命令+逻辑)
  3. 保存文件
案例:编写简单的问候脚本
bash 复制代码
# 步骤1:创建脚本文件
vim hello.sh

# 步骤2:写入以下内容(带注释)
#!/bin/bash
# 这是一个简单的Shell脚本,功能:输出问候语和系统信息
echo "====================================="
echo "欢迎使用Ubuntu 22.04 Shell编程演示"
echo "当前系统时间:$(date)"  # 命令替换,获取当前时间
echo "当前登录用户:$(whoami)"
echo "====================================="

# 步骤3:保存退出(vim中按Esc,输入:wq回车)

2. 执行Shell脚本

Shell脚本有4种执行方式,核心区别是是否需要脚本文件有执行权限、是否创建子Shell。

方式1:赋予执行权限后直接执行(推荐)
bash 复制代码
# 步骤1:给脚本添加执行权限(chmod +x 文件名)
chmod +x hello.sh

# 步骤2:执行脚本(当前目录下需加./,否则系统会在PATH中查找)
./hello.sh
方式2:通过bash解释器直接执行(无需执行权限)
bash 复制代码
# 语法:bash 脚本文件名
bash hello.sh

# 等价写法(sh是bash的软链接,Ubuntu中默认相同)
sh hello.sh
方式3:使用. 执行(加载到当前Shell,无新进程)
bash 复制代码
# 语法:. 脚本文件名(注意.和文件名之间有空格)
. hello.sh

# 作用:脚本中定义的变量/别名会保留在当前终端
方式4:使用source执行(与.等价,推荐用于加载配置)
bash 复制代码
source hello.sh
4种执行方式对比:
执行方式 是否需要执行权限 是否创建子Shell 适用场景
./脚本.sh 常规脚本执行
bash 脚本.sh 快速测试脚本(无需改权限)
. 脚本.sh 加载环境变量、别名等配置
source 脚本.sh 同上,更直观的配置加载方式

三、Shell变量

变量是存储数据的容器,Shell变量无需声明类型,直接赋值即可。

1. 变量类型

变量类型 定义范围 作用域 案例
局部变量 脚本/函数内部定义 仅当前脚本/函数有效 name="张三"
全局变量 脚本中用export声明 当前Shell及子Shell有效 export PATH=$PATH:/usr/local
环境变量 系统预定义或用户配置 所有Shell进程有效 $HOME(用户家目录)、$PATH
内部变量 Shell内置的特殊变量 自动赋值,无需定义 $0(脚本名)、$?(返回值)

2. 变量赋值和访问

核心语法规则:
  • 赋值时等号两边不能有空格(Shell语法严格)
  • 访问变量时用$变量名${变量名}(推荐后者,避免歧义)
  • 变量值含空格时,需用单引号'或双引号"包裹
案例:变量赋值与访问
bash 复制代码
#!/bin/bash
# 1. 基本赋值(无空格)
name="李四"  # 双引号:允许变量替换和特殊字符
age=25       # 数字无需引号
address='北京市海淀区'  # 单引号:原样输出,不解析变量和特殊字符

# 2. 访问变量(${}推荐)
echo "姓名:${name}"
echo "年龄:${age}"
echo "地址:${address}"

# 3. 变量拼接(无需连接符)
echo "拼接结果1:${name}今年${age}岁"
echo "拼接结果2:${address}-${name}"

# 4. 错误示范(等号两边有空格,会报错)
# wrong_var = "错误"  # 执行会提示:wrong_var: 未找到命令

3. 内部变量(Shell内置特殊变量)

核心内部变量,带案例演示:

变量名 含义 案例场景
$0 当前脚本文件名(含路径) 脚本中打印自身名称
$n 位置参数(n≥1,1第一个参数,2第二个) 脚本接收外部传入参数
$# 位置参数的总个数 判断传入参数是否足够
$* 所有位置参数(作为一个整体) 遍历所有传入参数
$@ 所有位置参数(每个参数独立) 遍历所有传入参数(推荐)
$? 上一条命令的返回值(0成功,非0失败) 判断命令执行结果
$$ 当前Shell进程ID(PID) 脚本中记录自身进程ID
$! 上一个后台进程的PID 管理后台运行的程序
内部变量案例:
bash 复制代码
#!/bin/bash
# 内部变量演示脚本:inner_var.sh
echo "====================================="
echo "当前脚本名:$0"  # 输出脚本文件名(如./inner_var.sh)
echo "传入参数总个数:$#"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第三个参数:$3"
echo "====================================="

# 遍历所有位置参数($@推荐,支持空格参数)
echo "用\$@遍历参数(每个参数独立):"
for param in "$@"; do
    echo "参数:$param"
done

echo "====================================="
# 测试$?(上一条命令的返回值)
echo "执行ls命令..."
ls /tmp  # 执行一个正确的命令
echo "ls命令返回值:$?"  # 输出0(成功)

echo "执行一个不存在的命令..."
wrong_cmd  # 执行错误命令
echo "错误命令返回值:$?"  # 输出非0(如127,代表命令未找到)

echo "当前脚本PID:$$"
执行测试(传入3个参数):
bash 复制代码
chmod +x inner_var.sh
./inner_var.sh "参数1" "参数2 带空格" 参数3
输出结果(示例):
复制代码
=====================================
当前脚本名:./inner_var.sh
传入参数总个数:3
第一个参数:参数1
第二个参数:参数2 带空格
第三个参数:参数3
=====================================
用$@遍历参数(每个参数独立):
参数:参数1
参数:参数2 带空格
参数:参数3
=====================================
执行ls命令...
tmpfile1  tmpfile2
ls命令返回值:0
执行一个不存在的命令...
./inner_var.sh: 第22行: wrong_cmd: 未找到命令
错误命令返回值:127
当前脚本PID:12345

4. 位置参数(脚本外部传入参数)

通过$1$2...$n接收,$#判断参数个数,$*/$@获取所有参数。

案例:计算两个数的和(接收外部参数)
bash 复制代码
#!/bin/bash
# 脚本名:add.sh,功能:接收两个整数参数,计算和
echo "====================================="
# 判断传入参数个数是否为2
if [ $# -ne 2 ]; then
    echo "错误:请传入2个整数参数!"
    echo "使用方式:$0 数字1 数字2"
    exit 1  # 退出脚本,返回非0值(表示执行失败)
fi

# 接收参数并计算($1是第一个参数,$2是第二个)
num1=$1
num2=$2
sum=$((num1 + num2))  # 算术运算

echo "${num1} + ${num2} = ${sum}"
echo "====================================="
执行测试:
bash 复制代码
chmod +x add.sh
./add.sh 10 25  # 正确传入2个参数,输出35
./add.sh 5      # 传入1个参数,提示错误

5. 变量值输出

常用输出命令:echo(简单输出)、printf(格式化输出)。

5.1 echo命令
  • 语法:echo [选项] 内容
  • 常用选项:-n(不换行)、-e(解析转义字符,如\n换行、\t制表符)
5.2 printf命令
  • 语法:printf "格式字符串" 变量1 变量2...
  • 支持格式化符号:%s(字符串)、%d(整数)、%f(浮点数)、%x(十六进制)
案例:输出命令对比
bash 复制代码
#!/bin/bash
# 输出命令演示
echo "=== echo命令 ==="
echo "普通输出"
echo -n "不换行输出:"  # 不换行
echo "后续内容"
echo -e "解析转义字符:\n第一行\n第二行\t制表符"  # \n换行,\t制表符

echo -e "\n=== printf命令 ==="
name="张三"
age=28
score=95.5
printf "姓名:%s,年龄:%d,分数:%.1f\n" $name $age $score  # %.1f保留1位小数
printf "十六进制:%x\n" 255  # 255的十六进制是ff

6. 变量值读取(接收用户输入)

使用read命令,语法:read [选项] 变量名

常用选项:
  • -p "提示信息":输入前显示提示
  • -t 秒数:超时时间(超过时间未输入则退出)
  • -n 字符数:读取指定个数的字符后自动结束(无需按回车)
  • -s:静默输入(不显示输入内容,适用于密码输入)
案例:read命令实用场景
bash 复制代码
#!/bin/bash
# 读取用户输入演示
echo "====================================="
# 1. 基础读取(带提示)
read -p "请输入你的姓名:" name
echo "你好,${name}!"

# 2. 超时读取(5秒超时)
read -t 5 -p "请在5秒内输入你的年龄(超时自动退出):" age
if [ -z "$age" ]; then  # 判断age是否为空(超时则为空)
    echo -e "\n超时未输入!"
else
    echo "你的年龄是:${age}"
fi

# 3. 静默输入(密码)
read -s -p "请输入密码(不显示):" password
echo -e "\n密码已接收(仅演示,实际脚本中不要明文输出密码)"

# 4. 读取2个字符(无需回车)
read -n 2 -p "请输入2个字符:" char
echo -e "\n你输入的字符是:${char}"
echo "====================================="

7. 变量替换(灵活处理变量值)

Shell支持多种变量替换语法,用于处理变量为空、默认值、字符串截取等场景。

替换语法 含义(变量var不存在或为空时) 变量var有值时
${var:-默认值} 返回默认值,不改变var本身 返回var的值
${var:=默认值} 返回默认值,且将默认值赋值给var 返回var的值
${var:?提示信息} 输出提示信息并退出脚本(适用于必填变量) 返回var的值
${var:+替代值} 返回空(无替代) 返回替代值,不改变var
${var:起始:长度} 字符串截取(起始索引从0开始,长度可选) 截取后的字符串
${#var} 返回变量值的字符长度 字符长度
案例:变量替换实战
bash 复制代码
#!/bin/bash
# 变量替换演示
echo "====================================="
# 1. ${var:-默认值}(不修改var)
var1=""  # 空变量
var2="已赋值"
echo "var1默认值:${var1:-未设置}"  # 输出"未设置"
echo "var1原值:${var1}"  # 仍为空
echo "var2默认值:${var2:-未设置}"  # 输出"已赋值"

# 2. ${var:=默认值}(修改var)
var3=""
echo "var3默认值:${var3:=默认值}"  # 输出"默认值",且var3被赋值
echo "var3新值:${var3}"  # 输出"默认值"

# 3. ${var:?提示}(必填变量校验)
# read -p "请输入必填参数:" required_var
# ${required_var:?错误:必填参数不能为空!}  # 为空则退出并提示

# 4. 字符串截取
str="Hello,Ubuntu22.04"
echo "原字符串:${str}"
echo "从索引0截取5个字符:${str:0:5}"  # 输出"Hello"
echo "从索引6截取(到末尾):${str:6}"  # 输出"Ubuntu22.04"
echo "字符串长度:${#str}"  # 输出14(H-e-l-l-o-,-U-b-u-n-t-u-2-2-.0-4共14个)

echo "====================================="

四、表达式与运算符

Shell中的表达式包括算术表达式、关系表达式、逻辑表达式等,需通过特定语法解析(如$((...))expr[])。

1. 表达式分类与解析方式

表达式类型 常用解析方式 适用场景
算术表达式 $((...))letexpr 整数运算(Shell不支持浮点数)
关系表达式 [][[ ]]test 数值比较、字符串比较
逻辑表达式 &&(与)、`

2. 算术运算符(仅支持整数)

运算符 含义 案例(a=10,b=3) 结果
+ 加法 $((a + b)) 13
- 减法 $((a - b)) 7
* 乘法 $((a * b)) 30
/ 除法(整数) $((a / b)) 3
% 取余(模运算) $((a % b)) 1
++ 自增(前/后) $((a++))$((++a)) 10、12
-- 自减(前/后) $((a--))$((--a)) 10、8
** 幂运算 $((a ** 2)) 100
案例:算术运算实战
bash 复制代码
#!/bin/bash
# 算术运算符演示
a=10
b=3
echo "====================================="
echo "初始值:a=${a},b=${b}"

# 1. 基础运算($((...))推荐)
echo "a + b = $((a + b))"
echo "a - b = $((a - b))"
echo "a * b = $((a * b))"
echo "a / b = $((a / b))"  # 整数除法,结果3
echo "a % b = $((a % b))"  # 取余,结果1
echo "a的平方 = $((a ** 2))"  # 100

# 2. 自增自减
echo -e "\n自增自减演示:"
echo "a++ = $((a++))"  # 先使用a的值(10),再自增(a变为11)
echo "a = ${a}"  # 11
echo "++a = $((++a))"  # 先自增(a变为12),再使用
echo "a = ${a}"  # 12

# 3. let命令(直接执行算术运算,无需$((...)))
let c=a*b  # c = 12 * 3 = 36
echo -e "\nlet命令:c = a * b = ${c}"

# 4. expr命令(注意运算符两边需空格,乘法需转义\*)
d=$(expr $a + $b)  # 12 + 3 = 15
e=$(expr $a \* $b)  # 12 * 3 = 36
echo "expr命令:a + b = ${d},a * b = ${e}"
echo "====================================="

3. 整数关系运算符(比较两个整数)

用于判断两个整数的大小关系,需结合[][[ ]]test使用。

运算符 含义 语法示例(a=10,b=3) 结果
-eq 等于(equal) [ $a -eq $b ] 假(0)
-ne 不等于(not equal) [ $a -ne $b ] 真(1)
-gt 大于(greater than) [ $a -gt $b ] 真(1)
-lt 小于(less than) [ $a -lt $b ] 假(0)
-ge 大于等于(>=) [ $a -ge $b ] 真(1)
-le 小于等于(<=) [ $a -le $b ] 假(0)
案例:整数比较
bash 复制代码
#!/bin/bash
# 整数关系运算符演示
read -p "请输入第一个整数:" num1
read -p "请输入第二个整数:" num2

echo "====================================="
# 注意:[]两边必须有空格,变量需加$
if [ $num1 -eq $num2 ]; then
    echo "${num1} 等于 ${num2}"
elif [ $num1 -gt $num2 ]; then
    echo "${num1} 大于 ${num2}"
else
    echo "${num1} 小于 ${num2}"
fi

# 进阶:使用((...))简化语法(无需-gt等,直接用>、<、==)
echo -e "\n使用((...))简化比较:"
if ((num1 >= num2)); then
    echo "${num1} >= ${num2}"
else
    echo "${num1} < ${num2}"
fi
echo "====================================="

4. 字符串检测运算符

用于判断字符串是否为空、是否相等、长度等。

运算符 含义 语法示例(str1="abc",str2="def") 结果
= 字符串相等(==等价) [ "$str1" = "$str2" ]
!= 字符串不相等 [ "$str1" != "$str2" ]
-z 字符串长度为0(空字符串) [ -z "$str3" ](str3="")
-n 字符串长度不为0(非空) [ -n "$str1" ]
< 字符串字典序小于(需转义) [ "$str1" \< "$str2" ] 真(abc<def)
> 字符串字典序大于(需转义) [ "$str1" \> "$str2" ]
案例:字符串检测
bash 复制代码
#!/bin/bash
# 字符串检测运算符演示
str1="hello"
str2="hello"
str3=""  # 空字符串
str4="world"

echo "====================================="
# 1. 字符串相等判断(推荐加双引号,避免空格问题)
if [ "$str1" = "$str2" ]; then
    echo "${str1} 和 ${str2} 相等"
else
    echo "${str1} 和 ${str2} 不相等"
fi

# 2. 字符串不相等
if [ "$str1" != "$str4" ]; then
    echo "${str1} 和 ${str4} 不相等"
fi

# 3. 空字符串判断(-z)
if [ -z "$str3" ]; then
    echo "str3 是空字符串"
fi

# 4. 非空判断(-n)
if [ -n "$str1" ]; then
    echo "str1 是非空字符串,长度:${#str1}"
fi

# 5. 字典序比较(需转义<、>)
if [ "$str1" \< "$str4" ]; then
    echo "${str1} 的字典序小于 ${str4}"
fi
echo "====================================="

5. 文件测试运算符

用于判断文件/目录的属性(是否存在、是否为目录、是否可执行等),是运维脚本的核心运算符。

运算符 含义 语法示例
-e 文件/目录是否存在 [ -e /etc/passwd ]
-f 是否为普通文件(非目录) [ -f ~/.bashrc ]
-d 是否为目录 [ -d /home ]
-r 是否有读权限 [ -r ./test.sh ]
-w 是否有写权限 [ -w ./test.sh ]
-x 是否有执行权限 [ -x ./test.sh ]
-s 是否为非空文件(大小>0) [ -s ./log.txt ]
-L 是否为符号链接 [ -L /usr/bin/sh ]
-nt 文件A是否比文件B新 [ fileA -nt fileB ]
-ot 文件A是否比文件B旧 [ fileA -ot fileB ]
案例:文件属性检测
bash 复制代码
#!/bin/bash
# 文件测试运算符演示
file_path="./test_file.txt"
dir_path="./test_dir"

echo "====================================="
# 1. 判断文件是否存在
if [ -e "$file_path" ]; then
    echo "${file_path} 已存在"
else
    echo "${file_path} 不存在,创建文件..."
    touch "$file_path"  # 创建文件
fi

# 2. 判断是否为普通文件
if [ -f "$file_path" ]; then
    echo "${file_path} 是普通文件"
fi

# 3. 判断是否为目录
if [ -d "$dir_path" ]; then
    echo "${dir_path} 是目录"
else
    echo "${dir_path} 不是目录,创建目录..."
    mkdir -p "$dir_path"  # 创建目录
fi

# 4. 判断文件权限
if [ -r "$file_path" ]; then
    echo "${file_path} 有读权限"
fi
if [ -w "$file_path" ]; then
    echo "${file_path} 有写权限"
fi
if [ -x "$file_path" ]; then
    echo "${file_path} 有执行权限"
else
    echo "${file_path} 无执行权限,添加权限..."
    chmod +rwx "$file_path"
fi

# 5. 判断文件是否非空
if [ -s "$file_path" ]; then
    echo "${file_path} 是非空文件"
else
    echo "${file_path} 是空文件,写入内容..."
    echo "测试内容" > "$file_path"
fi
echo "====================================="

6. 布尔运算符(多条件组合)

用于组合多个条件判断,支持逻辑与、或、非。

运算符 含义 语法示例(结合文件测试) 说明
-a 逻辑与(and) [ -f "$file" -a -r "$file" ] 文件是普通文件且有读权限
-o 逻辑或(or) [ -d "$dir" -o -f "$dir" ] 路径是目录或普通文件
! 逻辑非(not) [ ! -e "$file" ] 文件不存在
&& 逻辑与(推荐,[[ ]]中使用) [[ -f "$file" && -r "$file" ]] 同-a,更直观
` ` 逻辑或(推荐,[[ ]]中使用)
案例:布尔运算组合条件
bash 复制代码
#!/bin/bash
# 布尔运算符演示
file="./test.sh"

echo "====================================="
# 1. -a 逻辑与(文件存在且是普通文件)
if [ -e "$file" -a -f "$file" ]; then
    echo "${file} 存在且是普通文件"
fi

# 2. -o 逻辑或(文件存在或目录存在)
dir="./nonexistent_dir"
if [ -e "$file" -o -d "$dir" ]; then
    echo "${file} 存在 或 ${dir} 存在"
fi

# 3. ! 逻辑非(文件不存在)
if [ ! -e "$dir" ]; then
    echo "${dir} 不存在"
fi

# 4. 推荐用法:[[ ]] + &&/||(支持空格,更易读)
if [[ -f "$file" && -x "$file" ]]; then
    echo "${file} 是普通文件且有执行权限"
fi

# 5. 复杂组合(文件存在且(有读权限或有写权限))
if [[ -e "$file" && ( -r "$file" || -w "$file" ) ]]; then
    echo "${file} 存在且有读权限或写权限"
fi
echo "====================================="

7. 三目运算(条件判断简化)

Shell的三目运算语法:条件表达式 ? 表达式1 : 表达式2,需结合$((...))(数值)或$([[ ... ]] && ... || ...)(字符串/文件)。

案例:三目运算实战
bash 复制代码
#!/bin/bash
# 三目运算演示
echo "====================================="
# 1. 数值三目运算($((...)))
a=15
b=20
max=$((a > b ? a : b))  # a>b则取a,否则取b
echo "${a} 和 ${b} 中较大的数是:${max}"

# 2. 字符串三目运算([[ ... ]] && ... || ...)
str="test"
result=$([[ -n "$str" ]] && "非空" || "空字符串")
echo "字符串状态:${result}"

# 3. 文件三目运算
file="./test.sh"
file_status=$([[ -e "$file" ]] && "存在" || "不存在")
echo "${file} 的状态:${file_status}"

# 4. 结合read的三目运算
read -p "请输入分数(0-100):" score
grade=$((score >= 90 ? 1 : (score >= 60 ? 2 : 3)))
case $grade in
    1) echo "成绩等级:优秀";;
    2) echo "成绩等级:及格";;
    3) echo "成绩等级:不及格";;
esac
echo "====================================="

五、流程控制语句

Shell流程控制包括多命令组合、条件判断(if/case)、循环(for/while/until),是脚本逻辑的核心。

1. 多命令的组合执行

通过特定符号连接多个命令,控制执行顺序(顺序、条件、并行)。

组合符号 含义 语法示例 说明
; 顺序执行(忽略前命令结果) cd /tmp; touch test.txt 不管cd是否成功,都执行touch
&& 逻辑与(前命令成功才执行后命令) cd /tmp && touch test.txt cd成功才执行touch
` ` 逻辑或(前命令失败才执行后命令)
案例:多命令组合
bash 复制代码
#!/bin/bash
# 多命令组合执行演示
echo "====================================="
# 1. ; 顺序执行(忽略失败)
echo "=== ; 组合 ==="
cd /nonexistent; touch test1.txt  # cd失败,但仍执行touch(当前目录创建test1.txt)
ls test1.txt  # 存在

# 2. && 逻辑与(前成功后执行)
echo -e "\n=== && 组合 ==="
cd /tmp && touch test2.txt  # cd成功,执行touch(/tmp/test2.txt)
ls /tmp/test2.txt  # 存在

# 3. || 逻辑或(前失败后执行)
echo -e "\n=== || 组合 ==="
cd /nonexistent || echo "错误:目录不存在,无法切换"  # cd失败,输出提示

# 4. 组合使用(&& + ||)
echo -e "\n=== 复杂组合 ==="
read -p "请输入文件名:" filename
touch "$filename" && echo "文件${filename}创建成功" || echo "文件${filename}创建失败"
echo "====================================="

2. 条件语句

2.1 if语句(单条件判断)

语法:

bash 复制代码
if [ 条件表达式 ]; then
    命令1
    命令2
fi

或(推荐,支持更复杂条件):

bash 复制代码
if [[ 条件表达式 ]]; then
    命令1
    命令2
fi
2.2 if-else语句(二选一条件)

语法:

bash 复制代码
if [ 条件表达式 ]; then
    条件成立时执行的命令
else
    条件不成立时执行的命令
fi
2.3 if-elif-else语句(多条件判断)

语法:

bash 复制代码
if [ 条件1 ]; then
    命令1
elif [ 条件2 ]; then
    命令2
elif [ 条件3 ]; then
    命令3
else
    所有条件不成立时执行的命令
fi
2.4 case语句(多值匹配)

适用于变量值为固定几个选项的场景,比if-elif更简洁。

语法:

bash 复制代码
case $变量名 in
    选项1)
        命令1
        ;;  # 结束当前分支(必须)
    选项2)
        命令2
        ;;
    选项3|选项4)  # 多个选项匹配同一命令
        命令3
        ;;
    *)  # 默认选项(所有不匹配的情况)
        命令4
        ;;
esac
案例1:if-elif-else判断文件类型
bash 复制代码
#!/bin/bash
# if-elif-else演示:判断路径类型
read -p "请输入一个文件/目录路径:" path

echo "====================================="
if [[ -z "$path" ]]; then
    echo "错误:路径不能为空!"
elif [[ ! -e "$path" ]]; then
    echo "错误:${path} 不存在!"
elif [[ -d "$path" ]]; then
    echo "${path} 是一个目录"
    echo "目录下的文件:"
    ls "$path"
elif [[ -f "$path" ]]; then
    echo "${path} 是一个普通文件"
    echo "文件大小:$(du -sh "$path" | awk '{print $1}')"  # 显示文件大小
elif [[ -L "$path" ]]; then
    echo "${path} 是一个符号链接"
    echo "链接指向:$(readlink "$path")"  # 显示链接目标
else
    echo "${path} 是特殊文件(如设备文件、管道等)"
fi
echo "====================================="
案例2:case语句实现菜单交互
bash 复制代码
#!/bin/bash
# case语句演示:简易菜单
echo "====================================="
echo "          简易系统工具菜单"
echo "1. 查看系统时间"
echo "2. 查看磁盘空间"
echo "3. 查看内存使用"
echo "4. 退出"
echo "====================================="

read -p "请输入选项(1-4):" choice

case $choice in
    1)
        echo "当前系统时间:"
        date
        ;;
    2)
        echo "磁盘空间使用情况:"
        df -h  # 人类可读格式显示磁盘空间
        ;;
    3)
        echo "内存使用情况:"
        free -h  # 人类可读格式显示内存
        ;;
    4)
        echo "退出程序,再见!"
        exit 0  # 正常退出
        ;;
    *)
        echo "错误:无效选项,请输入1-4!"
        exit 1  # 错误退出
        ;;
esac
echo "====================================="

3. 循环结构

3.1 for循环(遍历式循环)

两种语法:

  • 语法1(遍历列表):

    bash 复制代码
    for 变量名 in 列表; do
        命令
    done
  • 语法2(类C风格循环):

    bash 复制代码
    for ((初始值; 循环条件; 步长)); do
        命令
    done
3.2 while循环(条件式循环,条件成立则执行)

语法:

bash 复制代码
while [ 条件表达式 ]; do
    命令
done
3.3 until循环(条件式循环,条件不成立则执行)

语法:

bash 复制代码
until [ 条件表达式 ]; do
    命令
done
3.4 循环控制符
  • break:跳出当前循环(多层循环需指定层数,如break 2跳出2层)
  • continue:跳过当前循环的剩余命令,直接进入下一次循环
案例1:for循环遍历列表
bash 复制代码
#!/bin/bash
# for循环演示1:遍历列表
echo "====================================="
# 1. 遍历固定列表
echo "=== 遍历固定列表 ==="
fruits=("苹果" "香蕉" "橙子" "葡萄")  # 数组
for fruit in "${fruits[@]}"; do
    echo "水果:${fruit}"
done

# 2. 遍历文件列表(当前目录下的.sh文件)
echo -e "\n=== 遍历.sh文件 ==="
for file in *.sh; do
    if [[ -f "$file" ]]; then  # 确保是文件(避免无.sh文件时匹配*.sh本身)
        echo "Shell脚本文件:${file}"
    fi
done

# 3. 类C风格循环(1到10求和)
echo -e "\n=== 类C风格循环(1-10求和) ==="
sum=0
for ((i=1; i<=10; i++)); do
    sum=$((sum + i))
done
echo "1+2+...+10 = ${sum}"  # 输出55
echo "====================================="
案例2:while循环实现计数器与用户交互
bash 复制代码
#!/bin/bash
# while循环演示
echo "====================================="
# 1. 计数器(1到5)
echo "=== 计数器 ==="
count=1
while [[ $count -le 5 ]]; do
    echo "计数:${count}"
    count=$((count + 1))  # 自增
    sleep 1  # 暂停1秒
done

# 2. 无限循环(用户输入q退出)
echo -e "\n=== 无限循环(输入q退出) ==="
while true; do
    read -p "请输入内容(输入q退出):" input
    if [[ "$input" == "q" || "$input" == "Q" ]]; then
        echo "退出循环,再见!"
        break  # 跳出循环
    fi
    echo "你输入的内容是:${input}"
done

# 3. until循环(条件不成立时执行,直到条件成立)
echo -e "\n=== until循环(计数到5) ==="
num=1
until [[ $num -gt 5 ]]; do
    echo "until计数:${num}"
    num=$((num + 1))
done
echo "====================================="
案例3:循环控制符break/continue
bash 复制代码
#!/bin/bash
# break/continue演示
echo "====================================="
# 1. break:跳出循环(遍历1-10,遇到5退出)
echo "=== break演示 ==="
for ((i=1; i<=10; i++)); do
    if [[ $i -eq 5 ]]; then
        echo "遇到5,跳出循环"
        break
    fi
    echo "当前值:${i}"
done

# 2. continue:跳过当前循环(遍历1-10,跳过偶数)
echo -e "\n=== continue演示 ==="
for ((i=1; i<=10; i++)); do
    if [[ $((i % 2)) -eq 0 ]]; then  # 判断偶数
        continue  # 跳过偶数,进入下一次循环
    fi
    echo "奇数:${i}"
done

# 3. break 2:跳出2层循环
echo -e "\n=== break 2演示 ==="
for ((i=1; i<=3; i++)); do
    echo "外层循环:${i}"
    for ((j=1; j<=3; j++)); do
        if [[ $j -eq 2 ]]; then
            echo "内层循环j=2,跳出2层循环"
            break 2  # 同时跳出内层和外层循环
        fi
        echo "  内层循环:${j}"
    done
done
echo "====================================="

六、函数

Shell函数用于封装重复执行的代码块,提高脚本复用性和可读性。

1. 函数的定义和调用

定义方式(两种等价):
  • 方式1(推荐,清晰):

    bash 复制代码
    函数名() {
        函数体(命令/逻辑)
    }
  • 方式2(带function关键字):

    bash 复制代码
    function 函数名 {
        函数体(命令/逻辑)
    }
调用方式:

直接输入函数名(无需括号,参数直接跟在后面,用空格分隔)。

案例:函数定义与调用
bash 复制代码
#!/bin/bash
# 函数定义与调用演示
echo "====================================="
# 定义函数1:无参数无返回值
hello() {
    echo "Hello, Shell函数!"
}

# 定义函数2:带参数(计算两个数的和)
add() {
    # 函数内的参数通过$1、$2获取(与脚本位置参数相同)
    local num1=$1  # local:定义局部变量(仅函数内有效)
    local num2=$2
    local sum=$((num1 + num2))
    echo "${num1} + ${num2} = ${sum}"
}

# 定义函数3:带function关键字
function print_info {
    echo "当前系统时间:$(date)"
    echo "当前登录用户:$(whoami)"
}

# 调用函数
echo "=== 调用hello函数 ==="
hello

echo -e "\n=== 调用add函数 ==="
add 20 30  # 传递两个参数
add 5 15   # 再次调用

echo -e "\n=== 调用print_info函数 ==="
print_info
echo "====================================="

2. 函数的返回值

Shell函数的返回值有两种方式,注意:return仅支持返回0-255的整数(0成功,非0失败)。

方式1:使用return(返回状态码,0-255)
  • 语法:return 状态码
  • 调用后通过$?获取返回值
方式2:使用echo输出(返回任意值,推荐)
  • 函数内用echo输出结果
  • 调用时通过变量=$(函数名)捕获输出
案例:函数返回值
bash 复制代码
#!/bin/bash
# 函数返回值演示
echo "====================================="
# 方式1:return返回状态码(0-255)
check_file() {
    local file=$1
    if [[ -f "$file" ]]; then
        return 0  # 成功(文件存在)
    else
        return 1  # 失败(文件不存在)
    fi
}

echo "=== return返回状态码 ==="
check_file "./test.sh"
if [[ $? -eq 0 ]]; then
    echo "文件存在"
else
    echo "文件不存在"
fi

# 方式2:echo返回任意值(计算阶乘)
factorial() {
    local n=$1
    local result=1
    if [[ $n -lt 0 ]]; then
        echo "错误:负数没有阶乘"
        return 1  # 状态码表示失败
    fi
    for ((i=1; i<=n; i++)); do
        result=$((result * i))
    done
    echo $result  # 输出结果作为返回值
}

echo -e "\n=== echo返回任意值 ==="
read -p "请输入一个非负整数:" num
fact_result=$(factorial $num)  # 捕获echo输出
if [[ $? -eq 0 ]]; then  # 判断函数执行状态
    echo "${num}的阶乘是:${fact_result}"
else
    echo "${fact_result}"  # 输出错误信息
fi
echo "====================================="

3. 函数参数

函数参数的传递和获取与脚本位置参数完全一致:

  • 传递参数:函数名 参数1 参数2 ... 参数n
  • 获取参数:$1(第一个参数)、$2(第二个)、$#(参数个数)、$@(所有参数)
案例:函数参数实战(批量创建用户)
bash 复制代码
#!/bin/bash
# 函数参数演示:批量创建用户
echo "====================================="
# 定义函数:创建单个用户
create_user() {
    local username=$1
    local password=$2

    # 检查参数是否完整
    if [[ $# -ne 2 ]]; then
        echo "错误:函数需要2个参数(用户名、密码)"
        return 1
    fi

    # 检查用户是否已存在
    if id -u "$username" &>/dev/null; then  # &>/dev/null:屏蔽输出
        echo "用户${username}已存在,跳过"
        return 0
    fi

    # 创建用户(-m:创建家目录,-s:指定shell)
    sudo useradd -m -s /bin/bash "$username" &>/dev/null
    if [[ $? -ne 0 ]]; then
        echo "用户${username}创建失败"
        return 1
    fi

    # 设置密码(echo "密码" | passwd --stdin 用户名)
    echo "${password}" | sudo passwd --stdin "$username" &>/dev/null
    if [[ $? -eq 0 ]]; then
        echo "用户${username}创建成功,初始密码:${password}"
    else
        echo "用户${username}密码设置失败"
        sudo userdel -r "$username"  # 删除创建失败的用户
        return 1
    fi
}

# 批量创建用户(调用函数多次)
echo "=== 批量创建用户 ==="
create_user "user1" "123456"
create_user "user2" "654321"
create_user "user3" "abc123"

# 遍历参数创建用户(脚本接收外部参数列表)
echo -e "\n=== 从脚本参数创建用户 ==="
if [[ $# -gt 0 ]]; then
    for ((i=1; i<=$#; i+=2)); do
        username=${!i}  # 间接引用参数(i=1时取$1,i=3时取$3)
        password=${!((i+1))}
        create_user "$username" "$password"
    done
else
    echo "未传入脚本参数,跳过批量创建"
fi
echo "====================================="
执行测试(需root权限,或sudo):
bash 复制代码
chmod +x create_users.sh
# 直接运行(创建user1、user2、user3)
sudo ./create_users.sh
# 从外部参数创建(创建user4、user5)
sudo ./create_users.sh user4 111222 user5 333444

七、管道、重定向与命令扩展

1. 管道(|)

将前一个命令的标准输出 作为后一个命令的标准输入,实现命令协作。

语法:命令1 | 命令2 | 命令3...
常用场景:
  • 过滤内容(grep
  • 排序(sort
  • 去重(uniq
  • 统计(wc
案例:管道命令实战
bash 复制代码
#!/bin/bash
# 管道命令演示
echo "====================================="
# 1. 过滤进程(查找bash相关进程)
echo "=== 查找bash进程 ==="
ps aux | grep bash  # ps aux列出所有进程,grep过滤含bash的行

# 2. 排序+去重(统计当前目录下文件类型)
echo -e "\n=== 统计文件类型 ==="
ls -l | awk '{print $1}' | sort | uniq -c  # 列1是文件类型,排序后去重并计数

# 3. 统计行数(统计/etc/passwd中用户数)
echo -e "\n=== 统计系统用户数 ==="
cat /etc/passwd | wc -l  # /etc/passwd每行一个用户,wc -l统计行数

# 4. 复杂管道(查找占用内存前5的进程)
echo -e "\n=== 占用内存前5的进程 ==="
ps aux --sort=-%mem | head -n 6  # --sort=-%mem按内存使用率降序,head取前6行(含表头)

# 5. 过滤日志(假设存在access.log,查找404错误)
echo -e "\n=== 查找404错误日志(模拟) ==="
# 模拟日志内容,过滤含404的行
echo -e "192.168.1.1 - - [01/Jan/2025:12:00:00 +0800] \"GET / HTTP/1.1\" 200 1024" > access.log
echo -e "192.168.1.2 - - [01/Jan/2025:12:01:00 +0800] \"GET /nonexistent HTTP/1.1\" 404 512" >> access.log
cat access.log | grep "404"  # 过滤404错误行
rm -f access.log  # 清理临时文件
echo "====================================="

2. 重定向

Shell默认有3种I/O流,重定向用于改变流的方向(如输出到文件、从文件读取):

  • 标准输入(stdin):0,默认从键盘输入
  • 标准输出(stdout):1,默认输出到终端
  • 标准错误(stderr):2,默认输出到终端
常用重定向符号:
符号 含义 语法示例
> 标准输出重定向(覆盖文件) ls > file.txt
>> 标准输出重定向(追加到文件) echo "内容" >> file.txt
< 标准输入重定向(从文件读取) read var < file.txt
2> 标准错误重定向(覆盖文件) ls /nonexistent 2> error.log
2>> 标准错误重定向(追加到文件) command 2>> error.log
&> 标准输出+标准错误重定向(覆盖) command &> all.log
&>> 标准输出+标准错误重定向(追加) command &>> all.log
>/dev/null 丢弃标准输出(黑洞设备) command > /dev/null
2>&1 将标准错误重定向到标准输出 command > all.log 2>&1
案例:重定向实战
bash 复制代码
#!/bin/bash
# 重定向演示
log_file="output.log"
error_file="error.log"
all_file="all.log"

# 清理旧文件
rm -f $log_file $error_file $all_file

echo "====================================="
# 1. > 覆盖输出
echo "=== > 覆盖输出 ==="
ls -l > $log_file  # 将ls结果覆盖写入log_file
echo "ls结果已写入${log_file}"
cat $log_file

# 2. >> 追加输出
echo -e "\n=== >> 追加输出 ==="
echo "当前时间:$(date)" >> $log_file  # 追加内容到log_file
echo "追加时间后:"
cat $log_file

# 3. 2> 错误重定向
echo -e "\n=== 2> 错误重定向 ==="
ls /nonexistent 2> $error_file  # 将错误信息写入error_file
echo "错误信息已写入${error_file}"
cat $error_file

# 4. &> 输出+错误重定向
echo -e "\n=== &> 输出+错误重定向 ==="
ls -l /tmp /nonexistent &> $all_file  # 正确输出和错误都写入all_file
echo "所有输出(含错误)已写入${all_file}"
cat $all_file

# 5. < 输入重定向(从文件读取输入)
echo -e "\n=== < 输入重定向 ==="
echo "用户名列表:" > users.txt
echo "user1" >> users.txt
echo "user2" >> users.txt
echo "user3" >> users.txt

echo "从users.txt读取用户名:"
while read username; do
    echo "用户名:${username}"
done < users.txt  # 从users.txt读取输入

# 6. 丢弃输出(>/dev/null)
echo -e "\n=== 丢弃输出 ==="
ls -l > /dev/null  # 不显示ls结果(丢弃)
echo "ls结果已丢弃,无输出"

# 清理临时文件
rm -f $log_file $error_file $all_file users.txt
echo "====================================="

3. 命令续行(\)

当命令过长时,用\(反斜杠)实现换行,Shell会将其视为一行命令。

语法:
bash 复制代码
长命令部分1 \
长命令部分2 \
长命令部分3
注意:\后面不能有任何字符(包括空格),否则会失效。
案例:命令续行
bash 复制代码
#!/bin/bash
# 命令续行演示
echo "====================================="
# 长命令换行(查找包含"root"且非注释的行,显示行号)
grep -n "root" /etc/passwd | \
awk -F: '{print "行号:"$1", 用户名:"$1", UID:"$3}' | \
sort -n

echo -e "\n=== 复杂命令续行 ==="
# 统计系统中UID大于1000的用户数
cat /etc/passwd | \
grep -v "^#" | \  # 排除注释行
awk -F: '{if($3 > 1000) print $1}' | \  # 筛选UID>1000的用户
wc -l | \
xargs echo "UID大于1000的用户数:"  # xargs去除多余空格

echo "====================================="

4. 命令替换($() 或 ``)

将命令的输出结果作为变量值或命令参数,推荐使用$()(比``更易读,支持嵌套)。

语法:
bash 复制代码
变量=$(命令)
# 或(兼容旧版本Shell)
变量=`命令`
案例:命令替换实战
bash 复制代码
#!/bin/bash
# 命令替换演示
echo "====================================="
# 1. 基本命令替换(获取系统信息)
current_date=$(date +"%Y-%m-%d %H:%M:%S")  # 获取格式化时间
hostname=$(hostname)  # 获取主机名
ip_addr=$(hostname -I | awk '{print $1}')  # 获取第一个IP地址

echo "当前时间:${current_date}"
echo "主机名:${hostname}"
echo "IP地址:${ip_addr}"

# 2. 嵌套命令替换(获取当前目录下.sh文件的个数)
sh_count=$(ls -l $(pwd) | grep -c "\.sh$")  # 嵌套$(pwd)获取当前目录
echo -e "\n当前目录下.sh脚本文件个数:${sh_count}"

# 3. 命令替换作为参数(备份日志文件)
log_dir="/var/log"
backup_dir="./log_backup_$(date +%Y%m%d)"  # 备份目录名含日期

echo -e "\n=== 备份日志 ==="
mkdir -p $backup_dir
cp -r $log_dir/*.log $backup_dir 2>/dev/null  # 备份日志,忽略错误
echo "日志已备份到:${backup_dir}"
ls -l $backup_dir

# 4. 兼容写法(``)
disk_usage=`df -h / | awk 'NR==2 {print $5}'`  # 获取根目录使用率
echo -e "\n根目录磁盘使用率:${disk_usage}"

# 清理备份目录
rm -rf $backup_dir
echo "====================================="

5. 命令别名(alias)

为常用命令创建简化别名,提高操作效率,分为临时别名(当前终端有效)和永久别名(所有终端有效)。

语法:
bash 复制代码
# 创建别名
alias 别名="原命令"

# 查看所有别名
alias

# 取消别名
unalias 别名
案例:命令别名实战
bash 复制代码
#!/bin/bash
# 命令别名演示
echo "====================================="
# 1. 创建临时别名(当前终端有效)
echo "=== 创建临时别名 ==="
alias ll="ls -l"  # ll替代ls -l
alias la="ls -la"  # la替代ls -la
alias grep="grep --color=auto"  # grep结果高亮

echo "执行ll(等价于ls -l):"
ll

echo -e "\n执行la(等价于ls -la):"
la

echo -e "\n执行grep高亮:"
echo "hello world" | grep "world"  # 匹配到的world会高亮

# 2. 查看别名
echo -e "\n=== 查看所有别名 ==="
alias

# 3. 取消别名
echo -e "\n=== 取消别名 ==="
unalias ll
# ll  # 取消后执行ll会报错(未找到命令)

# 4. 创建永久别名(写入配置文件)
echo -e "\n=== 创建永久别名(需手动执行或重启终端) ==="
echo 'alias ll="ls -l"' >> ~/.bashrc  # 写入用户bash配置文件
echo 'alias la="ls -la"' >> ~/.bashrc
echo 'alias grep="grep --color=auto"' >> ~/.bashrc

# 生效配置(无需重启终端)
source ~/.bashrc

echo "永久别名已添加,重启终端或执行source ~/.bashrc生效"
echo "====================================="

八、Linux命令行帮助系统

在Shell编程中,快速获取命令帮助是必备技能,Ubuntu 22.04提供3种核心帮助方式。

1. 使用man命令获取帮助(最全面)

man(manual)是最常用的帮助命令,提供命令的详细手册(语法、选项、示例)。

语法:man [选项] 命令/配置文件
常用选项:
  • -f 命令:快速查看命令的功能(等价于whatis
  • -k 关键词:搜索包含关键词的帮助手册(等价于apropos
  • 数字:指定手册章节(如man 2 open查看系统调用open的帮助)
手册章节说明:
章节 内容类型 示例
1 用户命令(/bin、/usr/bin) man 1 ls
2 系统调用(内核提供的函数) man 2 open
3 库函数(C标准库等) man 3 printf
4 设备文件(/dev下的文件) man 4 tty
5 配置文件格式 man 5 passwd(/etc/passwd)
6 游戏相关 man 6 fortune
7 杂项(协议、文件系统等) man 7 regex(正则)
8 系统管理命令(/sbin) man 8 useradd
案例:man命令使用
bash 复制代码
#!/bin/bash
# man命令帮助演示
echo "====================================="
# 1. 查看ls命令帮助(默认章节1)
echo "=== 查看ls命令帮助 ==="
man ls  # 按q退出帮助页面

# 2. 快速查看命令功能(-f)
echo -e "\n=== 快速查看grep功能 ==="
man -f grep  # 等价于whatis grep

# 3. 搜索关键词(-k)
echo -e "\n=== 搜索与磁盘相关的命令 ==="
man -k disk  # 等价于apropos disk

# 4. 查看配置文件帮助(章节5)
echo -e "\n=== 查看/etc/passwd配置文件格式 ==="
man 5 passwd

# 5. 查看系统调用帮助(章节2)
echo -e "\n=== 查看open系统调用帮助 ==="
man 2 open
echo "====================================="

2. 使用info命令获取帮助(更详细的文档)

info提供比man更详细的结构化文档,支持超链接跳转,适合深入学习命令。

语法:info 命令
常用操作:
  • 回车:进入超链接
  • n:下一个节点
  • p:上一个节点
  • u:上一级节点
  • q:退出info
案例:info命令使用
bash 复制代码
#!/bin/bash
# info命令帮助演示
echo "====================================="
# 查看bash的info帮助
echo "=== 查看bash的info帮助(按q退出) ==="
info bash

# 查看grep的info帮助
echo -e "\n=== 查看grep的info帮助 ==="
info grep

# 查看核心工具的info文档
echo -e "\n=== 查看coreutils的info文档(包含ls、cp等命令) ==="
info coreutils
echo "====================================="

3. 使用help选项获取帮助(快速简洁)

大多数命令支持--help-h选项,提供简洁的语法和常用选项说明,适合快速。

相关推荐
华清远见成都中心6 小时前
人工智能要学习的课程有哪些?
人工智能·学习
hssfscv6 小时前
Javaweb学习笔记——后端实战2_部门管理
java·笔记·学习
萧曵 丶6 小时前
Linux 业务场景常用命令详解
linux·运维·服务器
白帽子黑客罗哥6 小时前
不同就业方向(如AI、网络安全、前端开发)的具体学习路径和技能要求是什么?
人工智能·学习·web安全
豆是浪个7 小时前
Linux(Centos 7.6)命令详解:ps
linux·windows·centos
于越海7 小时前
材料电子理论核心四个基本模型的python编程学习
开发语言·笔记·python·学习·学习方法
我命由我123457 小时前
开发中的英语积累 P26:Recursive、Parser、Pair、Matrix、Inset、Appropriate
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
Run_Teenage7 小时前
Linux:深刻理解缓冲区
linux
北岛寒沫7 小时前
北京大学国家发展研究院 经济学原理课程笔记(第二十三课 货币供应与通货膨胀)
经验分享·笔记·学习
知识分享小能手8 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04中的Java与Android开发环境 (20)
java·学习·ubuntu