Linux是2016年服务器操作系统的绝对主流。无论是Web服务器、数据库服务器、云服务器,还是移动设备(Android基于Linux),都能看到Linux的身影。作为一个程序员或运维人员,掌握Linux命令行和Shell脚本,是必备的基础技能,能大幅提升工作效率。

很多人对Linux命令行感到恐惧,觉得黑底白字的终端很神秘、很难学。其实,Linux命令行并不难,只要掌握了常用命令和基本语法,就能应对大部分日常工作。而Shell脚本,更是能把重复的工作自动化,让你从繁琐的操作中解放出来。

这些年,我在Linux上做开发、做运维,用过不少发行版(CentOS、Ubuntu、Debian等),写过不少Shell脚本,对Linux命令行有比较深的了解。今天就来系统地讲解Linux命令行与Shell脚本实战,从基础命令到高级脚本,帮助你从入门到精通。

一、Linux简介

1. 什么是Linux

Linux是一套免费使用和自由传播的类Unix操作系统,由芬兰程序员Linus Torvalds在1991年创建。Linux的内核(Kernel)是操作系统的核心,加上各种应用程序和工具,就构成了各种Linux发行版。

Linux的特点:

  • 开源免费:源代码公开,免费使用,可以自由修改和分发
  • 稳定可靠:服务器可以连续运行数年不重启,稳定性极高
  • 安全:用户权限管理严格,病毒少,安全性高
  • 多用户多任务:支持多个用户同时登录,同时运行多个任务
  • 网络功能强:内置强大的网络功能,适合做服务器
  • 可定制:可以根据需要裁剪和定制,从嵌入式到超级计算机都能用

2. 常见Linux发行版

  • CentOS:基于Red Hat Enterprise Linux(RHEL)的社区版,稳定、免费,是国内服务器最常用的发行版之一。2016年主流版本是CentOS 6和CentOS 7。
  • Ubuntu:基于Debian,界面友好,社区活跃,适合桌面和服务器。2016年主流版本是Ubuntu 14.04 LTS和16.04 LTS。
  • Debian:稳定、保守,是很多发行版的基础(Ubuntu基于Debian)。
  • Fedora:Red Hat赞助的社区版,软件新,适合桌面和开发。
  • Arch Linux:滚动更新,轻量简洁,适合高级用户。
  • openSUSE:德国发行版,稳定,YAST配置工具强大。

服务器常用:CentOS、Ubuntu、Debian。桌面常用:Ubuntu、Fedora、Linux Mint。

3. 命令行 vs 图形界面

Linux既有图形界面(GUI,如GNOME、KDE),也有命令行(CLI,终端)。

  • 图形界面:直观易用,适合桌面用户、日常办公、浏览网页。但是服务器通常不安装图形界面(占用资源,而且远程管理不方便)。
  • 命令行:高效、强大、可脚本化,适合服务器管理、开发、运维、自动化。是程序员和运维人员的主要工作方式。

命令行的优势:

  • 速度快,不需要鼠标点击
  • 可以写脚本自动化重复工作
  • 资源占用少,适合服务器
  • 远程管理方便(SSH)
  • 功能强大,很多操作只能在命令行完成

二、Linux常用命令

Linux命令有几百个,但是常用的只有几十个。掌握这些常用命令,就能应对大部分日常工作。

1. 帮助命令

# 查看命令帮助
man ls          # 查看ls命令的手册(man page)
help cd         # 查看内置命令帮助(cd是bash内置命令)
ls --help       # 查看命令的帮助信息(大部分命令支持)

# 快速查找命令
whatis ls       # 查看命令的简短描述
which ls        # 查看命令的路径
whereis ls      # 查看命令的路径、源码、man页位置

2. 文件和目录操作

# 目录操作
pwd                     # 显示当前目录
cd /path/to/dir         # 切换目录
cd ~                    # 切换到用户主目录
cd -                    # 切换到上一个目录
ls                      # 列出目录内容
ls -l                   # 长格式列出(详细信息)
ls -a                   # 显示所有文件(包括隐藏文件)
ls -lh                  # 人类可读的文件大小(K/M/G)
ls -lt                  # 按修改时间排序
mkdir dirname           # 创建目录
mkdir -p a/b/c          # 递归创建目录
rmdir dirname           # 删除空目录

# 文件操作
touch filename          # 创建空文件或更新时间戳
cp source dest          # 复制文件
cp -r source_dir dest   # 递归复制目录
mv source dest          # 移动或重命名文件/目录
rm filename             # 删除文件
rm -r dirname           # 递归删除目录
rm -f filename          # 强制删除(不提示)
rm -rf dirname          # 强制递归删除(危险!慎用)
ln -s source link       # 创建软链接(符号链接)
ln source link          # 创建硬链接

# 查看文件
cat filename            # 查看文件内容(全部)
less filename           # 分页查看文件(q退出,空格翻页,/搜索)
more filename           # 分页查看(功能比less少)
head filename           # 查看文件前10行
head -n 20 filename     # 查看文件前20行
tail filename           # 查看文件后10行
tail -n 20 filename     # 查看文件后20行
tail -f filename        # 实时跟踪文件末尾(看日志常用)
wc filename             # 统计文件行数、单词数、字符数
wc -l filename          # 只统计行数
file filename           # 查看文件类型

3. 文本处理

# 查找和替换
grep "pattern" filename         # 在文件中查找匹配的行
grep -i "pattern" filename      # 忽略大小写
grep -v "pattern" filename      # 显示不匹配的行
grep -r "pattern" dir           # 递归查找目录下所有文件
grep -n "pattern" filename      # 显示行号
grep -c "pattern" filename      # 统计匹配行数
egrep "pattern1|pattern2" file # 扩展正则(或)

sed 's/old/new/g' filename      # 替换文本(全局替换)
sed -i 's/old/new/g' filename   # 直接修改文件
sed -n '1,10p' filename         # 显示第1-10行
sed '/pattern/d' filename        # 删除匹配的行

awk '{print $1}' filename        # 打印第一列
awk -F: '{print $1}' /etc/passwd # 以:为分隔符打印第一列
awk '{sum+=$1} END {print sum}' file # 求和
awk 'NR>2' filename              # 跳过前2行

# 排序和去重
sort filename                    # 排序
sort -r filename                 # 逆序排序
sort -n filename                 # 按数字排序
sort -u filename                 # 排序并去重
uniq filename                    # 去重(相邻行才去重,通常先sort)
uniq -c filename                 # 统计重复次数

# 切割和合并
cut -d: -f1 /etc/passwd         # 以:分隔,取第1列
cut -c1-5 filename               # 取第1-5个字符
paste file1 file2                # 按列合并两个文件
cat file1 file2 > merged         # 按行合并(拼接)

# 比较
diff file1 file2                 # 比较两个文件的差异
diff -u file1 file2              # unified格式(更友好)
vimdiff file1 file2              # vim方式比较(需要vim)

4. 系统管理

# 系统信息
uname -a                         # 显示系统信息(内核版本等)
hostname                         # 显示主机名
cat /etc/redhat-release         # 查看CentOS版本
cat /etc/os-release              # 查看OS版本(通用)
uptime                           # 显示系统运行时间和负载
date                             # 显示当前时间
date -s "2016-07-27 20:00:00"  # 设置时间
cal                              # 显示日历
free -m                          # 显示内存使用(MB)
df -h                            # 显示磁盘使用(人类可读)
du -sh dirname                   # 显示目录大小
du -sh *                         # 显示当前目录下各文件/目录大小
top                              # 实时显示进程和资源占用(q退出)
htop                             # 更友好的top(需要安装)

# 关机和重启
shutdown -h now                  # 立即关机
shutdown -r now                  # 立即重启
shutdown -h +10                  # 10分钟后关机
reboot                           # 重启
poweroff                         # 关机
halt                             # 关机

# 用户和组
whoami                           # 显示当前用户名
who                              # 显示已登录用户
w                                # 显示已登录用户及操作
id                               # 显示用户ID和组ID
useradd username                 # 添加用户
passwd username                  # 修改用户密码
userdel username                 # 删除用户
usermod -aG groupname username  # 将用户添加到组
groupadd groupname               # 添加组
su - username                    # 切换用户
sudo command                     # 以管理员权限执行命令

5. 进程管理

ps                               # 显示当前终端的进程
ps aux                           # 显示所有进程(详细)
ps -ef                           # 显示所有进程(另一种格式)
pstree                           # 以树状显示进程
top                              # 实时显示进程
htop                             # 更友好的top

# 终止进程
kill PID                         # 终止指定PID的进程(默认SIGTERM)
kill -9 PID                      # 强制终止(SIGKILL,慎用)
killall processname              # 按名称终止进程
pkill pattern                    # 按模式终止进程

# 前后台
command &                        # 后台运行命令
jobs                             # 显示后台任务
fg %1                            # 将后台任务1调到前台
bg %1                            # 将暂停的任务1放到后台运行
Ctrl+Z                           # 暂停当前前台任务
Ctrl+C                           # 终止当前前台任务
nohup command &                  # 后台运行,退出终端不终止

6. 权限管理

# 权限说明:r=4(读), w=2(写), x=1(执行)
# 三位数字分别代表:所有者、组、其他用户

chmod 755 filename               # 设置权限 rwxr-xr-x
chmod +x filename                # 添加执行权限
chmod -R 755 dirname             # 递归设置目录权限
chown user:group filename        # 修改文件所有者和组
chown -R user:group dirname      # 递归修改所有者
chgrp groupname filename         # 修改文件所属组

# 特殊权限
chmod u+s filename               # 设置SUID(执行时以所有者身份运行)
chmod g+s dirname                # 设置SGID(目录下新建文件继承组)
chmod +t dirname                 # 设置粘滞位(只有所有者能删除)

7. 网络管理

# 网络配置
ifconfig                         # 显示网络接口(CentOS 6,CentOS 7用ip addr)
ip addr                          # 显示网络接口(新命令)
ip route                         # 显示路由表
ping host                        # 测试网络连通性
ping -c 4 host                   # 发送4个包后停止
traceroute host                  # 追踪路由路径
mtr host                         # 更强大的路由追踪(需要安装)

# 端口和连接
netstat -tlnp                    # 显示监听的TCP端口和进程
netstat -an                      # 显示所有网络连接
ss -tlnp                         # 显示监听端口(新命令,比netstat快)
ss -s                            # 显示连接统计

# DNS
nslookup domain                  # 查询DNS
dig domain                       # 更详细的DNS查询
host domain                      # 查询DNS
cat /etc/resolv.conf             # 查看DNS配置

# 下载和传输
wget url                         # 下载文件
wget -c url                      # 断点续传
curl url                         # 传输URL(支持HTTP/FTP等)
curl -O url                      # 下载文件
scp file user@host:/path        # 远程复制文件(SSH)
scp -r dir user@host:/path      # 递归复制目录
rsync -avz source dest          # 远程同步(增量,高效)

# 防火墙
service iptables status          # CentOS 6 防火墙状态
systemctl status firewalld       # CentOS 7 防火墙状态
iptables -L                      # 查看防火墙规则

8. 压缩和解压

# tar包(最常用)
tar -cvf archive.tar dir/        # 创建tar包
tar -xvf archive.tar             # 解压tar包
tar -czvf archive.tar.gz dir/    # 创建tar.gz包(gzip压缩)
tar -xzvf archive.tar.gz         # 解压tar.gz包
tar -cjvf archive.tar.bz2 dir/   # 创建tar.bz2包(bzip2压缩,压缩率更高)
tar -xjvf archive.tar.bz2        # 解压tar.bz2包
tar -tf archive.tar              # 查看包内容(不解压)
tar -xzvf archive.tar.gz -C /path # 解压到指定目录

# zip
zip archive.zip file1 file2      # 创建zip包
zip -r archive.zip dir/          # 递归压缩目录
unzip archive.zip                 # 解压zip
unzip -l archive.zip              # 查看zip内容
unzip archive.zip -d /path        # 解压到指定目录

# gzip(只能压缩单个文件)
gzip filename                     # 压缩为filename.gz
gunzip filename.gz                # 解压
gzip -d filename.gz               # 解压(同上)

# bzip2
bzip2 filename                    # 压缩为filename.bz2
bunzip2 filename.bz2              # 解压

9. 软件包管理

# CentOS/RHEL(yum/rpm)
yum install package               # 安装软件包
yum remove package                # 卸载软件包
yum update package                # 更新软件包
yum update                        # 更新所有软件包
yum search keyword                # 搜索软件包
yum info package                  # 查看软件包信息
yum list installed                # 列出已安装的软件包
yum clean all                     # 清理缓存
rpm -ivh package.rpm              # 安装rpm包
rpm -e package                    # 卸载rpm包
rpm -qa                           # 列出所有已安装的rpm包
rpm -ql package                   # 查看软件包安装的文件
rpm -qf /path/to/file             # 查看文件属于哪个软件包

# Ubuntu/Debian(apt/dpkg)
apt-get update                    # 更新软件源
apt-get install package           # 安装软件包
apt-get remove package            # 卸载软件包(保留配置)
apt-get purge package             # 卸载软件包(删除配置)
apt-get upgrade                   # 更新所有软件包
apt-cache search keyword          # 搜索软件包
apt-cache show package            # 查看软件包信息
dpkg -i package.deb               # 安装deb包
dpkg -r package                   # 卸载deb包
dpkg -l                           # 列出所有已安装的deb包

10. 其他实用命令

# 查找文件
find /path -name "*.log"          # 按名称查找文件
find /path -type f -size +10M     # 查找大于10M的文件
find /path -mtime -7               # 查找7天内修改的文件
find /path -name "*.tmp" -delete   # 查找并删除文件
locate filename                     # 快速查找文件(需要updatedb)
which command                       # 查找命令位置

# 管道和重定向
command1 | command2                 # 管道(将command1的输出作为command2的输入)
command > file                      # 输出重定向到文件(覆盖)
command >> file                     # 输出追加到文件
command 2> file                     # 错误输出重定向
command > file 2>&1                 # 标准输出和错误都重定向到文件
command < file                      # 从文件读取输入

# 历史命令
history                             # 显示命令历史
!n                                  # 执行第n条历史命令
!!                                  # 执行上一条命令
!keyword                            # 执行最近一条以keyword开头的命令
Ctrl+R                              # 搜索历史命令

# 别名
alias ll='ls -l'                    # 设置别名
unalias ll                          # 取消别名
alias                               # 查看所有别名

# 其他
echo "text"                         # 输出文本
echo $PATH                          # 输出环境变量
env                                 # 显示所有环境变量
export VAR=value                    # 设置环境变量
source file                         # 加载文件中的配置(如.bashrc)
. file                              # 同上(source的简写)
clear                               # 清屏
exit                                # 退出终端

三、Shell脚本基础

Shell脚本是把一系列Linux命令写在一个文件里,按顺序执行,可以实现自动化。Bash(Bourne Again Shell)是Linux最常用的Shell。

1. 第一个Shell脚本

创建文件hello.sh

#!/bin/bash
# 这是我的第一个Shell脚本
echo "Hello, World!"
echo "当前时间是: $(date)"
echo "当前用户是: $USER"

执行:

chmod +x hello.sh    # 添加执行权限
./hello.sh            # 执行
# 或者
bash hello.sh         # 用bash解释器执行

说明:

  • #!/bin/bash:指定解释器(shebang)
  • #:注释
  • echo:输出
  • $(command):命令替换,执行命令并返回结果
  • $USER:环境变量

2. 变量

#!/bin/bash
# 定义变量(等号两边不能有空格)
name="张三"
age=25
pi=3.14

# 使用变量($变量名 或 ${变量名})
echo "姓名: $name"
echo "年龄: ${age}岁"
echo "圆周率: $pi"

# 只读变量
readonly PI=3.14159
echo "PI=$PI"
# PI=3.14  # 错误,只读变量不能修改

# 删除变量
unset name
echo "name已删除: $name"  # 输出空

# 变量类型(Shell变量都是字符串,但是可以做算术)
a=10
b=20
echo "a+b=$((a+b))"  # 算术运算用$((...))

# 特殊变量
echo "脚本名: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "参数个数: $#"
echo "所有参数: $@"
echo "所有参数(字符串): $*"
echo "上一个命令的退出码: $?"
echo "当前进程ID: $$"

执行带参数的脚本:

./script.sh arg1 arg2 arg3

3. 字符串操作

#!/bin/bash
str="Hello, World!"

# 字符串长度
echo "长度: ${#str}"  # 13

# 截取子串
echo "从第0个开始取5个: ${str:0:5}"   # Hello
echo "从第7个开始取5个: ${str:7:5}"   # World
echo "从第7个开始到结尾: ${str:7}"     # World!

# 替换
echo "替换第一个: ${str/World/Linux}"    # Hello, Linux!
echo "替换所有: ${str//o/0}"             # Hell0, W0rld!

# 删除
echo "删除最短前缀: ${str#Hello, }"      # World!
echo "删除最短后缀: ${str%!}"            # Hello, World
echo "删除最长前缀: ${str##*o}"          # rld!
echo "删除最长后缀: ${str%%o*}"          # Hell

# 大小写
echo "转大写: ${str^^}"  # HELLO, WORLD!
echo "转小写: ${str,,}"  # hello, world!

4. 数组

#!/bin/bash
# 定义数组
fruits=("苹果" "香蕉" "橙子" "葡萄")

# 访问元素
echo "第一个: ${fruits[0]}"
echo "第二个: ${fruits[1]}"
echo "所有元素: ${fruits[@]}"
echo "所有元素: ${fruits[*]}"
echo "元素个数: ${#fruits[@]}"
echo "第一个元素长度: ${#fruits[0]}"

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

# 修改元素
fruits[0]="红苹果"
echo "修改后第一个: ${fruits[0]}"

# 添加元素
fruits+=("西瓜")
echo "添加后所有: ${fruits[@]}"

# 删除元素
unset fruits[1]
echo "删除第二个后: ${fruits[@]}"

5. 条件判断

#!/bin/bash
# if语句
age=18
if [ $age -ge 18 ]; then
    echo "成年人"
elif [ $age -ge 12 ]; then
    echo "青少年"
else
    echo "儿童"
fi

# 条件测试([ ] 或 test)
# 注意:[ 后面和 ] 前面必须有空格

# 数值比较
# -eq 等于, -ne 不等于, -gt 大于, -ge 大于等于, -lt 小于, -le 小于等于
a=10
b=20
if [ $a -lt $b ]; then
    echo "a < b"
fi

# 字符串比较
# = 等于, != 不等于, -z 空串, -n 非空
str1="hello"
str2="world"
if [ $str1 = $str2 ]; then
    echo "相等"
else
    echo "不相等"
fi

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

# 文件测试
# -e 存在, -f 普通文件, -d 目录, -r 可读, -w 可写, -x 可执行
# -s 非空文件, -L 符号链接
file="/etc/passwd"
if [ -f $file ]; then
    echo "$file 是普通文件"
fi

if [ -d /etc ]; then
    echo "/etc 是目录"
fi

# 逻辑运算
# -a 与(and), -o 或(or), ! 非(not)
# 或者用 && ||
if [ $a -gt 5 ] && [ $b -lt 30 ]; then
    echo "a>5 且 b<30"
fi

# case语句
day="周一"
case $day in
    "周一"|"周二"|"周三"|"周四"|"周五")
        echo "工作日"
        ;;
    "周六"|"周日")
        echo "周末"
        ;;
    *)
        echo "无效"
        ;;
esac

6. 循环

#!/bin/bash
# for循环(遍历列表)
for i in 1 2 3 4 5; do
    echo "数字: $i"
done

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

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

# 遍历命令输出
for user in $(cat /etc/passwd | cut -d: -f1); do
    echo "用户: $user"
done

# while循环
i=1
while [ $i -le 5 ]; do
    echo "while i=$i"
    i=$((i+1))
done

# 读取文件每行
while read line; do
    echo "行: $line"
done < /etc/passwd

# until循环(条件为假时执行)
i=1
until [ $i -gt 5 ]; do
    echo "until i=$i"
    i=$((i+1))
done

# 循环控制
for i in 1 2 3 4 5; do
    if [ $i -eq 3 ]; then
        continue  # 跳过本次循环
    fi
    if [ $i -eq 5 ]; then
        break     # 跳出循环
    fi
    echo "i=$i"
done

7. 函数

#!/bin/bash
# 定义函数
function greet() {
    echo "Hello, $1!"
    return 0  # 返回值(0-255,0表示成功)
}

# 调用函数
greet "张三"
greet "李四"

# 函数返回值
function add() {
    return $(($1 + $2))
}
add 3 5
echo "3+5=$?"  # $?获取返回值

# 函数返回字符串(用echo输出,用$()捕获)
function get_greeting() {
    echo "你好,$1!今天是$(date +%Y-%m-%d)。"
}
greeting=$(get_greeting "张三")
echo "$greeting"

# 局部变量
function test_local() {
    local name="局部变量"
    echo "函数内: $name"
}
name="全局变量"
test_local
echo "函数外: $name"

# 递归函数
function factorial() {
    if [ $1 -le 1 ]; then
        echo 1
    else
        local prev=$(factorial $(($1-1)))
        echo $(($1 * prev))
    fi
}
echo "5!=$(factorial 5)"

8. 输入输出

#!/bin/bash
# 读取用户输入
echo "请输入你的名字:"
read name
echo "你好,$name!"

# 提示信息
read -p "请输入年龄: " age
echo "你的年龄是$age岁"

# 静默输入(密码)
read -s -p "请输入密码: " password
echo ""
echo "密码已输入"

# 读取多个值
read -p "请输入姓名和年龄(空格分隔): " username userage
echo "姓名: $username, 年龄: $userage"

# 输出带颜色
echo -e "\033[31m红色文字\033[0m"
echo -e "\033[32m绿色文字\033[0m"
echo -e "\033[33m黄色文字\033[0m"
echo -e "\033[34m蓝色文字\033[0m"
echo -e "\033[1m加粗文字\033[0m"
echo -e "\033[4m下划线文字\033[0m"

四、实用Shell脚本案例

下面是一些实用的Shell脚本案例,可以直接使用或修改。

1. 备份脚本

#!/bin/bash
# 自动备份脚本
# 用法: ./backup.sh /path/to/backup

BACKUP_DIR="$1"
DEST_DIR="/data/backup"
DATE=$(date +%Y%m%d_%H%M%S)
KEEP_DAYS=7

# 检查参数
if [ -z "$BACKUP_DIR" ]; then
    echo "用法: $0 /path/to/backup"
    exit 1
fi

if [ ! -d "$BACKUP_DIR" ]; then
    echo "错误: $BACKUP_DIR 不是目录"
    exit 1
fi

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

# 备份
BACKUP_NAME=$(basename "$BACKUP_DIR")_${DATE}.tar.gz
echo "开始备份: $BACKUP_DIR -> $DEST_DIR/$BACKUP_NAME"
tar -czf "$DEST_DIR/$BACKUP_NAME" -C "$(dirname "$BACKUP_DIR")" "$(basename "$BACKUP_DIR")"

if [ $? -eq 0 ]; then
    echo "备份成功: $DEST_DIR/$BACKUP_NAME"
else
    echo "备份失败!"
    exit 1
fi

# 删除7天前的备份
echo "删除${KEEP_DAYS}天前的备份..."
find "$DEST_DIR" -name "*.tar.gz" -mtime +$KEEP_DAYS -delete

echo "备份完成!"

2. 监控脚本

#!/bin/bash
# 系统监控脚本,异常时发送邮件

ADMIN_EMAIL="admin@example.com"
HOSTNAME=$(hostname)
DATE=$(date "+%Y-%m-%d %H:%M:%S")

# 磁盘使用率检查
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 80 ]; then
    echo "[$DATE] 警告: 根分区磁盘使用率 ${DISK_USAGE}%,超过80%!" | mail -s "[$HOSTNAME] 磁盘空间告警" $ADMIN_EMAIL
fi

# 内存使用率检查
MEM_TOTAL=$(free -m | awk 'NR==2 {print $2}')
MEM_USED=$(free -m | awk 'NR==2 {print $3}')
MEM_USAGE=$((MEM_USED * 100 / MEM_TOTAL))
if [ $MEM_USAGE -gt 80 ]; then
    echo "[$DATE] 警告: 内存使用率 ${MEM_USAGE}%,超过80%!" | mail -s "[$HOSTNAME] 内存告警" $ADMIN_EMAIL
fi

# CPU负载检查
LOAD_1=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | sed 's/ //g')
CPU_CORES=$(nproc)
LOAD_INT=$(echo "$LOAD_1 * 100" | cut -d. -f1)
CORES_INT=$((CPU_CORES * 100))
if [ $LOAD_INT -gt $CORES_INT ]; then
    echo "[$DATE] 警告: 1分钟负载 $LOAD_1,超过CPU核心数 $CPU_CORES!" | mail -s "[$HOSTNAME] CPU负载告警" $ADMIN_EMAIL
fi

# 关键进程检查
for process in nginx mysql php-fpm; do
    if ! pgrep -x "$process" > /dev/null; then
        echo "[$DATE] 警告: 进程 $process 未运行!" | mail -s "[$HOSTNAME] 进程告警" $ADMIN_EMAIL
    fi
done

echo "[$DATE] 监控检查完成"

3. 日志切割脚本

#!/bin/bash
# Nginx日志切割脚本(配合cron每天凌晨执行)

LOG_DIR="/var/log/nginx"
KEEP_DAYS=30
DATE=$(date +%Y%m%d)

# 切割access.log
if [ -f "$LOG_DIR/access.log" ]; then
    mv "$LOG_DIR/access.log" "$LOG_DIR/access_${DATE}.log"
fi

# 切割error.log
if [ -f "$LOG_DIR/error.log" ]; then
    mv "$LOG_DIR/error.log" "$LOG_DIR/error_${DATE}.log"
fi

# 重新打开日志文件(发送USR1信号给Nginx)
kill -USR1 $(cat /var/run/nginx.pid)

# 压缩旧日志
find "$LOG_DIR" -name "access_*.log" -mtime +1 -exec gzip {} \;
find "$LOG_DIR" -name "error_*.log" -mtime +1 -exec gzip {} \;

# 删除30天前的日志
find "$LOG_DIR" -name "access_*.log.gz" -mtime +$KEEP_DAYS -delete
find "$LOG_DIR" -name "error_*.log.gz" -mtime +$KEEP_DAYS -delete

echo "[$(date)] 日志切割完成"

4. 批量重命名脚本

#!/bin/bash
# 批量重命名文件
# 用法: ./rename.sh /path/to/dir .txt .md

DIR="$1"
OLD_EXT="$2"
NEW_EXT="$3"

if [ -z "$DIR" ] || [ -z "$OLD_EXT" ] || [ -z "$NEW_EXT" ]; then
    echo "用法: $0 /path/to/dir 旧扩展名 新扩展名"
    exit 1
fi

cd "$DIR" || exit 1

count=0
for file in *"$OLD_EXT"; do
    if [ -f "$file" ]; then
        newname="${file%$OLD_EXT}$NEW_EXT"
        mv "$file" "$newname"
        echo "$file -> $newname"
        count=$((count+1))
    fi
done

echo "共重命名 $count 个文件"

五、Shell脚本最佳实践

1. 脚本规范

  • 开头加#!/bin/bash指定解释器
  • 加注释说明脚本用途、用法、作者、日期
  • 使用有意义的变量名
  • 变量名大写(环境变量)或小写(局部变量),保持一致
  • 函数名小写,用下划线分隔
  • 缩进统一(4个空格或Tab)
  • 代码分段,空行分隔

2. 安全实践

# 开启严格模式(出错即停,未定义变量报错,管道失败即停)
set -e          # 命令失败时退出
set -u          # 使用未定义变量时报错
set -o pipefail # 管道中任一命令失败则整个管道失败
# 可以合并写: set -euo pipefail

# 变量引用加双引号,防止单词分割和通配符展开
if [ -f "$file" ]; then ...

# 使用$()而不是反引号` `
date_now=$(date)  # 好
date_now=`date`   # 不推荐

# 用printf而不是echo(更可靠,特别是处理转义字符)
printf "姓名: %s, 年龄: %d\n" "$name" "$age"

# 临时文件用mktemp
tmpfile=$(mktemp)
# ... 使用tmpfile ...
rm -f "$tmpfile"  # 用完删除

3. 调试技巧

# 执行时显示命令(调试用)
bash -x script.sh

# 脚本中开启调试
set -x  # 开启
# ... 代码 ...
set +x  # 关闭

# 只读取不执行(检查语法)
bash -n script.sh

# 打印变量
echo "DEBUG: name=$name" >&2  # 调试信息输出到stderr

4. 性能优化

  • 尽量用内置命令,少调用外部命令(内置命令更快)
  • 减少管道中的命令数量
  • 大文件处理用awk/sed,不要用while read(慢)
  • 循环中避免重复执行相同命令
  • [[ ]]代替[ ](bash内置,功能更强,更快)
  • 批量操作时尽量用一条命令完成

5. 兼容性

  • 如果需要跨平台(macOS、BusyBox等),避免使用bash特有语法
  • #!/bin/sh和POSIX标准语法写可移植脚本
  • 检查命令是否存在:command -v curl >/dev/null || echo "curl未安装"

六、常见问题

1. 脚本报错"bad interpreter: No such file or directory"

原因:脚本是Windows格式(CRLF换行),Linux不识别。 解决:dos2unix script.shsed -i 's/\r$//' script.sh

2. 脚本报错"Permission denied"

原因:没有执行权限。 解决:chmod +x script.sh

3. 变量赋值后为空

原因:等号两边有空格(Shell变量赋值等号两边不能有空格)。

name = "张三"  # 错误!
name="张三"    # 正确

4. 条件判断不生效

原因:[ ]两边没有空格,或者变量为空导致语法错误。

if [$a -eq $b]; then    # 错误![后面和]前面必须有空格
if [ "$a" -eq "$b" ]; then  # 正确,变量加双引号防止为空

5. 循环遍历文件名有空格的文件出错

原因:for循环默认按空格分割。 解决:用find配合while read,或者用glob并加引号。

find . -name "*.txt" | while read file; do
    echo "$file"
done

6. 算术运算不生效

原因:Shell变量默认是字符串,需要用$((...))做算术。

a=10
b=20
c=$a+$b        # 错误,c是字符串"10+20"
c=$((a+b))     # 正确,c是30

总结

Linux命令行和Shell脚本是程序员和运维人员的必备技能。2016年,Linux已经成为服务器操作系统的主流,掌握Linux命令行和Shell脚本,能大幅提升工作效率,实现自动化运维。

本文从Linux简介、常用命令(文件操作、文本处理、系统管理、网络、进程、权限、压缩、软件包管理)、Shell脚本基础(变量、字符串、数组、条件判断、循环、函数、输入输出)、实用脚本案例(备份、监控、日志切割、批量重命名)、最佳实践、常见问题等方面,系统讲解了Linux命令行与Shell脚本实战。

Linux命令行学习的核心要点:

  1. 掌握常用命令(ls、cd、cp、mv、rm、cat、grep、sed、awk、find、ps、top、netstat等)
  2. 理解管道(|)和重定向(>、>>、2>&1)
  3. 掌握文本处理三剑客(grep、sed、awk)
  4. 学会用man和--help查看帮助
  5. 多练多用,在实际工作中积累

Shell脚本学习的核心要点:

  1. 掌握基本语法(变量、条件、循环、函数)
  2. 理解特殊变量($0、$1、$#、$@、$?、$$)
  3. 学会写实用脚本(备份、监控、自动化部署)
  4. 遵循最佳实践(严格模式、引号、注释、调试)
  5. 注意性能和兼容性

Linux命令行和Shell脚本的学习,没有捷径,只有多练多用。在实际工作中遇到问题,尝试用命令行和脚本解决,慢慢就熟练了。希望本文能帮助你入门Linux命令行和Shell脚本,从入门到精通,成为Linux高手。