shell脚本

Shell脚本学习笔记

第一章 Shell基础认知

一、什么是Shell?

作为命令解释器和操作系统内核之间的接口,负责解析并执行用户输入的命令。

二、Bash/Zsh的区别

特性 Bash Zsh
稳定性 稳定 较稳定,插件过多可能启动慢
定制化 定制化复杂 定制化简单,支持丰富插件
版本特性 较老旧 较新

三、脚本文件结构

1. 脚本声明行

通过#!提示系统选择Shell解释器,需放在脚本第一行。

示例:

bash 复制代码
#!/bin/bash
2. 注释部分

#开头的内容为注释,不参与脚本执行,用于说明代码功能。

示例:

bash 复制代码
# 这是单行注释
name="John"  # 定义用户名变量(行尾注释)
3. 多行注释

通过<<COMMENTCOMMENT包裹实现多行注释。

示例:

bash 复制代码
#!/bin/bash

<<COMMENT
这是第一行注释内容
这是第二行注释内容
可以有多行注释内容在此处
COMMENT

echo "脚本继续执行"
4. 变量定义

用于存储数据(字符串、数字等),等号两边不能有空格 ,引用变量需加$

语法:变量名="值"

示例:

bash 复制代码
name="John"  # 字符串变量
age=25       # 数字变量
echo "Hello, $name!"  # 引用变量,输出:Hello, John!
5. 函数定义

将特定功能代码封装,方便重复调用,调用时需写函数名。

语法1(带function关键字):

bash 复制代码
function 函数名() {
    函数体
}

语法2(简化版):

bash 复制代码
函数名() {
    函数体
}

示例:

bash 复制代码
# 定义函数
function greet() {
    echo "Welcome to the script!"
}
# 调用函数(必须调用才执行)
greet
6. 主程序逻辑

脚本核心部分,包含文件操作、条件判断、循环等核心任务。

示例(判断文件是否存在):

bash 复制代码
# 判断当前目录下是否存在test.txt文件
if [ -f "test.txt" ]; then
    echo "File test.txt exists." 
else
    echo "File test.txt does not exist."
fi
7. 退出状态和返回值
  • 退出状态码:0表示执行成功,非0表示失败。
  • 通过exit指定退出状态码。

完整脚本示例:

bash 复制代码
#!/bin/bash

# 这是一个完整的Shell脚本示例,用于判断目录是否存在并创建新文件

# 定义变量
dir_name="my_directory"

# 定义函数,用于创建文件
create_file() {
    touch "$1/new_file.txt"
    echo "File new_file.txt created in $1."
}

# 主程序逻辑
if [ -d "$dir_name" ]; then
    echo "Directory $dir_name exists."
    create_file "$dir_name"
else
    echo "Directory $dir_name does not exist. Creating it..."
    mkdir "$dir_name"
    create_file "$dir_name"
fi

# 脚本执行成功,返回退出状态码0
exit 0

四、文件权限管理

Shell脚本需添加执行权限才能运行,通过chmod命令设置:

bash 复制代码
chmod +x 脚本.sh  # 给脚本添加执行权限

五、执行脚本的4种方式

1. 作为可执行程序执行
  • 步骤1:添加执行权限(chmod +x test.sh
  • 步骤2:指定路径执行(当前目录需加./
    示例:
bash 复制代码
./test.sh  # 当前目录执行
/home/user/test.sh  # 绝对路径执行
2. 使用bash或者sh命令执行

无需添加执行权限,直接通过解释器调用。

示例:

bash 复制代码
bash test.sh  # 使用bash执行
sh test.sh    # 使用sh执行(sh通常指向bash)
3. 通过环境变量(PATH)执行
  • 步骤1:将脚本目录添加到PATH(export PATH=$PATH:..表示当前目录)
  • 步骤2:直接执行脚本
    示例:
bash 复制代码
export PATH=$PATH:.  # 临时添加当前目录到PATH
test.sh              # 直接执行脚本
4. 使用source命令执行

在当前Shell环境中执行,脚本内变量/函数会影响当前Shell。

示例:

bash 复制代码
source 脚本.sh  # 方式1
. 脚本.sh       # 方式2(.后需加空格)

第二章 变量和运算符

一、变量的定义

1. 基本变量的定义
  • 规则:变量名由字母、数字、下划线组成,不能以数字开头,等号两边无空格。
    示例:
bash 复制代码
# 字符串变量
name="John"
# 数字变量
age=25
2. 环境变量的定义
  • 作用域:当前Shell及子Shell可访问,通过export定义。
    示例:
bash 复制代码
# 定义并导出环境变量
export MY_VARIABLE="Hello, World!"
3. 多组变量定义(数组)
  • 存储多个值,元素间用空格分隔,索引从0开始。
    示例:
bash 复制代码
# 方式1:直接初始化
fruits=("apple" "banana" "cherry")
# 方式2:按索引赋值
my_array[0]="apple"
my_array[1]="banana"
my_array[2]="cherry"

# 打印数组元素
echo ${my_array[0]}  # 输出:apple
echo ${my_array[1]}  # 输出:banana
echo ${my_array[2]}  # 输出:cherry

二、变量的拼接和引用

1. 基本变量引用

通过$变量名引用,示例:

bash 复制代码
name="John"
echo "My name is $name."  # 输出:My name is John.
2. 变量值的拼接

直接将变量与字符串拼接,示例:

bash 复制代码
greeting="Hello"
name="John"
message="$greeting, $name!"
echo $message  # 输出:Hello, John!
3. 数组变量的引用
  • 单个元素:${数组名[索引]}
  • 整个数组:${数组名[@]}${数组名[*]}
    示例:
bash 复制代码
fruits=("apple" "banana" "cherry")
echo ${fruits[0]}  # 输出:apple
echo ${fruits[@]}  # 输出:apple banana cherry
4. 变量的间接引用

通过另一个变量的值引用目标变量,语法:${!变量名}

示例:

bash 复制代码
var1="name"
name="John"
echo ${!var1}  # 输出:John
5. 变量的作用域
类型 定义位置 作用域范围 关键字
局部变量 函数内部 仅函数内可见 local
全局变量 函数外部 整个脚本可见

示例(局部变量):

bash 复制代码
function test_function {
    local local_var="This is a local variable."
    echo $local_var  # 函数内可输出
}
test_function
echo $local_var     # 函数外输出为空

示例(全局变量):

bash 复制代码
global_var="This is a global variable."
function test_function {
    echo $global_var  # 函数内可输出
}
test_function
echo $global_var     # 函数外可输出

三、环境变量和局部变量

1. 环境变量
  • 作用域:整个系统或特定Shell会话,所有用户/进程可访问。
  • 常见环境变量:
    • PATH:可执行文件搜索路径
    • HOME:当前用户主目录
    • LANG:系统语言和字符集
2. export命令
  • 导出局部变量:让子Shell可访问。
    示例:
bash 复制代码
# 局部变量(子Shell不可见)
local_variable="Hello, World!"
bash -c 'echo $local_variable'  # 输出空

# 导出为环境变量(子Shell可见)
export global_variable="Hello, Global!"
bash -c 'echo $global_variable'  # 输出:Hello, Global!
  • 导出函数:export -f 函数名,让子Shell可调用。
    示例:
bash 复制代码
# 定义函数
my_function() {
    echo "This is a function."
}
# 导出函数
export -f my_function
# 子Shell调用
bash -c 'my_function'  # 输出:This is a function.
3. 局部变量
  • 使用场景:临时存储数据、控制循环/条件判断。
  • 定义:函数内用local关键字,示例:
bash 复制代码
function test_func {
    local var="local value"  # 局部变量
    echo $var
}

四、算数运算

Shell仅支持整数运算,常用方式有3种:

1. $((a+b))

语法简洁,支持+-*/%

示例:

bash 复制代码
a=5
b=3
result=$((a + b))
echo "a + b 的结果是: $result"  # 输出:a + b 的结果是: 8
2. let

执行算术表达式,变量名无需加$

示例:

bash 复制代码
a=5
b=3
let result=a+b
echo "a + b 的结果是: $result"  # 输出:a + b 的结果是: 8
3. expr

外部命令,运算符与操作数间需有空格。

示例:

bash 复制代码
a=5
b=3
result=$(expr $a + $b)
echo "a + b 的结果是: $result"  # 输出:a + b 的结果是: 8

五、字符串操作

1. 字符串拼接
  • 直接拼接:
bash 复制代码
str1="Hello"
str2=" World"
result=$str1$str2
echo $result  # 输出:Hello World
  • 双引号拼接:
bash 复制代码
str1="Hello"
str2=" World"
result="$str1$str2"
echo $result  # 输出:Hello World
2. 获取字符串长度

语法:${#字符串}

示例:

bash 复制代码
str="Hello, World!"
length=${#str}
echo "字符串的长度是: $length"  # 输出:字符串的长度是: 13
3. 字符串的提取

语法:${string:offset:length}(offset起始位置,length可选)

示例:

bash 复制代码
str="Hello, World!"
# 从索引7开始提取
substr1=${str:7}
echo "从索引7开始的子串是: $substr1"  # 输出:从索引7开始的子串是: World!

# 从索引0开始,提取长度5的子串
substr2=${str:0:5}
echo "从索引0开始,长度为5的子串是: $substr2"  # 输出:从索引0开始,长度为5的子串是: Hello

第三章 控制结构入门

一、条件判断

1. if语句

语法:

bash 复制代码
if [ 条件判断 ]; then
    # 条件为真执行
elif [ 另一个条件判断 ]; then
    # 前条件为假,此条件为真执行
else
    # 所有条件为假执行
fi

示例:

bash 复制代码
num=10
if [ $num -gt 20 ]; then
    echo "数字大于20"
elif [ $num -gt 5 ]; then
    echo "数字大于5但不大于20"
else
    echo "数字小于等于5"
fi  # 输出:数字大于5但不大于20
2. case语句

用于多分支判断,语法:

bash 复制代码
case 值 in
    模式1)
        # 匹配模式1执行
        ;;
    模式2)
        # 匹配模式2执行
        ;;
    *)
        # 所有模式不匹配执行
        ;;
esac

示例:

bash 复制代码
fruit="apple"
case $fruit in
    apple)
        echo "这是苹果"
        ;;
    banana)
        echo "这是香蕉"
        ;;
    *)
        echo "未知水果"
        ;;
esac  # 输出:这是苹果
3. test命令

用于数据比较和文件测试,语法:test 条件(等价于[ 条件 ][]两边需有空格)。

数值比较
运算符 描述
-eq 等于
-ne 不等于
-gt 大于
-ge 大于等于
-lt 小于
-le 小于等于
字符串比较
运算符 描述
= 等于
!= 不等于
-z 字符串长度为零
-n 字符串长度不为零
文件测试
运算符 描述
-e 文件/目录存在
-f 文件存在且为普通文件
-d 目录存在
-r 文件可读
-w 文件可写
-x 文件可执行

二、"[]" 和 "[[]]" 命令的区别

1. 语法差异
  • [][]与操作数间需有空格,否则语法错误。
    示例:if [ "$a" = "$b" ]; then
  • [[]]:语法要求宽松,建议加空格但非强制。
    示例:if [[ "$a" = "$b" ]]; then
2. 字符串比较差异
  • []:不支持模式匹配,特殊字符需转义。

  • [[]]:支持模式匹配(如*?),无需转义。
    示例:

    bash 复制代码
    str="abc123"
    if [[ $str = abc* ]]; then
        echo "字符串以 abc 开头"  # 输出此内容
    fi
3. 正则表达式匹配差异
  • []:不支持。

  • [[]]:支持=~进行正则匹配。
    示例:

    bash 复制代码
    str="abc123"
    if [[ $str =~ ^abc[0-9]+$ ]]; then
        echo "字符串符合正则表达式"  # 输出此内容
    fi
4. 逻辑操作差异
  • []:用-a(与)、-o(或)。
    示例:if [ $a -gt 5 -a $b -lt 30 ]; then
  • [[]]:用&&(与)、||(或)。
    示例:if [[ $a -gt 5 && $b -lt 30 ]]; then
5. 短路求值差异
  • []:不支持,无论前条件结果,后条件都会求值。
  • [[]]:支持,前条件可确定结果时,后条件不求值。
6. 兼容性差异
  • []:POSIX标准,兼容所有Shell。
  • [[]]:Bash扩展,仅支持Bash、Zsh等。

三、基础循环

1. for循环遍历列表

语法:

bash 复制代码
for 变量名 in 取值列表; do
    命令序列
done

示例:

bash 复制代码
# 遍历数字1-5
for i in {1..5}; do
    echo "数字: $i"
done
2. while循环基础用法

语法:

bash 复制代码
while [ 条件判断 ]; do
    # 条件为真执行
    命令序列
done

示例:

bash 复制代码
count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    count=$((count + 1))  # 变量自增
done
3. until循环语句

while相反,条件为假时执行循环。

语法:

bash 复制代码
until 条件; do
    命令序列
done

第四章 高级控制结构

一、case多分支选择结构

示例(根据输入输出星期):

bash 复制代码
#!/bin/bash

# 提示用户输入一个数字
echo "请输入一个1 - 7之间的数字:"
read day

# 使用case语句根据输入的数字输出对应的星期几
case $day in
    1)
        echo "星期一"
        ;;
    2)
        echo "星期二"
        ;;
    3)
        echo "星期三"
        ;;
    4)
        echo "星期四"
        ;;
    5)
        echo "星期五"
        ;;
    6)
        echo "星期六"
        ;;
    7)
        echo "星期日"
        ;;
    *)
        echo "输入无效,请输入1 - 7之间的数字。"
        ;;
esac

二、break循环语句

  • 作用:终止循环,break [n]可终止n层循环。
    示例(for循环中使用):
bash 复制代码
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break  # 遇到5终止循环
    fi
    echo $i
done
echo "Loop finished."  # 输出:1 2 3 4 + Loop finished.

示例(while循环中使用):

bash 复制代码
count=1
while [ $count -le 10 ]; do
    if [ $count -eq 3 ]; then
        break  # 遇到3终止循环
    fi
    echo $count
    ((count++))
done
echo "Loop finished."  # 输出:1 2 + Loop finished.

三、continue循环语句

  • 作用:跳过当前循环剩余部分,直接进入下一次循环。
    示例:
bash 复制代码
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        continue  # 遇到5跳过后续echo
    fi
    echo $i
done  # 输出:1 2 3 4 6 7 8 9 10

四、shell退出码($?)

  • 规则:命令执行成功返回0,失败返回非0
  • 查看方式:$?获取上一命令的退出码。
    示例:
bash 复制代码
grep "pattern" file.txt
if [ $? -eq 0 ]; then
    echo "找到了匹配的内容。"
else
    echo "未找到匹配的内容或者出现错误。"
fi

第五章 函数和模块化

一、函数定义和参数传递(1−1-1−n的用法)

  • 函数内通过$1-$n获取参数($1第一个参数,$n第n个参数)。
    示例:
bash 复制代码
# 定义函数(计算两数之和)
add() {
    echo $(( $1 + $2 ))  # $1第一个参数,$2第二个参数
}

# 调用函数(传递参数3和5)
result=$(add 3 5)
echo $result  # 输出:8

二、返回值处理(return与输出结果的区别)

  • return:返回状态码(0成功,非0失败),仅支持整数。
    示例:
bash 复制代码
function check_number() {
    if [ $1 -gt 10 ]; then
        return 0  # 成功
    else
        return 1  # 失败
    fi
}

check_number 15
echo "函数返回状态码: $?"  # 输出:0
  • 输出结果:通过echo输出计算结果,外部用$(函数名)捕获。
    示例:
bash 复制代码
function add() {
    echo $(( $1 + $2 ))  # 输出结果
}

result=$(add 2 3)
echo $result  # 输出:5

三、局部变量和作用域(local关键字)

  • local:函数内定义局部变量,仅函数内可见。
    示例:
bash 复制代码
# 定义函数
function test_function {
    local var="I am a local variable"  # 局部变量
    echo $var
}

# 调用函数
test_function  # 输出:I am a local variable
# 函数外部访问
echo $var  # 输出空

第六章 输入输出处理

一、shell脚本中的数据流

数据流 编号 默认来源/目的地
标准输入(stdin) 0 键盘
标准输出(stdout) 1 屏幕
标准错误(stderr) 2 屏幕

二、重定向技巧(>,>>,2>,&>)

1. > 覆盖重定向

覆盖写入文件,示例:

bash 复制代码
# 将ls结果覆盖写入file_list.txt
ls -l > file_list.txt
2. >> 追加重定向

在文件末尾追加,示例:

bash 复制代码
# 将日期追加写入log.txt
date >> log.txt
3. 2> 错误输出重定向

将错误信息覆盖写入文件,示例:

bash 复制代码
# 错误信息写入error.log
cat no_such_file.txt 2> error.log
4. &> 合并输出重定向

合并标准输出和错误,覆盖写入文件,示例:

bash 复制代码
# 所有输出写入all.log
bash script.sh &> all.log
5. 黑洞文件(/dev/null)

丢弃输出,示例:

bash 复制代码
# 丢弃所有输出
command &> /dev/null

三、管道的高级应用(多命令组合)

  • 管道(|):前一命令的stdout作为后一命令的stdin。
    示例(筛选并排序文件大小):
bash 复制代码
du -sh /var/log/* | grep -E "[0-9]+M" | sort -hr

四、用户交互:read -p "提示" var

  • read -p "提示" var:显示提示并读取输入到变量。
  • 常用选项:
    • -t 秒数:超时时间
    • -s:输入不回显(密码)
    • -n 字符数:读取指定字符数后结束
      示例:
bash 复制代码
# 基础交互
read -p "请输入姓名:" name
echo "你好,$name!"

# 输入密码(不回显)
read -s -p "请输入密码:" pwd
echo -e "\n密码已接收"

第七章 字符串与数组

一、数组

1. 数组的定义

示例:

bash 复制代码
# 数组定义
arr=(a b c d)

# 访问数组元素(索引从0开始)
echo ${arr[0]}  # 输出:a
echo ${arr[2]}  # 输出:c

# 访问所有元素
echo ${arr[@]}  # 输出:a b c d
echo ${arr[*]}  # 输出:a b c d

# 获取数组长度
echo ${#arr[@]}  # 输出:4
2. 数组的遍历方式
  • 方式1:for循环遍历
bash 复制代码
for item in "${arr[@]}"; do
    echo $item
done
  • 方式2:按索引遍历
bash 复制代码
for ((i=0; i<${#arr[@]}; i++)); do
    echo "索引 $i: ${arr[$i]}"
done
3. 数组的扩展和修改
bash 复制代码
# 添加元素
arr+=(e f)  # 数组变为(a b c d e f)

# 修改元素
arr[1]=new  # 数组变为(a new c d e f)

# 删除元素
unset arr[2]  # 删除索引2的元素

二、字符串模式匹配与截取

1. 字符串长度

示例:

bash 复制代码
str="hello world"
echo ${#str}  # 输出:11
2. # 操作符号(从左侧截取)
bash 复制代码
path="/usr/local/bin/bash"

# 移除最短匹配的前缀
echo ${path#/*/}  # 输出:local/bin/bash

# 移除最长匹配的前缀
echo ${path##/*/}  # 输出:bash
3. % 操作符号(从右侧截取)
bash 复制代码
file="document.txt.bak"

# 移除最短匹配的后缀
echo ${file%.*}  # 输出:document.txt

# 移除最长匹配的后缀
echo ${file%%.*}  # 输出:document
4. 字符串切片
bash 复制代码
str="abcdefgh"
echo ${str:2:3}  # 输出:cde(索引2开始,截取3个字符)
echo ${str:4}    # 输出:efgh(索引4开始,截取到末尾)

实践任务

任务1:开发文件类型统计脚本(分类统计目录下各类文件数量)
bash 复制代码
#!/bin/bash

# 检查参数
if [ $# -ne 1 ]; then
    echo "使用方法: $0 <目录路径>"
    exit 1
fi

dir="$1"
if [ ! -d "$dir" ]; then
    echo "错误: $dir 不是有效的目录"
    exit 1
fi

# 声明关联数组
declare -A type_count

# 遍历目录
for file in "$dir"/*; do
    if [ -f "$file" ]; then
        filename=$(basename "$file")
        # 提取后缀
        if [[ "$filename" == *.* ]]; then
            ext="${filename##*.}"
            ext=$(echo "$ext" | tr 'A-Z' 'a-z')  # 转为小写
        else
            ext="无扩展名"
        fi
        ((type_count["$ext"]++))
    fi
done

# 输出结果
echo "目录 $dir 中的文件类型统计:"
echo "=========================="
for ext in "${!type_count[@]}"; do
    echo "$ext: ${type_count[$ext]}"
done | sort -k2nr
任务2:编写用户管理系统(通过函数实现增删查功能)
bash 复制代码
#!/bin/bash

# 声明关联数组
declare -A users

# 加载初始用户
load_users() {
    users["admin"]="管理员,admin@example.com,1001"
    users["guest"]="访客,guest@example.com,1002"
}

# 显示所有用户
list_users() {
    echo "==================== 用户列表 ===================="
    echo "用户名 | 姓名 | 邮箱 | UID"
    echo "-------------------------------------------------"
    for username in "${!users[@]}"; do
        IFS=',' read -r name email uid <<< "${users[$username]}"
        echo "$username | $name | $email | $uid"
    done
    echo "================================================="
}

# 添加用户
add_user() {
    read -p "请输入用户名: " username
    if [ -n "${users[$username]}" ]; then
        echo "错误: 用户 $username 已存在"
        return 1
    fi
    read -p "请输入姓名: " name
    read -p "请输入邮箱: " email
    read -p "请输入UID: " uid
    users["$username"]="$name,$email,$uid"
    echo "用户 $username 添加成功"
}

# 删除用户
delete_user() {
    read -p "请输入要删除的用户名: " username
    if [ -z "${users[$username]}" ]; then
        echo "错误: 用户 $username 不存在"
        return 1
    fi
    unset users["$username"]
    echo "用户 $username 已删除"
}

# 查询用户
search_user() {
    read -p "请输入要查询的用户名: " username
    if [ -z "${users[$username]}" ]; then
        echo "用户 $username 不存在"
        return 1
    fi
    IFS=',' read -r name email uid <<< "${users[$username]}"
    echo "==================== 用户详情 ===================="
    echo "用户名: $username"
    echo "姓名: $name"
    echo "邮箱: $email"
    echo "UID: $uid"
    echo "================================================="
}

# 显示菜单
show_menu() {
    echo "=============== 用户管理系统 ==============="
    echo "1. 显示所有用户"
    echo "2. 添加用户"
    echo "3. 删除用户"
    echo "4. 查询用户"
    echo "5. 退出"
    echo "============================================"
    read -p "请选择操作 (1-5): " choice
}

# 主程序
main() {
    load_users
    while true; do
        show_menu
        case $choice in
            1) list_users ;;
            2) add_user ;;
            3) delete_user ;;
            4) search_user ;;
            5) 
                echo "谢谢使用,再见!"
                exit 0 
                ;;
            *) echo "无效的选择,请重试" ;;
        esac
        read -p "按回车键继续..."
        clear
    done
}

# 启动程序
main
相关推荐
初学者_xuan2 个月前
零基础Linux操作基础小白快速掌握Shell脚本bash的配置文件
linux·运维·bash·shell脚本
朱包林4 个月前
day27-shell编程(自动化)
linux·运维·服务器·网络·shell脚本
jjkkzzzz6 个月前
Linux之shell脚本
linux·shell·shell脚本
Zfox_6 个月前
【Shell 脚本入门】轻松上手的实战指南
linux·服务器·运维开发·shell脚本
阳洞洞7 个月前
在shell脚本内部获取该脚本所在目录的绝对路径
linux·shell脚本
浅安的邂逅7 个月前
Linux shell脚本-概述、语法定义、自定义变量、环境变量、预设变量、变量的特殊用法(转义字符、单双引号、大小括号)的验证
linux·c语言·bash·shell脚本
Ronin-Lotus9 个月前
上位机知识篇---Linux的shell脚本&搜索、查找、管道
linux·笔记·学习·shell脚本·管道·搜索·查找
高hongyuan1 年前
shell脚本一键部署nginx
运维·服务器·nginx·shell脚本
954L1 年前
Mysql8异地定时自动备份
mysql·安全·备份·shell脚本·定时任务·异地灾备