Linux Shell 脚本从零到能用:一个新手的一天学习总结

一、Shell 是什么?

简单理解:

  • Shell 是 Linux 的"命令解释器",你敲的命令它帮你传给系统执行
  • Shell 脚本 是把一堆命令写到文件里,让它自动按顺序跑

常见的 Shell 类型:

bash 复制代码
# 查看当前系统用的什么 Shell
echo $SHELL
# 输出:/bin/bash

# 查看系统有哪些 Shell
cat /etc/shells

目前最常用的是 Bash(Bourne Again Shell),大多数 Linux 发行版默认就是它。


二、第一个脚本

创建一个文件 hello.sh

bash 复制代码
#!/bin/bash
# 这是我的第一个 Shell 脚本

echo "Hello, World!"
echo "当前时间是: $(date)"
echo "当前用户是: $(whoami)"

运行方式:

bash 复制代码
# 方式一:给执行权限
chmod +x hello.sh
./hello.sh

# 方式二:用 bash 直接执行
bash hello.sh

注意#!/bin/bash 叫做 Shebang,告诉系统用哪个解释器来跑这个脚本。写脚本第一行最好都加上。


三、变量

定义变量

bash 复制代码
# 定义变量(等号两边不能有空格!)
name="Linux"
version=22

# 使用变量
echo "学习 $name, 版本 $version"
echo "学习 ${name}, 版本 ${version}"  # 推荐用花括号,更清晰

踩坑点 :等号两边不能加空格,name = "Linux" 会报错。

变量类型

bash 复制代码
# 字符串
str="hello"

# 字符串拼接
greeting="Hello, ${str}!"
echo $greeting

# 字符串长度
echo ${#str}

# 只读变量
readonly PI=3.14
# PI=3.15  # 这行会报错,只读变量不能修改

# 删除变量(只读变量不能删除)
unset str

环境变量 vs 局部变量

bash 复制代码
# 局部变量:只在当前 Shell 有效
local_var="我在局部"

# 环境变量:子进程也能访问
export GLOBAL_VAR="我是全局的"

# 查看所有环境变量
env
# 或
printenv

四、字符串操作

bash 复制代码
str="Hello Shell World"

# 单引号:原样输出,不解析变量
echo 'Hello $str'    # 输出: Hello $str

# 双引号:会解析变量
echo "Hello $str"    # 输出: Hello Hello Shell World

# 字符串切片
echo ${str:0:5}      # 输出: Hello(从位置0开始,取5个字符)
echo ${str:6}        # 输出: Shell World(从位置6到结尾)

# 字符串替换
echo ${str/Shell/Bash}  # 输出: Hello Bash World(替换第一个匹配)
echo ${str//l/L}        # 输出: HeLLo SheLL WorLd(替换所有匹配)

# 字符串长度
echo ${#str}         # 输出: 17

五、数组

bash 复制代码
# 定义数组
fruits=("apple" "banana" "cherry" "date")

# 访问单个元素(下标从0开始)
echo ${fruits[0]}    # 输出: apple
echo ${fruits[2]}    # 输出: cherry

# 所有元素
echo ${fruits[@]}    # 输出: apple banana cherry date

# 数组长度
echo ${#fruits[@]}   # 输出: 4

# 添加元素
fruits[4]="elderberry"

# 遍历数组
for fruit in "${fruits[@]}"; do
    echo "水果: $fruit"
done

六、条件判断

if 语句

bash 复制代码
#!/bin/bash

age=20

if [ $age -ge 18 ]; then
    echo "成年人"
elif [ $age -ge 12 ]; then
    echo "青少年"
else
    echo "儿童"
fi

注意[ 后面和 ] 前面都要有空格,这是最容易忘的地方。

比较运算符

bash 复制代码
# 数值比较
# -eq  等于
# -ne  不等于
# -gt  大于
# -ge  大于等于
# -lt  小于
# -le  小于等于

a=10
b=20

if [ $a -eq $b ]; then
    echo "相等"
fi

if [ $a -lt $b ]; then
    echo "a 小于 b"
fi

字符串比较

bash 复制代码
str1="hello"
str2="world"

# =  判断是否相等
# != 判断是否不等
# -z 判断字符串是否为空
# -n 判断字符串是否非空

if [ "$str1" = "$str2" ]; then
    echo "字符串相等"
fi

if [ -z "$str1" ]; then
    echo "字符串为空"
fi

文件判断

bash 复制代码
# -f  是否为普通文件
# -d  是否为目录
# -e  是否存在
# -r  是否可读
# -w  是否可写
# -x  是否可执行
# -s  文件是否非空

file="/etc/passwd"

if [ -f "$file" ]; then
    echo "$file 是一个普通文件"
fi

if [ -r "$file" ]; then
    echo "$file 可读"
fi

逻辑运算

bash 复制代码
age=25
score=85

# &&  逻辑与
if [ $age -ge 18 ] && [ $score -ge 60 ]; then
    echo "成年人且及格"
fi

# ||  逻辑或
if [ $age -lt 18 ] || [ $score -ge 90 ]; then
    echo "未成年或优秀"
fi

# !  逻辑非
if [ ! $age -lt 18 ]; then
    echo "不是未成年人"
fi

七、循环

for 循环

bash 复制代码
# 遍历列表
for fruit in apple banana cherry; do
    echo "水果: $fruit"
done

# C风格for循环
for ((i=1; i<=5; i++)); do
    echo "第 $i 次"
done

# 遍历文件
for file in /tmp/*.log; do
    echo "日志文件: $file"
done

# 遍历数字序列
for i in {1..5}; do
    echo "数字: $i"
done

# 步长
for i in {0..10..2}; do
    echo "偶数: $i"  # 输出 0 2 4 6 8 10
done

while 循环

bash 复制代码
# 基础while
count=1
while [ $count -le 5 ]; do
    echo "第 $count 次"
    count=$((count + 1))
done

# 读取文件每一行
while IFS= read -r line; do
    echo "内容: $line"
done < /etc/passwd

# 无限循环
while true; do
    echo "按 Ctrl+C 退出"
    sleep 1
done

until 循环

bash 复制代码
# until:条件为假时执行,条件为真时停止(和while相反)
count=1
until [ $count -gt 5 ]; do
    echo "第 $count 次"
    count=$((count + 1))
done

break 和 continue

bash 复制代码
# break:跳出整个循环
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break
    fi
    echo $i
done
# 输出 1 2 3 4

# continue:跳过本次循环
for i in {1..5}; do
    if [ $i -eq 3 ]; then
        continue
    fi
    echo $i
done
# 输出 1 2 4 5

八、函数

定义和调用

bash 复制代码
# 方式一
greet() {
    echo "Hello, $1!"
}

# 方式二
function say_hi {
    echo "Hi, $1!"
}

# 调用函数
greet "Linux"     # 输出: Hello, Linux!
say_hi "Shell"    # 输出: Hi, Shell!

参数和返回值

bash 复制代码
# 函数参数
add() {
    local sum=$(($1 + $2))  # local 定义局部变量
    return $sum
}

# 调用并获取返回值
add 3 5
echo "3 + 5 = $?"  # $? 获取上一个命令的返回值

# 更推荐用 echo 输出,用 $() 获取
add() {
    echo $(($1 + $2))
}

result=$(add 3 5)
echo "3 + 5 = $result"

实用函数示例

bash 复制代码
# 检查命令是否存在
check_command() {
    if command -v $1 &> /dev/null; then
        echo "$1 已安装"
        return 0
    else
        echo "$1 未安装"
        return 1
    fi
}

check_command docker
check_command kubectl

# 带颜色的输出
red() {
    echo -e "\033[31m$1\033[0m"
}

green() {
    echo -e "\033[32m$1\033[0m"
}

yellow() {
    echo -e "\033[33m$1\033[0m"
}

green "操作成功"
red "操作失败"
yellow "警告信息"

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log "开始执行任务"
log "任务完成"

九、输入输出

读取用户输入

bash 复制代码
# 基础读取
echo "请输入你的名字:"
read name
echo "你好, $name!"

# 带提示的读取
read -p "请输入年龄: " age
echo "你 $age 岁了"

# 静默输入(比如密码)
read -sp "请输入密码: " password
echo
echo "密码长度: ${#password}"

# 读取多个变量
read -p "输入姓名和年龄: " name age
echo "$name 今年 $age 岁"

输出重定向

bash 复制代码
# 标准输出重定向
echo "hello" > output.txt    # 覆盖写入
echo "world" >> output.txt   # 追加写入

# 错误输出重定向
ls /nonexistent 2> error.log

# 同时重定向标准输出和错误输出
command > output.txt 2>&1
# 或者更简洁
command &> output.txt

# 丢弃输出
command > /dev/null 2>&1

管道

bash 复制代码
# 管道:把前一个命令的输出作为后一个命令的输入
ps aux | grep nginx
cat /etc/passwd | wc -l
ls -la | sort -k5 -n  # 按文件大小排序

# 多级管道
ps aux | grep nginx | grep -v grep | awk '{print $2}'

十、实战脚本

脚本一:系统信息查看

bash 复制代码
#!/bin/bash

echo "========== 系统信息 =========="
echo "主机名: $(hostname)"
echo "系统版本: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
echo "内核版本: $(uname -r)"
echo "CPU: $(grep 'model name' /proc/cpuinfo | head -1 | cut -d':' -f2)"
echo "内存: $(free -h | grep Mem | awk '{print $2}')"
echo "磁盘使用: $(df -h / | tail -1 | awk '{print $5}')"
echo "当前用户: $(whoami)"
echo "系统运行时间: $(uptime -p)"
echo "=============================="

脚本二:批量创建用户

bash 复制代码
#!/bin/bash

# 从文件读取用户名,批量创建
USER_FILE="users.txt"

if [ ! -f "$USER_FILE" ]; then
    echo "错误: $USER_FILE 不存在"
    exit 1
fi

while IFS= read -r username; do
    if [ -z "$username" ]; then
        continue
    fi
    
    if id "$username" &>/dev/null; then
        echo "用户 $username 已存在,跳过"
    else
        useradd -m "$username"
        echo "用户 $username 创建成功"
    fi
done < "$USER_FILE"

脚本三:日志清理

bash 复制代码
#!/bin/bash

# 清理指定天数前的日志文件
LOG_DIR="/var/log/myapp"
DAYS=7

if [ ! -d "$LOG_DIR" ]; then
    echo "目录 $LOG_DIR 不存在"
    exit 1
fi

echo "清理 $DAYS 天前的日志..."

find "$LOG_DIR" -name "*.log" -type f -mtime +$DAYS -exec rm -f {} \;

echo "清理完成"

脚本四:服务健康检查

bash 复制代码
#!/bin/bash

# 检查服务是否运行
check_service() {
    local service_name=$1
    
    if systemctl is-active --quiet "$service_name"; then
        echo "[✓] $service_name 运行中"
        return 0
    else
        echo "[✗] $service_name 未运行"
        return 1
    fi
}

# 检查端口是否监听
check_port() {
    local port=$1
    local service_name=$2
    
    if ss -tlnp | grep -q ":$port"; then
        echo "[✓] $service_name (端口 $port) 监听中"
        return 0
    else
        echo "[✗] $service_name (端口 $port) 未监听"
        return 1
    fi
}

echo "========== 服务状态检查 =========="
check_service nginx
check_service mysql
check_service redis
check_port 80 "HTTP"
check_port 443 "HTTPS"
check_port 3306 "MySQL"
check_port 6379 "Redis"
echo "=================================="

脚本五:自动备份

bash 复制代码
#!/bin/bash

# 配置
BACKUP_DIR="/backup"
SOURCE_DIR="/var/www/html"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
KEEP_DAYS=30

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 执行备份
echo "开始备份 $SOURCE_DIR ..."
tar -czf "${BACKUP_DIR}/${BACKUP_FILE}" "$SOURCE_DIR"

if [ $? -eq 0 ]; then
    echo "备份成功: ${BACKUP_FILE}"
else
    echo "备份失败"
    exit 1
fi

# 清理旧备份
echo "清理 ${KEEP_DAYS} 天前的备份..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$KEEP_DAYS -delete

echo "备份完成"

十一、常用技巧

脚本调试

bash 复制代码
# 方式一:使用 -x 参数(显示执行的每一行)
bash -x script.sh

# 方式二:在脚本中开启调试
#!/bin/bash
set -x  # 开启调试
# ... 脚本内容 ...
set +x  # 关闭调试

# 方式三:使用 -e 参数(遇到错误立即退出)
#!/bin/bash
set -e  # 遇到错误退出
set -u  # 使用未定义变量时报错
set -o pipefail  # 管道中任何命令失败则整体失败

常用内置变量

bash 复制代码
$0      # 脚本名称
$1-$9   # 第1-9个参数
$#      # 参数个数
$@      # 所有参数(作为独立字符串)
$*      # 所有参数(作为单个字符串)
$$      # 当前脚本的PID
$?      # 上一个命令的返回值
$!      # 后台运行的最后一个进程的PID

参数处理

bash 复制代码
#!/bin/bash

# 简单的参数处理
if [ $# -lt 1 ]; then
    echo "用法: $0 <参数>"
    exit 1
fi

echo "参数1: $1"
echo "参数个数: $#"
echo "所有参数: $@"

# 遍历所有参数
for arg in "$@"; do
    echo "参数: $arg"
done

条件表达式简写

bash 复制代码
# 传统写法
if [ -f "/etc/passwd" ]; then
    echo "文件存在"
fi

# 简写(双括号更现代)
if [[ -f "/etc/passwd" ]]; then
    echo "文件存在"
fi

# 三元运算风格
[[ -f "/etc/passwd" ]] && echo "存在" || echo "不存在"

# 默认值
name=${1:-"默认名称"}
echo "名字: $name"

十二、学习心得

今天学完 Shell 脚本,有几个感受:

  1. Shell 不难,但细节很多 :比如 [ 后面要有空格,变量赋值不能有空格,这些小细节不注意就会报错

  2. 实践比看书重要:看文档觉得自己会了,一写就出错。今天写了十几个小脚本,踩了不少坑,但印象很深

  3. Shell 是运维的基本功:之前觉得会敲命令就行,现在发现,能把重复性工作写成脚本,才是真正的效率提升

  4. 学会看报错信息:Shell 的报错信息有时候很不直观,但仔细看 usually 能找到问题所在


相关推荐
AlfredZhao17 小时前
Docker 容器时区不对,`timedatectl` 不存在怎么办?
linux·timezone
zzzzzz3102 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode2 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
A小辣椒4 天前
TShark:Wireshark CLI 功能
linux
A小辣椒4 天前
TShark:基础知识
linux
AlfredZhao4 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao5 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334665 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪5 天前
linux 拷贝文件或目录到指定的位置
linux