第一部分:Shell脚本概述

1.1 什么是Shell?

Shell是一个命令行解释器,它是用户与Linux内核之间的桥梁。当你在终端输入命令时,Shell负责解析这些命令,并将其传递给操作系统内核执行。

Linux系统中常见的Shell有:

  • bash(Bourne Again Shell):Linux默认Shell,功能强大,本文基于bash。

  • sh(Bourne Shell):Unix早期Shell,bash兼容sh。

  • zsh:功能更丰富的Shell,macOS Catalina开始默认使用。

  • csh/tcsh:类C语法Shell。

查看当前系统使用的Shell:

bash

echo $SHELL
# 输出示例:/bin/bash

1.2 Shell脚本的作用

Shell脚本是将一系列Shell命令写入文件,通过解释器执行,实现自动化任务。它的主要作用包括:

应用场景 示例
系统管理与维护 日志清理、备份、监控脚本
软件部署 一键安装LNMP环境
批量处理 批量重命名文件、批量创建用户
任务自动化 定时备份数据库、自动同步文件
工具开发 定制化的运维小工具

第一个Shell脚本

bash

#!/bin/bash
# 这是我的第一个脚本
echo "Hello, World!"
echo "当前时间: $(date)"
  • #!/bin/bashShebang,指定使用哪个解释器执行脚本。

  • #:注释行,解释脚本用途。

  • echo:输出文本。

执行脚本:

bash

chmod +x first.sh
./first.sh

1.3 重定向与管道操作——数据流动的艺术

在Shell中,每个命令都有三个默认的I/O流:

  • 标准输入(stdin):文件描述符0,默认从键盘读取。

  • 标准输出(stdout):文件描述符1,默认输出到屏幕。

  • 标准错误(stderr):文件描述符2,默认输出到屏幕。

重定向就是改变这些默认的输入输出源。

1.3.1 输出重定向
操作符 作用 示例
> 将标准输出重定向到文件(覆盖) ls > filelist.txt
>> 将标准输出重定向到文件(追加) echo "new line" >> filelist.txt
2> 将标准错误重定向到文件 ls /nonexist 2> error.log
&> 将标准输出和标准错误都重定向 ls /home /nonexist &> all.log
2>&1 将标准错误合并到标准输出 ls /home /nonexist > all.log 2>&1

实战示例

bash

# 将命令输出保存到文件
date > now.txt

# 追加内容
echo "Hello" >> now.txt

# 分别保存正确和错误输出
find / -name "*.conf" > find_ok.txt 2> find_err.txt

# 丢弃错误输出(常见技巧)
find / -name "*.conf" 2> /dev/null
1.3.2 输入重定向
操作符 作用 示例
< 从文件读取输入 wc -l < /etc/passwd
<< Here Document,从当前脚本读取多行输入直到指定分隔符 cat << EOF > test.txt

Here Document示例

bash

#!/bin/bash
# 使用Here Document生成配置文件
cat << EOF > /tmp/myconfig.conf
# This is a config file
username=admin
password=123456
port=8080
EOF
1.3.3 管道操作

管道(|) 将一个命令的标准输出作为下一个命令的标准输入,实现命令间的数据传递。这是Shell最强大的特性之一。

基本语法

bash

command1 | command2 | command3

经典示例

bash

# 查看进程,筛选包含nginx的行,并统计行数
ps aux | grep nginx | wc -l

# 查看日志,提取IP,排序并去重
cat access.log | awk '{print $1}' | sort | uniq -c | sort -nr

# 找出当前目录最大的5个文件
du -sh * | sort -rh | head -5

# 使用xargs将前一个命令的输出作为后一个命令的参数
find . -name "*.log" | xargs rm -f

管道与重定向的区别

  • 管道:连接命令,传递数据流。

  • 重定向:连接命令和文件,改变输入输出源。


第二部分:Shell变量深度解析

变量是脚本中存储数据的容器。理解变量类型和作用域是编写健壮脚本的关键。

2.1 变量的作用

变量让我们能够:

  • 存储临时数据:如用户输入、命令结果。

  • 提高脚本可维护性:修改一处变量定义,多处生效。

  • 传递参数:在脚本内外、函数之间传递数据。

2.2 变量的命名规则

  • 由字母、数字、下划线组成,不能以数字开头

  • 区分大小写:nameNAME是不同的变量。

  • 避免使用Shell关键字(如ifthenelsefi等)。

  • 惯例:环境变量全大写自定义变量小写或驼峰

bash

# 正确示例
name="John"
file_name="data.txt"
MAX_COUNT=100
_db_host="localhost"

# 错误示例
1st_name="Tom"    # 数字开头
user-name="joe"   # 包含连字符(会被解释为减法)

2.3 变量的定义和使用

2.3.1 基本语法

bash

# 定义变量(等号两边不能有空格)
变量名=变量值

# 引用变量
$变量名
${变量名}  # 推荐使用花括号,避免歧义

示例

bash

name="Alice"
echo $name
echo "My name is ${name}"   # 输出:My name is Alice
2.3.2 命令替换

将命令的执行结果赋值给变量:

bash

# 旧语法:反引号
current_date=`date`

# 新语法:$(),推荐,支持嵌套
current_date=$(date)
files_count=$(ls -l | wc -l)

echo "今天日期: $current_date"
echo "文件数量: $files_count"
2.3.3 变量的作用范围

默认定义的变量是局部变量,只在当前Shell进程有效,不会传递给子进程。

bash

# 定义局部变量
name="local"
# 启动子Shell
bash
echo $name   # 输出为空
exit

# 使用export导出为环境变量,可传递给子进程
export name="global"
bash
echo $name   # 输出:global

2.4 变量类型详解

Shell变量主要分为以下几类:

2.4.1 自定义变量(用户变量)

用户在当前Shell或脚本中定义的变量,默认是字符串类型。

高级赋值技巧

bash

# 直接赋值
a=100
b="hello world"

# 从命令结果赋值
c=$(pwd)

# 从其他变量赋值
d=$a

# 交互式赋值
read -p "请输入你的名字: " user_name
echo "你好, $user_name"

# 只读变量(常量)
readonly PI=3.14159
PI=3.14  # 报错:PI: readonly variable

# 删除变量
unset a
echo $a   # 输出为空
2.4.2 环境变量

环境变量是系统或用户设置的全局变量,影响Shell和所有子进程的行为。

常用环境变量

bash

echo $HOME      # 用户主目录
echo $PATH      # 命令搜索路径
echo $USER      # 当前用户名
echo $SHELL     # 当前Shell
echo $PWD       # 当前工作目录
echo $LANG      # 系统语言
echo $UID       # 当前用户ID

查看所有环境变量

bash

env          # 显示所有环境变量
set          # 显示所有变量(包括局部变量和环境变量)
export       # 显示所有已导出的环境变量

设置环境变量

bash

# 临时生效(当前Shell)
export MY_VAR="hello"

# 永久生效(写入配置文件)
echo 'export MY_VAR="hello"' >> ~/.bashrc
source ~/.bashrc
2.4.3 位置变量(命令行参数)

位置变量用于接收脚本或函数执行时传递的参数:

变量 含义
$0 脚本名称
$1$2... 第1、2...个参数
${10} 第10个参数(大于9需要花括号)
$# 参数个数
$* 所有参数(作为一个字符串)
$@ 所有参数(每个作为独立字符串)

示例脚本 args.sh

bash

#!/bin/bash
echo "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "参数个数: $#"
echo "所有参数: $*"
echo "所有参数: $@"

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

执行:

bash

./args.sh a b c d
# 输出:
# 脚本名称: ./args.sh
# 第一个参数: a
# 第二个参数: b
# 参数个数: 4
# 所有参数: a b c d
# 参数: a
# 参数: b
# 参数: c
# 参数: d

$*$@的区别

  • 不加引号时,两者行为相同。

  • 加双引号时:"$*"将所有参数合并为一个字符串,"$@"保持参数的独立性。

bash

# 测试脚本
set -- "a b" "c d"
echo "使用\$*:"
for i in $*; do echo $i; done
echo "使用\"\$*\":"
for i in "$*"; do echo $i; done
echo "使用\"\$@\":"
for i in "$@"; do echo $i; done
2.4.4 预定义变量(特殊变量)

Shell提供了一些特殊变量,用于获取脚本运行状态:

变量 含义
$? 上一个命令的退出状态码(0表示成功,非0表示失败)
$$ 当前Shell进程的PID
$! 最后一个后台进程的PID
$_ 上一个命令的最后一个参数

实战示例

bash

#!/bin/bash
# 演示预定义变量

echo "当前脚本的PID: $$"

# 后台执行命令
sleep 30 &
echo "最后一个后台进程的PID: $!"

# 测试命令执行状态
ls /etc/passwd > /dev/null 2>&1
echo "ls /etc/passwd 执行状态: $?"   # 0

ls /nonexist > /dev/null 2>&1
echo "ls /nonexist 执行状态: $?"     # 非0

# 获取上一个命令的最后一个参数
echo "之前命令的最后一个参数: $_"

常用退出码含义

  • 0:成功

  • 1:一般性错误

  • 2:Shell内建命令使用错误

  • 126:命令不可执行

  • 127:命令未找到

  • 128:无效退出参数

  • 128+n:信号n的致命错误

  • 130:Ctrl+C终止

2.5 变量高级技巧

2.5.1 变量默认值

在处理可能未定义的变量时,可以设置默认值:

语法 作用
${var:-default} 如果var未定义或为空,返回default,var不变
${var:=default} 如果var未定义或为空,将var设为default,并返回default
${var:+value} 如果var已定义且非空,返回value,否则返回空
${var:?message} 如果var未定义或为空,打印message并退出脚本

示例

bash

#!/bin/bash
# 变量默认值演示

name=""
echo "1: ${name:-张三}"   # 输出:张三(name仍为空)
echo "name: $name"        # 输出:name: 

echo "2: ${name:=李四}"   # 输出:李四(name被设为李四)
echo "name: $name"        # 输出:name: 李四

age=18
echo "3: ${age:+成年}"    # 输出:成年
echo "age: $age"          # 输出:age: 18

echo "4: ${not_exist:?变量未定义}"  # 报错并退出
2.5.2 变量字符串操作
语法 作用
${#var} 获取字符串长度
${var:offset} 从offset开始截取到结尾
${var:offset:length} 从offset开始截取length长度
${var#pattern} 从开头删除最短匹配
${var##pattern} 从开头删除最长匹配
${var%pattern} 从结尾删除最短匹配
${var%%pattern} 从结尾删除最长匹配
${var/old/new} 替换第一个匹配
${var//old/new} 替换所有匹配

示例

bash

file="/home/user/docs/report.txt"

echo "文件名: ${file##*/}"        # 输出:report.txt
echo "目录: ${file%/*}"           # 输出:/home/user/docs
echo "扩展名: ${file##*.}"        # 输出:txt
echo "无扩展名: ${file%.*}"       # 输出:/home/user/docs/report

text="hello world hello"
echo "长度: ${#text}"             # 输出:17
echo "替换: ${text/hello/hi}"     # 输出:hi world hello
echo "全部替换: ${text//hello/hi}" # 输出:hi world hi
2.5.3 变量类型声明

bash支持使用declaretypeset声明变量类型:

bash

declare -i num=10     # 整数类型
num="20"              # 自动转为整数
num="abc"             # 赋值为0(因为不是数字)

declare -r PI=3.14    # 只读变量(同readonly)

declare -a arr        # 数组类型
arr=(1 2 3 4 5)

declare -A map        # 关联数组(需要bash 4.0+)
map[name]="Tom"
map[age]=25

declare -x global_var # 导出为环境变量(同export)

declare -f            # 列出所有函数
declare -F            # 只列出函数名

数组操作示例

bash

# 索引数组
fruits=("apple" "banana" "orange")
echo ${fruits[0]}      # apple
echo ${fruits[@]}      # 所有元素
echo ${#fruits[@]}     # 数组长度

# 关联数组
declare -A user
user=([name]="Alice" [age]=30 [city]="Beijing")
echo ${user[name]}     # Alice
echo ${!user[@]}       # 输出所有键:name age city

第三部分:实战案例与最佳实践

3.1 实战:系统信息收集脚本

结合变量、管道和重定向,编写一个实用的系统信息收集脚本:

bash

#!/bin/bash
# 文件名:sysinfo.sh
# 用途:收集系统关键信息并输出报告

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # 无颜色

# 主机信息
HOSTNAME=$(hostname)
OS_VERSION=$(cat /etc/redhat-release 2>/dev/null || cat /etc/os-release | grep "^PRETTY_NAME" | cut -d= -f2 | tr -d '"')
KERNEL=$(uname -r)
UPTIME=$(uptime | awk '{print $3,$4}' | tr -d ',')

# 硬件信息
CPU_MODEL=$(lscpu | grep "Model name" | cut -d: -f2 | xargs)
CPU_CORES=$(nproc)
MEM_TOTAL=$(free -h | awk '/^Mem:/ {print $2}')
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}')

# 网络信息
IP_ADDR=$(ip -4 addr show | grep inet | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 | head -1)

# 输出报告
cat << EOF
========================================
       系统信息报告
========================================
主机名:     $HOSTNAME
操作系统:   $OS_VERSION
内核版本:   $KERNEL
运行时间:   $UPTIME
----------------------------------------
CPU型号:    $CPU_MODEL
CPU核心数:  $CPU_CORES
内存总量:   $MEM_TOTAL
根分区使用: $DISK_USAGE
----------------------------------------
IP地址:     $IP_ADDR
========================================
EOF

# 检查关键服务
echo -e "\n${GREEN}服务状态检查:${NC}"
for service in sshd httpd crond; do
    if systemctl is-active --quiet $service; then
        echo -e "$service: ${GREEN}运行中${NC}"
    else
        echo -e "$service: ${RED}未运行${NC}"
    fi
done

3.2 最佳实践:编写高质量Shell脚本

  1. 脚本头部规范

    bash

    #!/bin/bash
    # 文件名:script.sh
    # 作者:Your Name
    # 日期:2025-02-26
    # 描述:脚本的用途说明
    # 版本:1.0
  2. 使用set命令增强脚本健壮性

    bash

    set -e   # 遇到错误立即退出
    set -u   # 使用未定义变量时报错
    set -x   # 调试模式,显示执行的命令
    set -o pipefail  # 管道中任何命令失败都返回失败状态
    
    # 通常组合使用
    set -euo pipefail
  3. 变量使用规范

    • 始终使用${变量}形式,避免歧义。

    • 使用有意义的变量名。

    • 局部变量用local声明(函数内)。

    • 常量用readonly声明。

  4. 引号使用

    • 始终给变量加双引号,防止单词分割和路径名扩展。

    bash

    # 错误示例
    file="my document.txt"
    rm $file   # 实际执行:rm my document.txt(删除了两个文件!)
    
    # 正确示例
    rm "$file"
  5. 函数化

    • 将重复代码封装为函数。

    • 函数应具有单一职责。

  6. 错误处理

    bash

    command || { echo "命令失败"; exit 1; }
    
    # 或
    if ! command; then
        echo "错误信息" >&2
        exit 1
    fi
  7. 注释充分

    • 解释复杂的逻辑。

    • 说明输入输出格式。

    • 注明特殊技巧的原因。

3.3 常见陷阱与解决方法

陷阱 示例 解决方法
等号两边空格 name = "Tom" name="Tom"
忘记$引用 name="Tom"; echo name echo $name
未加引号导致分词 file="a b.txt"; rm $file rm "$file"
使用未定义变量 rm -rf /${dir}/(dir未定义则删根) set -u,或使用默认值${dir:-}
命令替换中的换行符 files=$(ls) 使用数组files=($(ls)),但更推荐find
管道丢失退出状态 command1 | command2 set -o pipefail
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐