玩转 Linux Shell:从命令行到脚本编写,一篇搞定日常开发与运维!

目录

引言

一、先搞懂:什么是 Linux Shell?—— 内核与用户的 “沟通桥梁”

1.1 Shell 的本质:不是内核,却是 “指挥官”

1.2 常见 Shell 类型:我们用的是哪一种?

二、Shell 命令行基础:玩转终端的 “必备技能”

2.1 终端与命令行:打开 Shell 的 “窗口”

2.2 核心命令:文件与目录操作(高频使用)

2.2.1 目录切换:cd 命令

2.2.2 目录查看:ls 命令

2.2.3 目录创建:mkdir 命令

2.2.4 文件创建:touch 命令

2.2.5 文件 / 目录复制:cp 命令

2.2.6 文件 / 目录移动 / 重命名:mv 命令

2.2.7 文件 / 目录删除:rm 命令

2.2.8 文件内容查看:cat/tail/head/more/less 命令

2.3 权限管理:看懂并修改文件权限

2.3.1 权限的表示:rwx 与数字权限

2.3.2 修改权限:chmod 命令

2.3.3 修改所有者 / 所属组:chown/chgrp 命令

2.4 其他常用命令:提升效率必备

2.4.1 查找文件 / 目录:find 命令

2.4.2 查找命令 / 文件路径:which/whereis 命令

2.4.3 查看系统信息:uname/top/free 命令

2.4.4 压缩与解压:tar 命令

三、Shell 脚本入门:从 “手动输入” 到 “自动执行”

3.1 脚本的基本结构:Hello World 示例

脚本执行的 3 种方式:

关键说明:

3.2 变量:脚本中的 “容器”

3.2.1 变量定义与使用

3.2.2 特殊变量:脚本参数与系统变量

(1)脚本参数变量(运行脚本时传入的参数)

(2)常用系统变量

3.3 字符串处理:拼接、截取与替换

3.3.1 字符串拼接

3.3.2 字符串长度

3.3.3 字符串截取

3.3.4 字符串替换

3.4 数组:存储多个数据

3.4.1 数组定义与访问

3.4.2 数组遍历

四、流程控制:让脚本 “聪明” 起来

4.1 条件判断:if 语句

4.1.1 基本语法

4.1.2 条件表达式:比较与判断

(1)数值比较(适用于整数)

(2)字符串比较

(3)文件状态判断

4.2 循环结构:for 与 while 循环

4.2.1 for 循环:遍历列表或范围

(1)遍历列表

(2)遍历数字范围

(3)C 语言风格 for 循环

4.2.2 while 循环:条件满足时执行

(1)基本用法

(2)无限循环(配合 break 退出)

(3)读取文件内容(逐行读取)

4.3 循环控制:break 与 continue

五、文本处理三剑客:grep、sed、awk(核心应用)

5.1 grep:文本搜索神器

5.1.1 基本用法

5.1.2 实战示例

(1)搜索包含 “ERROR” 的行

(2)搜索包含 “INFO” 的行,并显示行号

(3)忽略大小写搜索 “warning”

(4)统计匹配 “INFO” 的行数

(5)反向匹配:显示不包含 “INFO” 的行

(6)递归搜索目录下所有文件中的 “ERROR”

5.2 sed:文本编辑工具

5.2.1 基本用法

5.2.2 实战示例(基于 app.log 文件)

(1)替换字符串:将 “ERROR” 改为 “严重错误”

(2)删除包含 “WARNING” 的行

(3)在包含 “INFO” 的行前插入内容

(4)打印包含 “admin” 的行

5.3 awk:文本分析工具

5.3.1 基本用法

5.3.2 实战示例

(1)提取字段:获取日志中的时间和日志级别

(2)指定字段分隔符:处理 CSV 文件

(4)数值计算:统计日志中 ERROR 的出现次数

(5)按行号处理:显示第 2-4 行的内容

六、Shell 脚本实战案例:解决实际问题

6.1 案例 1:批量备份文件

6.2 案例 2:日志分析与统计

6.3 案例 3:自动化部署 Python 项目

七、避坑指南:Shell 脚本常见错误与解决方案

7.1 坑点 1:变量赋值时等号两边有空格

7.2 坑点 2:字符串包含空格时未加引号

7.3 坑点 3:条件判断中变量未加引号(含空格时出错)

7.4 坑点 4:循环遍历文件时,目录下无匹配文件导致遍历失败

7.5 坑点 5:使用rm -rf时路径错误,误删重要文件

7.6 坑点 6:脚本权限不足,无法执行

八、总结:Shell 学习路径与进阶建议

8.1 核心要点回顾

8.2 进阶学习建议


 

class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

引言

在 Linux 世界里,Shell 就像一位 “全能翻译官”,帮我们把人类的指令传递给 Linux 内核,完成文件操作、进程管理、自动化部署等一系列工作。无论是开发调试代码,还是运维监控服务器,Shell 都是不可或缺的工具。很多新手觉得 Shell 晦涩难懂,其实只要掌握核心逻辑,从命令行逐步过渡到脚本实战,就能轻松上手。

本文从 Shell 基础概念切入,循序渐进讲解命令行操作、脚本编写、文本处理、流程控制等核心内容,搭配大量可直接复用的实战示例,帮你彻底吃透 Linux Shell,解决日常工作中的 80% 问题。

一、先搞懂:什么是 Linux Shell?—— 内核与用户的 “沟通桥梁”

1.1 Shell 的本质:不是内核,却是 “指挥官”

Linux 内核(Kernel)是操作系统的核心,负责管理硬件资源(CPU、内存、硬盘),但它 “高冷” 且 “难懂”,只接受二进制指令。而 Shell 是一个命令解释器,介于用户和内核之间,把用户输入的字符串命令翻译成内核能理解的指令,执行后再把结果反馈给用户。

简单类比:内核是餐厅的后厨(负责做菜),Shell 是服务员(接收顾客订单,传达给后厨,再把菜端给顾客),用户就是顾客。

1.2 常见 Shell 类型:我们用的是哪一种?

Linux 系统中有多种 Shell,不同 Shell 功能略有差异,常见的有:

  • sh(Bourne Shell):最早期的 Shell,功能简单,兼容性强,是很多 Shell 的基础;
  • bash(Bourne Again Shell):目前绝大多数 Linux 发行版(CentOS、Ubuntu、Debian)的默认 Shell,兼容 sh,新增了别名、历史命令、脚本编程等强大功能;
  • csh/tcsh:语法类似 C 语言,适合习惯 C 语言的用户;
  • zsh:功能更丰富,支持自动补全、语法高亮,很多开发者的个性化选择。

查看当前系统默认 Shell:

echo $SHELL
# 输出示例:/bin/bash(表示默认是bash)

查看系统支持的 Shell:

cat /etc/shells
# 输出示例:
# /bin/sh
# /bin/bash
# /usr/bin/sh
# /usr/bin/bash

后续内容均以bash为准,这是日常工作中最常用的 Shell。

二、Shell 命令行基础:玩转终端的 “必备技能”

2.1 终端与命令行:打开 Shell 的 “窗口”

  • 终端(Terminal):是用户与 Shell 交互的界面,比如 Ubuntu 的 GNOME 终端、CentOS 的 Konsole;
  • 命令行(Command Line):终端中输入命令的区域,提示符通常为$(普通用户)或#(root 用户)。

切换 root 用户(获取最高权限):

su - root  # 输入root密码后切换
# 或使用sudo临时获取权限(Ubuntu常用)
sudo ls /root

2.2 核心命令:文件与目录操作(高频使用)

文件和目录操作是 Shell 最基础的功能,以下命令必须熟练掌握,每个命令搭配实战示例。

2.2.1 目录切换:cd 命令

# 1. 切换到指定绝对路径(从根目录/开始)
cd /home/user/Documents  # 切换到用户的文档目录

# 2. 切换到相对路径(相对于当前目录)
cd ../Desktop  # 切换到上一级目录的Desktop文件夹(../表示上一级)
cd ./test      # 切换到当前目录的test文件夹(./可省略,直接cd test)

# 3. 切换到用户主目录(~表示主目录)
cd ~  # 等价于cd /home/user

# 4. 切换到上一次所在目录
cd -  # 输出示例:/home/user,同时切换到该目录

2.2.2 目录查看:ls 命令

# 1. 查看当前目录下的文件和目录(默认仅显示名称)
ls

# 2. 详细查看(权限、所有者、大小、修改时间等)
ls -l  # 简写为ll(很多系统已设置别名)
# 输出示例:
# drwxr-xr-x 2 user user 4096 10月  1 10:00 test
# -rw-r--r-- 1 user user  120 10月  1 09:30 test.txt
# 解释:d表示目录,-表示文件;rwx表示读写执行权限;2表示链接数;user是所有者;4096是大小(字节)

# 3. 查看隐藏文件(以.开头的文件)
ls -a  # 结合-l:ls -la

# 4. 按大小排序查看(从大到小)
ls -lhS  # h表示人性化显示大小(KB/MB/GB),S表示按大小排序

2.2.3 目录创建:mkdir 命令

# 1. 创建单个目录
mkdir test

# 2. 递归创建多级目录(父目录不存在时自动创建)
mkdir -p /home/user/project/code  # 同时创建project和code目录

2.2.4 文件创建:touch 命令

# 1. 创建空文件
touch test.txt

# 2. 同时创建多个文件
touch a.txt b.log c.csv

# 3. 修改文件的访问/修改时间(不改变文件内容)
touch -d "2024-10-01 12:00" test.txt  # 将文件时间改为指定时间

2.2.5 文件 / 目录复制:cp 命令

# 1. 复制文件到指定目录
cp test.txt /home/user/Desktop  # 把test.txt复制到桌面

# 2. 复制目录(必须加-r,递归复制)
cp -r test /home/user/Desktop  # 把test目录及内容复制到桌面

# 3. 复制时保留文件属性(权限、时间等)
cp -a test.txt /home/user/Desktop  # -a等价于-pdr

# 4. 覆盖文件时提示确认
cp -i test.txt /home/user/Desktop  # 若目标目录有同名文件,提示是否覆盖

2.2.6 文件 / 目录移动 / 重命名:mv 命令

# 1. 移动文件到指定目录
mv test.txt /home/user/Desktop  # 把test.txt移动到桌面

# 2. 移动目录(无需加-r,直接移动)
mv test /home/user/Desktop

# 3. 文件重命名(同一目录下移动即重命名)
mv test.txt new_test.txt  # 把test.txt改名为new_test.txt

# 4. 移动时覆盖提示
mv -i test.txt /home/user/Desktop

2.2.7 文件 / 目录删除:rm 命令

⚠️ 注意:rm 命令删除的文件 / 目录无法恢复,慎用!

# 1. 删除文件
rm test.txt

# 2. 强制删除文件(无提示)
rm -f test.txt  # 即使文件只读,也能删除

# 3. 删除目录(必须加-r,递归删除)
rm -r test

# 4. 强制删除目录(无提示)
rm -rf test  # 高危命令!避免误删系统文件(如rm -rf /)

2.2.8 文件内容查看:cat/tail/head/more/less 命令

# 1. 查看文件全部内容(适合小文件)
cat test.txt

# 2. 查看文件前n行(默认前10行)
head test.txt  # 前10行
head -n 5 test.txt  # 前5行

# 3. 查看文件后n行(默认后10行,实时监控日志常用)
tail test.txt  # 后10行
tail -n 5 test.txt  # 后5行
tail -f test.log  # 实时跟踪文件内容变化(日志输出时自动刷新)

# 4. 分页查看大文件(按空格键翻页,q退出)
more test.txt  # 只能向下翻页
less test.txt  # 可上下翻页,支持搜索(输入/关键词查找)

2.3 权限管理:看懂并修改文件权限

Linux 是多用户系统,每个文件 / 目录都有严格的权限控制,避免未授权访问。

2.3.1 权限的表示:rwx 与数字权限

ls -l查看文件时,最前面的 10 个字符就是权限信息,例如:drwxr-xr-x

  • 第 1 位:文件类型(d = 目录,-= 普通文件,l = 链接文件);
  • 第 2-4 位:所有者(user)权限(r = 读,w = 写,x = 执行);
  • 第 5-7 位:所属组(group)权限;
  • 第 8-10 位:其他用户(others)权限。

权限的数字表示(r=4,w=2,x=1,相加得到权限值):

  • rw- = 4+2=6;
  • r-x = 4+1=5;
  • rwx = 4+2+1=7;
  • --- = 0。

例如rwxr-xr-x对应的数字权限是 755,rw-r--r--是 644。

2.3.2 修改权限:chmod 命令

# 1. 数字方式修改(推荐,简洁)
chmod 755 test.sh  # 所有者rwx,所属组r-x,其他r-x
chmod 644 test.txt  # 所有者rw-,所属组r--,其他r--

# 2. 符号方式修改(更灵活)
chmod u+x test.sh  # u=所有者,+x=添加执行权限
chmod g-w test.txt  # g=所属组,-w=移除写权限
chmod o=r test.txt  # o=其他用户,=r=设置为只读权限
chmod a+r test.txt  # a=所有用户,+r=添加读权限

2.3.3 修改所有者 / 所属组:chown/chgrp 命令

# 1. 修改所有者(需root权限)
chown root test.txt  # 把test.txt的所有者改为root

# 2. 同时修改所有者和所属组
chown root:root test.txt  # 所有者和所属组都改为root

# 3. 修改所属组
chgrp user test.txt  # 把所属组改为user

2.4 其他常用命令:提升效率必备

2.4.1 查找文件 / 目录:find 命令

# 1. 在当前目录下查找名为test.txt的文件
find . -name "test.txt"

# 2. 在/home目录下查找所有.log后缀的文件
find /home -name "*.log"

# 3. 查找大小大于100MB的文件
find / -size +100M

# 4. 查找24小时内修改过的文件
find /home/user -mtime 0

# 5. 查找所有者为user的文件
find /home -user user

2.4.2 查找命令 / 文件路径:which/whereis 命令

# 1. 查找命令的执行路径
which ls  # 输出:/bin/ls
which python3  # 输出:/usr/bin/python3

# 2. 查找命令的二进制文件、源码和帮助文档路径
whereis ls  # 输出:ls: /bin/ls /usr/share/man/man1/ls.1.gz

2.4.3 查看系统信息:uname/top/free 命令

# 1. 查看系统内核版本
uname -r  # 输出示例:5.15.0-78-generic

# 2. 查看系统负载和进程信息(实时刷新)
top  # 按q退出,PID=进程ID,%CPU=CPU占用率,%MEM=内存占用率

# 3. 查看内存使用情况
free -h  # h表示人性化显示(总内存、已用、空闲)
# 输出示例:
#               total        used        free      shared  buff/cache   available
# Mem:           15Gi       2.3Gi        10Gi       224Mi       3.1Gi        13Gi

2.4.4 压缩与解压:tar 命令

tar 是 Linux 最常用的压缩解压工具,支持.tar、.tar.gz、.tar.bz2 等格式。

# 1. 压缩文件/目录为.tar.gz格式(最常用)
tar -zcvf test.tar.gz test.txt test/  # z=gzip压缩,c=创建,v=显示过程,f=指定文件名

# 2. 解压.tar.gz文件
tar -zxvf test.tar.gz  # x=解压,解压到当前目录
tar -zxvf test.tar.gz -C /home/user/Desktop  # -C指定解压目录

# 3. 压缩为.tar.bz2格式(压缩率更高,速度较慢)
tar -jcvf test.tar.bz2 test.txt test/

# 4. 解压.tar.bz2文件
tar -jxvf test.tar.bz2

 

三、Shell 脚本入门:从 “手动输入” 到 “自动执行”

手动输入命令适合单次操作,若需要重复执行一系列命令(如批量处理文件、自动化部署),就需要编写 Shell 脚本(.sh 文件),把命令按逻辑组织起来,一键执行。

3.1 脚本的基本结构:Hello World 示例

新建一个脚本文件hello.sh,内容如下:

#!/bin/bash
# 这是注释:第一个Shell脚本,输出Hello World

# 输出字符串
echo "Hello World!"

脚本执行的 3 种方式:

# 方式1:赋予执行权限后直接运行(推荐)
chmod +x hello.sh  # 赋予执行权限
./hello.sh         # 运行脚本(./表示当前目录)

# 方式2:用bash命令解释执行(无需执行权限)
bash hello.sh

# 方式3:用source命令执行(会在当前Shell环境中运行,影响环境变量)
source hello.sh

输出结果:Hello World!

关键说明:

  • #!/bin/bash:指定脚本的解释器为 bash,必须放在脚本第一行;
  • #:注释符号,后面的内容不会执行;
  • echo:输出字符串到终端。

3.2 变量:脚本中的 “容器”

变量用于存储数据(字符串、数字等),在脚本中重复使用,提升灵活性。

3.2.1 变量定义与使用

#!/bin/bash
# 变量定义:变量名=值(等号两边不能有空格!)
name="Linux"
version=5.15
message="Hello, $name!"  # 变量引用:$变量名

# 输出变量
echo "系统名称:$name"
echo "内核版本:$version"
echo $message

# 变量重新赋值
version=6.0
echo "更新后的内核版本:$version"

执行结果:

系统名称:Linux
内核版本:5.15
Hello, Linux!
更新后的内核版本:6.0

3.2.2 特殊变量:脚本参数与系统变量

(1)脚本参数变量(运行脚本时传入的参数)

变量含义
$0脚本文件名
1−n第 1 个到第 n 个参数
$#参数总数
$*所有参数(作为一个整体)
$@所有参数(每个参数独立)
$?上一条命令的执行结果(0 = 成功,非 0 = 失败)

示例:参数处理脚本param.sh

#!/bin/bash
echo "脚本文件名:$0"
echo "第1个参数:$1"
echo "第2个参数:$2"
echo "参数总数:$#"
echo "所有参数(整体):$*"
echo "所有参数(独立):$@"

# 验证上一条命令执行结果
echo "执行echo命令后的状态:$?"  # 输出0,因为echo执行成功

运行脚本并传入参数:

./param.sh "Shell" "Script"

输出结果:

脚本文件名:./param.sh
第1个参数:Shell
第2个参数:Script
参数总数:2
所有参数(整体):Shell Script
所有参数(独立):Shell Script
执行echo命令后的状态:0

(2)常用系统变量

#!/bin/bash
echo "当前用户:$USER"
echo "用户主目录:$HOME"
echo "当前工作目录:$PWD"
echo "系统PATH:$PATH"  # 命令搜索路径,可执行文件所在目录
echo "主机名:$HOSTNAME"

3.3 字符串处理:拼接、截取与替换

字符串是 Shell 脚本中最常用的数据类型,掌握以下操作能应对大部分场景。

3.3.1 字符串拼接

#!/bin/bash
str1="Hello"
str2="World"

# 拼接方式1:直接连接
str3=$str1$str2
echo $str3  # 输出:HelloWorld

# 拼接方式2:加双引号(推荐,可读性强)
str4="$str1 $str2"
echo $str4  # 输出:Hello World

# 拼接方式3:单引号(单引号内变量不解析,原样输出)
str5='$str1 $str2'
echo $str5  # 输出:$str1 $str2

3.3.2 字符串长度

#!/bin/bash
str="Linux Shell"
echo "字符串长度:${#str}"  # 输出:11(空格算1个字符)

3.3.3 字符串截取

语法:${变量名:起始位置:长度}(起始位置从 0 开始,长度可选)

#!/bin/bash
str="Linux Shell Script"

# 从第0位开始,截取5个字符
echo ${str:0:5}  # 输出:Linux

# 从第6位开始,截取到末尾(省略长度)
echo ${str:6}    # 输出:Shell Script

# 从倒数第7位开始,截取7个字符
echo ${str: -7:7}  # 输出:Script(注意-7前有空格)

3.3.4 字符串替换

#!/bin/bash
str="Linux Shell Shell"

# 替换第一个匹配的子串
echo ${str/Shell/Bash}  # 输出:Linux Bash Shell

# 替换所有匹配的子串
echo ${str//Shell/Bash}  # 输出:Linux Bash Bash

# 从开头匹配并替换
echo ${str/#Linux/Unix}  # 输出:Unix Shell Shell

# 从结尾匹配并替换
echo ${str/%Script/Code}  # 输出:Linux Shell Code(原字符串末尾是Shell,无Script,所以不替换)

3.4 数组:存储多个数据

Shell 数组支持存储多个值,常用于批量处理数据(如文件列表、IP 地址等)。

3.4.1 数组定义与访问

#!/bin/bash
# 数组定义方式1:空格分隔值
fruits=("apple" "banana" "orange" "grape")

# 数组定义方式2:单独赋值
fruits[4]="mango"

# 访问数组元素:${数组名[索引]}(索引从0开始)
echo "第1个元素:${fruits[0]}"
echo "第3个元素:${fruits[2]}"
echo "第5个元素:${fruits[4]}"

# 访问所有元素:${数组名[@]} 或 ${数组名[*]}
echo "所有元素:${fruits[@]}"

# 数组长度:${#数组名[@]}
echo "数组长度:${#fruits[@]}"

输出结果:

第1个元素:apple
第3个元素:orange
第5个元素:mango
所有元素:apple banana orange grape mango
数组长度:5

3.4.2 数组遍历

#!/bin/bash
fruits=("apple" "banana" "orange" "grape")

# 方式1:for循环遍历
for fruit in ${fruits[@]}; do
    echo "水果:$fruit"
done

# 方式2:按索引遍历
for ((i=0; i<${#fruits[@]}; i++)); do
    echo "索引$i:${fruits[$i]}"
done

输出结果:

水果:apple
水果:banana
水果:orange
水果:grape
索引0:apple
索引1:banana
索引2:orange
索引3:grape

 

四、流程控制:让脚本 “聪明” 起来

默认情况下,Shell 脚本按顺序执行命令,流程控制能让脚本根据条件判断执行不同命令,或重复执行某段命令,实现更复杂的逻辑。

4.1 条件判断:if 语句

if 语句根据条件是否成立,执行不同的代码块,支持单分支、双分支、多分支。

4.1.1 基本语法

# 单分支
if [ 条件表达式 ]; then
    命令1
    命令2
fi

# 双分支
if [ 条件表达式 ]; then
    命令1(条件成立时执行)
else
    命令2(条件不成立时执行)
fi

# 多分支
if [ 条件表达式1 ]; then
    命令1
elif [ 条件表达式2 ]; then
    命令2
else
    命令3
fi

⚠️ 注意:[ ] 前后必须加空格,否则会报错!

4.1.2 条件表达式:比较与判断

条件表达式主要用于比较数值、字符串,或判断文件状态。

(1)数值比较(适用于整数)

运算符含义
-eq等于(equal)
-ne不等于(not equal)
-gt大于(greater than)
-lt小于(less than)
-ge大于等于(greater or equal)
-le小于等于(less or equal)

示例:数值比较脚本

#!/bin/bash
a=10
b=20

if [ $a -eq $b ]; then
    echo "$a 等于 $b"
elif [ $a -lt $b ]; then
    echo "$a 小于 $b"
else
    echo "$a 大于 $b"
fi

输出结果:10 小于 20

(2)字符串比较

运算符含义
= 或 ==等于(== 在 [ ] 中需加引号)
!=不等于
-z字符串长度为 0(空字符串)
-n字符串长度不为 0(非空字符串)

示例:字符串比较脚本

#!/bin/bash
str1="Shell"
str2="Script"

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

# 判断字符串是否为空
str3=""
if [ -z "$str3" ]; then
    echo "str3是空字符串"
else
    echo "str3是非空字符串"
fi

输出结果:

两个字符串不相等
str3是空字符串

(3)文件状态判断

运算符含义
-f是否为普通文件
-d是否为目录
-e文件 / 目录是否存在
-r是否有读权限
-w是否有写权限
-x是否有执行权限
-s文件是否非空(大小大于 0)

示例:文件状态判断脚本

#!/bin/bash
file="test.txt"
dir="test"

# 判断文件是否存在
if [ -e "$file" ]; then
    echo "$file 存在"
    # 判断是否为普通文件
    if [ -f "$file" ]; then
        echo "$file 是普通文件"
    fi
else
    echo "$file 不存在"
    touch "$file"  # 创建文件
    echo "已创建 $file"
fi

# 判断目录是否存在
if [ -d "$dir" ]; then
    echo "$dir 是目录"
else
    echo "$dir 不是目录"
fi

运行脚本(假设 test.txt 和 test 目录初始不存在):

test.txt 不存在
已创建 test.txt
test 不是目录

4.2 循环结构:for 与 while 循环

循环用于重复执行某段命令,比如遍历文件列表、批量处理数据等。

4.2.1 for 循环:遍历列表或范围

(1)遍历列表

#!/bin/bash
# 遍历数组
fruits=("apple" "banana" "orange")
for fruit in ${fruits[@]}; do
    echo "水果:$fruit"
done

# 遍历文件列表
echo "当前目录下的.log文件:"
for file in *.log; do
    echo $file
done

(2)遍历数字范围

语法:for 变量 in {起始..结束..步长}(步长可选,默认 1)

#!/bin/bash
# 遍历1-5(步长1)
echo "1-5的数字:"
for i in {1..5}; do
    echo $i
done

# 遍历1-10,步长2(只取奇数)
echo "1-10的奇数:"
for i in {1..10..2}; do
    echo $i
done

# 遍历10-1(倒序)
echo "10-1的数字:"
for i in {10..1}; do
    echo $i
done

(3)C 语言风格 for 循环

语法:for ((初始值; 条件; 增量))

#!/bin/bash
# 计算1-10的和
sum=0
for ((i=1; i<=10; i++)); do
    sum=$((sum + i))  # 数值计算:$((表达式))
done
echo "1-10的和:$sum"  # 输出:55

4.2.2 while 循环:条件满足时执行

while 循环只要条件成立,就会一直执行循环体。

(1)基本用法

#!/bin/bash
# 计算1-10的和
sum=0
i=1
while [ $i -le 10 ]; do
    sum=$((sum + i))
    i=$((i + 1))  # 变量自增
done
echo "1-10的和:$sum"  # 输出:55

(2)无限循环(配合 break 退出)

#!/bin/bash
i=1
while true; do  # true表示条件永远成立
    echo "当前数字:$i"
    if [ $i -eq 5 ]; then
        break  # 当i=5时退出循环
    fi
    i=$((i + 1))
done

输出结果:

当前数字:1
当前数字:2
当前数字:3
当前数字:4
当前数字:5

(3)读取文件内容(逐行读取)

#!/bin/bash
# 逐行读取test.txt的内容
while read line; do
    echo "文件内容:$line"
done < test.txt  # < 表示从文件读取输入

4.3 循环控制:break 与 continue

  • break:退出当前循环(多层循环时只退出最内层);
  • continue:跳过当前循环的剩余命令,直接进入下一次循环。

示例:循环控制脚本

#!/bin/bash
# break示例:遍历1-10,遇到5退出
echo "break示例:"
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break
    fi
    echo $i
done

# continue示例:遍历1-10,跳过偶数
echo "continue示例:"
for i in {1..10}; do
    if [ $((i % 2)) -eq 0 ]; then  # 判断偶数
        continue
    fi
    echo $i
done

输出结果:

break示例:
1
2
3
4
continue示例:
1
3
5
7
9

 

五、文本处理三剑客:grep、sed、awk(核心应用)

Linux 中处理文本文件(日志、配置文件)时,grep、sed、awk 是最强大的工具组合,被誉为 “文本处理三剑客”。

5.1 grep:文本搜索神器

grep 用于在文件或输入中搜索匹配的字符串,支持正则表达式,是日志分析的必备工具。

5.1.1 基本用法

语法:grep [选项] 搜索模式 文件名

常用选项

  • -i:忽略大小写;
  • -n:显示匹配行的行号;
  • -v:反向匹配(显示不包含匹配模式的行);
  • -c:统计匹配的行数;
  • -r:递归搜索目录下的所有文件;
  • -E:支持扩展正则表达式。

5.1.2 实战示例

假设有一个日志文件app.log,内容如下:

2024-10-01 08:00:00 [INFO] 系统启动成功
2024-10-01 08:05:00 [ERROR] 数据库连接失败
2024-10-01 08:10:00 [INFO] 用户登录:admin
2024-10-01 08:15:00 [WARNING] 内存使用率过高:85%
2024-10-01 08:20:00 [INFO] 用户退出:admin
2024-10-01 08:25:00 [ERROR] 接口调用失败:/api/user

(1)搜索包含 “ERROR” 的行

grep "ERROR" app.log
# 输出:
# 2024-10-01 08:05:00 [ERROR] 数据库连接失败
# 2024-10-01 08:25:00 [ERROR] 接口调用失败:/api/user

(2)搜索包含 “INFO” 的行,并显示行号

grep -n "INFO" app.log
# 输出:
# 1:2024-10-01 08:00:00 [INFO] 系统启动成功
# 3:2024-10-01 08:10:00 [INFO] 用户登录:admin
# 5:2024-10-01 08:20:00 [INFO] 用户退出:admin

(3)忽略大小写搜索 “warning”

grep -i "warning" app.log
# 输出:
# 2024-10-01 08:15:00 [WARNING] 内存使用率过高:85%

(4)统计匹配 “INFO” 的行数

grep -c "INFO" app.log
# 输出:3

(5)反向匹配:显示不包含 “INFO” 的行

grep -v "INFO" app.log
# 输出所有非INFO级别的日志

(6)递归搜索目录下所有文件中的 “ERROR”

grep -r "ERROR" /var/log/  # 搜索/var/log目录下所有文件中的ERROR

5.2 sed:文本编辑工具

sed 是流编辑器,用于对文本进行替换、删除、插入等操作,不修改原文件(除非使用-i选项)。

5.2.1 基本用法

语法:sed [选项] '操作' 文件名

常用选项

  • -i:直接修改原文件(慎用!建议先备份);
  • -n:只显示匹配的行;
  • -e:执行多个操作。

常用操作

  • s/原字符串/新字符串/:替换匹配的字符串(默认替换每行第一个匹配);
  • s/原字符串/新字符串/g:全局替换(每行所有匹配);
  • d:删除匹配的行;
  • i:在匹配行前插入内容;
  • a:在匹配行后追加内容;
  • p:打印匹配的行。

5.2.2 实战示例(基于 app.log 文件)

(1)替换字符串:将 “ERROR” 改为 “严重错误”

# 不修改原文件,输出修改后的结果
sed 's/ERROR/严重错误/' app.log

# 全局替换(若一行有多个ERROR)
sed 's/ERROR/严重错误/g' app.log

# 直接修改原文件(建议先备份:cp app.log app.log.bak)
sed -i 's/ERROR/严重错误/' app.log

(2)删除包含 “WARNING” 的行

# 输出删除后的结果(不修改原文件)
sed '/WARNING/d' app.log

# 直接删除原文件中的行
sed -i '/WARNING/d' app.log

(3)在包含 “INFO” 的行前插入内容

sed '/INFO/i\【日志信息】' app.log
# 输出示例:
# 【日志信息】
# 2024-10-01 08:00:00 [INFO] 系统启动成功

(4)打印包含 “admin” 的行

sed -n '/admin/p' app.log
# 输出:
# 2024-10-01 08:10:00 [INFO] 用户登录:admin
# 2024-10-01 08:20:00 [INFO] 用户退出:admin

5.3 awk:文本分析工具

awk 是功能最强大的文本处理工具,支持模式匹配、字段提取、数值计算等,适合复杂的文本分析场景。

5.3.1 基本用法

语法:awk '模式{动作}' 文件名

  • 模式:用于匹配行(如条件判断);
  • 动作:对匹配的行执行操作(如打印字段、计算);
  • 字段分隔符:默认是空格或制表符,可用-F指定(如-F,表示逗号分隔);
  • 字段引用$1表示第 1 个字段,$2表示第 2 个字段,$0表示整行内容;
  • 内置变量NR(行号)、NF(当前行的字段数)。

5.3.2 实战示例

(1)提取字段:获取日志中的时间和日志级别

app.log 文件的字段用空格分隔,时间是第 1-2 个字段,日志级别是第 3 个字段(带 []):

# 打印时间($1 $2)和日志级别($3)
awk '{print "时间:"$1" "$2", 级别:"$3}' app.log
# 输出示例:
# 时间:2024-10-01 08:00:00, 级别:[INFO]
# 时间:2024-10-01 08:05:00, 级别:[ERROR]

(2)指定字段分隔符:处理 CSV 文件

假设有user.csv文件,内容如下(逗号分隔):

1,admin,123456,admin@example.com
2,user1,654321,user1@example.com
3,user2,112233,user2@example.com

提取用户名和邮箱:

(3)条件过滤:只显示 ERROR 级别的日志

bash

awk '$3 == "[ERROR]"{print $0}' app.log
# 输出所有ERROR级别的日志行

(4)数值计算:统计日志中 ERROR 的出现次数

awk '$3 == "[ERROR]"{count++} END{print "ERROR出现次数:"count}' app.log
# 输出:ERROR出现次数:2

(5)按行号处理:显示第 2-4 行的内容

awk 'NR>=2 && NR<=4{print $0}' app.log
# 输出第2到第4行的日志

六、Shell 脚本实战案例:解决实际问题

掌握了基础语法和工具后,我们通过几个实战案例,将知识应用到实际工作中。

6.1 案例 1:批量备份文件

需求:编写脚本,批量备份指定目录下的.log 文件到备份目录,并按日期命名备份文件。

#!/bin/bash
# 批量备份.log文件脚本

# 配置参数
source_dir="/var/log"  # 源目录(日志文件所在目录)
backup_dir="/home/user/backup/log"  # 备份目录
date=$(date +%Y%m%d)  # 当前日期(格式:年月日)

# 检查备份目录是否存在,不存在则创建
if [ ! -d "$backup_dir" ]; then
    mkdir -p "$backup_dir"
    echo "创建备份目录:$backup_dir"
fi

# 批量备份.log文件(复制到备份目录,并添加日期后缀)
echo "开始备份$source_dir下的.log文件..."
for file in "$source_dir"/*.log; do
    # 判断文件是否存在(避免目录下无.log文件时出错)
    if [ -f "$file" ]; then
        filename=$(basename "$file")  # 获取文件名(不含路径)
        backup_file="$backup_dir/${filename%.log}_$date.log"  # 备份文件名:原文件名_日期.log
        cp "$file" "$backup_file"
        echo "备份成功:$file -> $backup_file"
    fi
done

echo "所有.log文件备份完成!"

运行脚本:

chmod +x backup_log.sh
./backup_log.sh

输出示例:

创建备份目录:/home/user/backup/log
开始备份/var/log下的.log文件...
备份成功:/var/log/syslog.log -> /home/user/backup/log/syslog_20241001.log
备份成功:/var/log/auth.log -> /home/user/backup/log/auth_20241001.log
所有.log文件备份完成!

6.2 案例 2:日志分析与统计

需求:分析 app.log 日志,统计不同级别日志的数量,并输出 ERROR 级别的详细信息。

#!/bin/bash
# 日志分析脚本

log_file="app.log"

# 检查日志文件是否存在
if [ ! -f "$log_file" ]; then
    echo "错误:日志文件$log_file不存在!"
    exit 1  # 退出脚本,状态码1表示错误
fi

echo "=== 日志分析结果 ==="

# 统计INFO级别日志数量
info_count=$(grep -c "INFO" "$log_file")
echo "INFO级别日志:$info_count 条"

# 统计ERROR级别日志数量
error_count=$(grep -c "ERROR" "$log_file")
echo "ERROR级别日志:$error_count 条"

# 统计WARNING级别日志数量
warning_count=$(grep -c "WARNING" "$log_file")
echo "WARNING级别日志:$warning_count 条"

# 输出ERROR级别日志的详细信息(带行号)
echo -e "\n=== ERROR级别日志详情 ==="
grep -n "ERROR" "$log_file"

echo -e "\n日志分析完成!"

运行脚本输出:

=== 日志分析结果 ===
INFO级别日志:3 条
ERROR级别日志:2 条
WARNING级别日志:1 条

=== ERROR级别日志详情 ===
2:2024-10-01 08:05:00 [ERROR] 数据库连接失败
6:2024-10-01 08:25:00 [ERROR] 接口调用失败:/api/user

日志分析完成!

6.3 案例 3:自动化部署 Python 项目

需求:编写脚本,实现 Python 项目的自动化部署(拉取代码、安装依赖、启动服务)。

#!/bin/bash
# Python项目自动化部署脚本

# 配置参数
project_dir="/home/user/project/myapp"  # 项目目录
git_repo="https://github.com/user/myapp.git"  # Git仓库地址
python_env="/home/user/venv/bin/python"  # Python虚拟环境路径

echo "=== 开始部署Python项目 ==="

# 1. 拉取最新代码
echo "1. 拉取最新代码..."
if [ -d "$project_dir" ]; then
    cd "$project_dir" || exit 1  # 进入项目目录,失败则退出
    git pull  # 拉取代码
else
    git clone "$git_repo" "$project_dir"  # 克隆代码
    cd "$project_dir" || exit 1
fi

# 2. 安装依赖
echo "2. 安装项目依赖..."
"$python_env" -m pip install -r requirements.txt

# 3. 停止旧服务(假设用killall停止进程,根据实际情况调整)
echo "3. 停止旧服务..."
killall -9 python3 2>/dev/null  # 忽略无进程的错误信息

# 4. 启动新服务(后台运行)
echo "4. 启动新服务..."
nohup "$python_env" app.py > app.log 2>&1 &  # nohup表示后台运行,输出重定向到app.log

echo "=== 项目部署完成! ==="
echo "服务日志:$project_dir/app.log"

运行脚本:

chmod +x deploy_app.sh
./deploy_app.sh

输出示例:

=== 开始部署Python项目 ===
1. 拉取最新代码...
Already up to date.
2. 安装项目依赖...
Requirement already satisfied: flask in /home/user/venv/lib/python3.8/site-packages (from -r requirements.txt (line 1))
3. 停止旧服务...
4. 启动新服务...
=== 项目部署完成! ===
服务日志:/home/user/project/myapp/app.log

七、避坑指南:Shell 脚本常见错误与解决方案

新手写 Shell 脚本时容易踩坑,以下是高频错误及应对方法。

7.1 坑点 1:变量赋值时等号两边有空格

错误代码

name = "Linux"  # 等号两边有空格,报错:name: 未找到命令

解决方案:等号两边不能有空格。

name="Linux"

7.2 坑点 2:字符串包含空格时未加引号

错误代码

str=Hello World  # 空格导致被解析为两个参数,报错:World: 未找到命令

解决方案:加双引号或单引号。

str="Hello World"

7.3 坑点 3:条件判断中变量未加引号(含空格时出错)

错误代码

file="my file.txt"
if [ -f $file ]; then  # 变量含空格,被解析为两个参数,报错
    echo "文件存在"
fi

解决方案:变量加双引号。

if [ -f "$file" ]; then
    echo "文件存在"
fi

7.4 坑点 4:循环遍历文件时,目录下无匹配文件导致遍历失败

错误代码

for file in *.log; do
    echo $file  # 若目录下无.log文件,会输出"*.log"
done

解决方案:先判断文件是否存在。

for file in *.log; do
    if [ -f "$file" ]; then
        echo $file
    fi
done

7.5 坑点 5:使用rm -rf时路径错误,误删重要文件

错误代码

rm -rf $dir/*  # 若dir为空,会变成rm -rf /*,删除根目录文件!

解决方案:先判断目录是否存在,或使用更安全的写法。

if [ -d "$dir" ]; then
    rm -rf "$dir"/*
fi

7.6 坑点 6:脚本权限不足,无法执行

错误现象

./test.sh: 权限不够

解决方案:赋予执行权限。

chmod +x test.sh

八、总结:Shell 学习路径与进阶建议

8.1 核心要点回顾

  1. Shell 的定位:用户与 Linux 内核的命令解释器,是日常操作和自动化的核心工具;
  2. 基础命令:重点掌握文件 / 目录操作、权限管理、压缩解压、系统信息查看;
  3. 脚本编程:掌握变量、字符串、数组、流程控制(if/for/while),能编写简单脚本;
  4. 文本处理:熟练使用 grep(搜索)、sed(编辑)、awk(分析),应对日志和配置文件处理;
  5. 实战优先:通过实际案例巩固知识,解决工作中的具体问题。

8.2 进阶学习建议

  1. 正则表达式:深入学习正则表达式,提升 grep、sed、awk 的使用效率;
  2. Shell 高级特性:学习函数、数组高级操作、进程管理、信号处理;
  3. 自动化工具整合:结合 crontab(定时任务)、ansible(批量运维),实现更复杂的自动化场景;
  4. 阅读优秀脚本:查看系统自带脚本(如 /etc/init.d/ 下的服务脚本),学习规范的编写风格。

Shell 的学习没有捷径,多动手写脚本、多解决实际问题,才能从 “会用” 到 “精通”。记住:Shell 的核心价值是 “自动化”,把重复的工作交给脚本,让自己专注于更重要的事情!

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值