首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Shell编程从入门到放弃?别急,这份教程让你轻松上手!

Shell编程从入门到放弃?别急,这份教程让你轻松上手!

作者头像
悠悠12138
发布2025-09-29 14:54:03
发布2025-09-29 14:54:03
29200
代码可运行
举报
运行总次数:0
代码可运行
Bash Logo
Bash Logo

发现一个通俗易懂,风趣幽默人工智能学习网站:https://www.captainai.net/jiangyu1013/,建议收藏!!!

说起Shell编程,我想很多朋友都有过这样的经历:看着那些黑乎乎的命令行界面,心里就发怵。今天我就来和大家分享一下Shell编程的那些事儿,从最基础的概念开始,一步步带你入门。不求你看完就能成为Shell大神,但至少能让你不再害怕那个黑漆漆的终端界面。

什么是Shell?为什么要学它?

Shell(也称为壳层)在计算机科学中指“为用户提供用户界面”的软件,通常指的是命令行界面的解析器。一般来说,这个词是指操作系统中提供访问内核所提供之服务的程序。Shell也用于泛指所有为用户提供操作界面的程序,也就是程序和用户交互的层面。因此与之相对的是内核(英语:Kernel),内核不提供和用户的交互功能。

Shell说白了就是一个命令解释器,它就像是你和操作系统之间的翻译官。你用人话告诉Shell要做什么,它就翻译成机器能理解的指令去执行。

在Linux系统中,最常见的Shell就是bash(Bourne Again Shell)。当然还有其他的,比如zsh、fish什么的,但是bash基本上是标配,学会了bash,其他的也就触类旁通了。

为什么要学Shell呢?这个问题我被问过无数次。简单来说,如果你要在Linux环境下工作,不会Shell就像是哑巴一样,很多事情都做不了。比如说批量处理文件、自动化部署、系统监控等等,这些都离不开Shell脚本。

我记得有一次,项目要求把几百个日志文件按照日期重新命名,如果手动操作的话,估计要搞一整天。但是用Shell脚本,几分钟就搞定了。这就是Shell的魅力所在。

更详细的学习地址:https://www.bookstack.cn/books/open-shell-book

image-20250908223947089
image-20250908223947089

image-20250908223947089

基础语法和变量

变量定义和使用

Shell中的变量定义很简单,直接写就行了:

代码语言:javascript
代码运行次数:0
运行
复制
name="张三"
age=25

注意这里有个坑,等号两边不能有空格!这个我当初也踩过,写成了name = "张三",结果报错半天才发现问题。

使用变量的时候要加上美元符号:

代码语言:javascript
代码运行次数:0
运行
复制
echo $name
echo "我的名字是$name,今年$age岁"

还有一种更安全的写法,用花括号把变量名括起来:

代码语言:javascript
代码运行次数:0
运行
复制
echo "我的名字是${name},今年${age}岁"

这样做的好处是避免变量名和其他字符混在一起造成歧义。

特殊变量

Shell里面有一些特殊的变量,这些是系统预定义的:

  • $0 - 脚本名称
  • 1, 2,
  • $# - 参数个数
  • $? - 上一个命令的退出状态
  • $$ - 当前进程ID

举个例子,如果你有个脚本叫test.sh,然后这样运行:

代码语言:javascript
代码运行次数:0
运行
复制
./test.sh hello world

那么在脚本里面:

  • $0 就是 ./test.sh
  • $1 就是 hello
  • $2 就是 world
  • $# 就是 2

这些特殊变量在写脚本的时候特别有用,特别是处理命令行参数的时候。

条件判断和流程控制

if语句

Shell的if语句语法稍微有点特别:

代码语言:javascript
代码运行次数:0
运行
复制
if [ condition ]; then
    # 执行的命令
elif [ another_condition ]; then
    # 另一个条件
else
    # 默认执行的命令
fi

注意那个方括号,两边都要有空格!这又是一个容易踩的坑。

常用的条件判断:

代码语言:javascript
代码运行次数:0
运行
复制
# 数字比较
if [ $age -gt 18 ]; then
    echo "成年了"
fi

# 字符串比较
if [ "$name" = "张三" ]; then
    echo "你好,张三"
fi

# 文件判断
if [ -f "/etc/passwd" ]; then
    echo "文件存在"
fi

数字比较的操作符有:-eq(等于)、-ne(不等于)、-gt(大于)、-lt(小于)、-ge(大于等于)、-le(小于等于)。

文件判断的操作符也很多:-f(是否为文件)、-d(是否为目录)、-e(是否存在)、-r(是否可读)等等。

case语句

当需要判断多个条件的时候,case语句比if更清晰:

代码语言:javascript
代码运行次数:0
运行
复制
case $1 in
    "start")
        echo "启动服务"
        ;;
    "stop")
        echo "停止服务"
        ;;
    "restart")
        echo "重启服务"
        ;;
    *)
        echo "未知参数"
        ;;
esac

这种写法在处理服务脚本的时候特别常见。

循环结构

for循环

for循环有几种写法,最常用的是这样:

代码语言:javascript
代码运行次数:0
运行
复制
# 遍历列表
for item in apple banana orange; do
    echo "水果:$item"
done

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

# C风格的for循环
for ((i=1; i<=10; i++)); do
    echo "数字:$i"
done

我经常用for循环来批量处理文件,比如批量重命名、批量转换格式什么的。

while循环

while循环适合在不知道循环次数的情况下使用:

代码语言:javascript
代码运行次数:0
运行
复制
count=1
while [ $count -le 5 ]; do
    echo "第${count}次循环"
    count=$((count + 1))
done

还有一种常见的用法是读取文件内容:

代码语言:javascript
代码运行次数:0
运行
复制
while read line; do
    echo "读到一行:$line"
done < /etc/passwd

这个在处理配置文件或者日志文件的时候特别有用。

函数的使用

Shell也支持函数,语法是这样的:

代码语言:javascript
代码运行次数:0
运行
复制
function hello() {
    echo "Hello, $1!"
}

# 或者更简单的写法
hello() {
    echo "Hello, $1!"
}

# 调用函数
hello "World"

函数可以有返回值,但是只能返回数字(0-255):

代码语言:javascript
代码运行次数:0
运行
复制
check_file() {
    if [ -f "$1" ]; then
        return 0  # 成功
    else
        return 1  # 失败
    fi
}

if check_file "/etc/passwd"; then
    echo "文件存在"
else
    echo "文件不存在"
fi

如果要返回字符串,可以用echo然后用命令替换来获取:

代码语言:javascript
代码运行次数:0
运行
复制
get_date() {
    echo $(date +%Y-%m-%d)
}

today=$(get_date)
echo "今天是:$today"

数组操作

Shell也支持数组,虽然功能比较简单:

代码语言:javascript
代码运行次数:0
运行
复制
# 定义数组
fruits=("apple" "banana" "orange")

# 或者这样定义
fruits[0]="apple"
fruits[1]="banana"
fruits[2]="orange"

# 访问数组元素
echo ${fruits[0]}  # 输出第一个元素
echo ${fruits[@]}  # 输出所有元素
echo ${#fruits[@]} # 输出数组长度

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

数组在处理批量数据的时候很有用,比如存储服务器列表、配置项等等。

字符串处理

Shell的字符串处理功能还是挺强大的:

代码语言:javascript
代码运行次数:0
运行
复制
str="Hello World"

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

# 字符串截取
echo ${str:0:5}    # 从位置0开始,取5个字符
echo ${str:6}      # 从位置6开始到结尾

# 字符串替换
echo ${str/World/Shell}  # 替换第一个匹配
echo ${str//l/L}         # 替换所有匹配

# 字符串删除
echo ${str#Hello }   # 从开头删除匹配的部分
echo ${str%World}    # 从结尾删除匹配的部分

这些操作在处理文件名、路径等场景下特别有用。

文件操作和I/O重定向

基本文件操作

代码语言:javascript
代码运行次数:0
运行
复制
# 创建文件
touch newfile.txt

# 复制文件
cp source.txt dest.txt

# 移动/重命名文件
mv oldname.txt newname.txt

# 删除文件
rm unwanted.txt

# 创建目录
mkdir newdir

# 删除目录
rmdir emptydir
rm -rf nonemptydir

I/O重定向

重定向是Shell的一个重要特性:

代码语言:javascript
代码运行次数:0
运行
复制
# 输出重定向
echo "Hello" > file.txt      # 覆盖写入
echo "World" >> file.txt     # 追加写入

# 输入重定向
sort < unsorted.txt

# 错误重定向
command 2> error.log         # 只重定向错误输出
command > output.log 2>&1    # 同时重定向标准输出和错误输出

# 管道
cat file.txt | grep "pattern" | sort

管道是个很强大的功能,可以把多个命令串联起来,前一个命令的输出作为后一个命令的输入。

实战案例

说了这么多理论,来看几个实际的例子。

系统监控脚本

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash

# 检查系统负载
check_load() {
    load=$(uptime | awk '{print $10}' | cut -d',' -f1)
    if (( $(echo "$load > 2.0" | bc -l) )); then
        echo "警告:系统负载过高 ($load)"
    fi
}

# 检查磁盘使用率
check_disk() {
    df -h | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{print $5 " " $1}' | while read output; do
        usage=$(echo $output | awk '{print $1}' | cut -d'%' -f1)
        partition=$(echo $output | awk '{print $2}')
        if [ $usage -ge 80 ]; then
            echo "警告:磁盘使用率过高 $partition ($usage%)"
        fi
    done
}

# 检查内存使用
check_memory() {
    memory_usage=$(free | grep Mem | awk '{printf("%.2f"), $3/$2 * 100.0}')
    if (( $(echo "$memory_usage > 80" | bc -l) )); then
        echo "警告:内存使用率过高 ($memory_usage%)"
    fi
}

echo "=== 系统监控报告 $(date) ==="
check_load
check_disk
check_memory

这个脚本可以定时运行,监控系统的基本状态。

日志分析脚本

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash

log_file="/var/log/nginx/access.log"
output_file="log_analysis_$(date +%Y%m%d).txt"

echo "=== Nginx日志分析报告 ===" > $output_file
echo "分析时间:$(date)" >> $output_file
echo "" >> $output_file

# 统计访问量最多的IP
echo "访问量前10的IP地址:" >> $output_file
awk '{print $1}' $log_file | sort | uniq -c | sort -nr | head -10 >> $output_file
echo "" >> $output_file

# 统计最受欢迎的页面
echo "访问量前10的页面:" >> $output_file
awk '{print $7}' $log_file | sort | uniq -c | sort -nr | head -10 >> $output_file
echo "" >> $output_file

# 统计HTTP状态码
echo "HTTP状态码统计:" >> $output_file
awk '{print $9}' $log_file | sort | uniq -c | sort -nr >> $output_file

echo "分析完成,结果保存在 $output_file"

这种日志分析脚本在运维工作中经常用到。

自动备份脚本

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash

# 配置参数
backup_source="/var/www"
backup_dest="/backup"
backup_name="website_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
keep_days=7

# 创建备份目录
mkdir -p $backup_dest

# 执行备份
echo "开始备份 $backup_source ..."
tar -czf "$backup_dest/$backup_name" -C $(dirname $backup_source) $(basename $backup_source)

if [ $? -eq 0 ]; then
    echo "备份成功:$backup_dest/$backup_name"
    
    # 清理旧备份
    find $backup_dest -name "website_backup_*.tar.gz" -mtime +$keep_days -delete
    echo "已清理${keep_days}天前的旧备份"
else
    echo "备份失败!"
    exit 1
fi

这个脚本可以放到crontab里定时执行,实现自动备份。

调试和错误处理

写Shell脚本难免会遇到各种问题,掌握一些调试技巧很重要。

调试技巧

代码语言:javascript
代码运行次数:0
运行
复制
# 开启调试模式
set -x  # 显示执行的每一条命令
set -e  # 遇到错误立即退出
set -u  # 使用未定义变量时报错

# 或者在脚本开头加上
#!/bin/bash -x

还可以用bash -x script.sh来运行脚本,这样会显示每一步的执行过程。

错误处理

代码语言:javascript
代码运行次数:0
运行
复制
# 检查命令执行结果
if ! command -v git > /dev/null; then
    echo "错误:未安装git"
    exit 1
fi

# 使用trap捕获信号
trap 'echo "脚本被中断"; exit 1' INT TERM

# 函数中的错误处理
safe_execute() {
    "$@"
    local status=$?
    if [ $status -ne 0 ]; then
        echo "命令执行失败: $*"
        exit $status
    fi
}

safe_execute cp important_file.txt backup/

良好的错误处理可以让脚本更加健壮,避免出现意外情况。

性能优化和最佳实践

写Shell脚本也有一些最佳实践需要注意。

性能优化

代码语言:javascript
代码运行次数:0
运行
复制
# 避免在循环中调用外部命令
# 不好的写法
for file in *.txt; do
    lines=$(wc -l < "$file")
    echo "$file: $lines lines"
done

# 更好的写法
wc -l *.txt
代码语言:javascript
代码运行次数:0
运行
复制
# 使用内置命令替代外部命令
# 不好的写法
result=$(echo $string | cut -c1-5)

# 更好的写法
result=${string:0:5}

代码规范

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash
# 脚本说明:这是一个示例脚本
# 作者:张三
# 创建时间:2024-01-01

# 设置严格模式
set -euo pipefail

# 全局变量使用大写
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly CONFIG_FILE="${SCRIPT_DIR}/config.conf"

# 函数名使用小写,用下划线分隔
check_prerequisites() {
    # 函数内容
    return 0
}

# 主函数
main() {
    check_prerequisites
    # 其他逻辑
}

# 只有在直接执行脚本时才运行main函数
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

常见问题和解决方案

在学习Shell的过程中,我遇到过很多坑,这里分享一些常见的问题。

空格问题

Shell对空格特别敏感,很多错误都是因为空格引起的:

代码语言:javascript
代码运行次数:0
运行
复制
# 错误的写法
if [ $var= "hello" ]; then  # 等号前后不能有空格(在变量赋值时)
    echo "world"
fi

# 正确的写法
var="hello"
if [ "$var" = "hello" ]; then  # 比较时等号前后要有空格
    echo "world"
fi

引号问题

什么时候用单引号,什么时候用双引号,这个也容易搞混:

代码语言:javascript
代码运行次数:0
运行
复制
name="张三"

# 单引号:原样输出,不解析变量
echo '我的名字是$name'  # 输出:我的名字是$name

# 双引号:解析变量
echo "我的名字是$name"  # 输出:我的名字是张三

# 不加引号:可能会有问题
echo 我的名字是$name    # 如果name包含空格就会出问题

路径问题

处理文件路径时要特别小心:

代码语言:javascript
代码运行次数:0
运行
复制
# 不好的写法
cd /some/path
rm -rf *  # 如果cd失败,这条命令会在当前目录执行!

# 更安全的写法
cd /some/path || exit 1
rm -rf *

# 或者使用绝对路径
rm -rf /some/path/*

数组和字符串混淆

代码语言:javascript
代码运行次数:0
运行
复制
# 定义数组时的常见错误
files="file1.txt file2.txt file3.txt"  # 这是字符串,不是数组

# 正确的数组定义
files=("file1.txt" "file2.txt" "file3.txt")

# 遍历时也要注意
for file in $files; do  # 如果文件名包含空格会有问题
    echo $file
done

# 更安全的写法
for file in "${files[@]}"; do
    echo "$file"
done

进阶技巧

掌握了基础语法之后,还有一些进阶的技巧可以让你的Shell脚本更加强大。

命令替换和进程替换

代码语言:javascript
代码运行次数:0
运行
复制
# 命令替换
current_date=$(date +%Y-%m-%d)
file_count=`ls | wc -l`  # 旧式写法,不推荐

# 进程替换
diff <(sort file1.txt) <(sort file2.txt)

参数扩展

代码语言:javascript
代码运行次数:0
运行
复制
filename="document.pdf"

# 获取文件名和扩展名
basename=${filename%.*}    # document
extension=${filename##*.}  # pdf

# 设置默认值
config_file=${CONFIG_FILE:-"/etc/default.conf"}

# 参数长度
echo ${#filename}  # 12

正则表达式

代码语言:javascript
代码运行次数:0
运行
复制
# 使用=~操作符进行正则匹配
email="user@example.com"
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "有效的邮箱地址"
fi

# 提取匹配的部分
text="版本号:v1.2.3"
if [[ $text =~ v([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
    major=${BASH_REMATCH[1]}
    minor=${BASH_REMATCH[2]}
    patch=${BASH_REMATCH[3]}
    echo "主版本:$major,次版本:$minor,补丁版本:$patch"
fi

Here Document

代码语言:javascript
代码运行次数:0
运行
复制
# 生成配置文件
cat > /etc/myapp.conf << EOF
server_name = $SERVER_NAME
port = $PORT
debug = true
EOF

# 多行字符串
message=$(cat << 'EOF'
这是一个
多行的
消息内容
EOF
)

与其他工具的集成

Shell脚本的强大之处在于可以很容易地与其他工具集成。

与数据库交互

代码语言:javascript
代码运行次数:0
运行
复制
# 连接MySQL
mysql -u root -p$PASSWORD -e "SELECT COUNT(*) FROM users;" mydb

# 使用Here Document执行复杂SQL
mysql -u root -p$PASSWORD mydb << EOF
UPDATE users SET last_login = NOW() WHERE id = 1;
SELECT * FROM users WHERE active = 1;
EOF

与API交互

代码语言:javascript
代码运行次数:0
运行
复制
# 使用curl调用REST API
api_response=$(curl -s -X GET "https://api.example.com/users" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json")

# 解析JSON响应(需要安装jq)
user_count=$(echo $api_response | jq '.total_count')

发送邮件通知

代码语言:javascript
代码运行次数:0
运行
复制
send_alert() {
    local subject="$1"
    local message="$2"
    
    echo "$message" | mail -s "$subject" admin@example.com
}

# 使用示例
if [ $disk_usage -gt 90 ]; then
    send_alert "磁盘空间警告" "磁盘使用率已达到 ${disk_usage}%"
fi

学习建议和资源推荐

学Shell编程其实没有什么捷径,就是多练多用。我当初也是从最简单的命令开始,慢慢积累经验的。

建议大家从这几个方面入手:

  1. 1. 先熟悉基本的Linux命令,像ls、cd、grep、awk、sed这些,这是基础
  2. 2. 然后开始写一些简单的脚本,比如文件备份、日志清理之类的
  3. 3. 遇到问题多查文档,man命令是你的好朋友
  4. 4. 看看别人写的脚本,学习他们的思路和技巧

网上有很多不错的学习资源,比如《Shell 编程范例》《Shell编程基础》这本书,内容很全面。还有一些在线的教程网站,像菜鸟教程、实验楼等等,都有不错的Shell教程。

最重要的是要多实践,光看不练是学不会的。可以从自己工作中的实际需求出发,尝试用Shell脚本来解决问题。比如自动化一些重复性的工作,或者写一些监控脚本等等。

写在最后

Shell编程虽然看起来有点复杂,但是掌握了基本语法和常用技巧之后,你会发现它其实是一个非常实用的工具。无论是系统管理、自动化运维,还是日常的文件处理,Shell脚本都能帮你提高效率。

当然,Shell也有它的局限性,比如在处理复杂的数据结构或者需要高性能计算的场景下,可能Python或者其他语言会更合适。但是对于大部分的系统管理任务来说,Shell已经足够强大了。

记住,学习任何技术都需要时间和耐心,不要指望一蹴而就。从简单的脚本开始,慢慢积累经验,相信你很快就能写出实用的Shell脚本了。

如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!

公众号:运维躬行录

个人博客:躬行笔记

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 运维躬行录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是Shell?为什么要学它?
  • 基础语法和变量
    • 变量定义和使用
    • 特殊变量
  • 条件判断和流程控制
    • if语句
    • case语句
  • 循环结构
    • for循环
    • while循环
  • 函数的使用
  • 数组操作
  • 字符串处理
  • 文件操作和I/O重定向
    • 基本文件操作
    • I/O重定向
  • 实战案例
    • 系统监控脚本
    • 日志分析脚本
    • 自动备份脚本
  • 调试和错误处理
    • 调试技巧
    • 错误处理
  • 性能优化和最佳实践
    • 性能优化
    • 代码规范
  • 常见问题和解决方案
    • 空格问题
    • 引号问题
    • 路径问题
    • 数组和字符串混淆
  • 进阶技巧
    • 命令替换和进程替换
    • 参数扩展
    • 正则表达式
    • Here Document
  • 与其他工具的集成
    • 与数据库交互
    • 与API交互
    • 发送邮件通知
  • 学习建议和资源推荐
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档