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 arg33. 字符串操作
#!/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 "无效"
;;
esac6. 循环
#!/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"
done7. 函数
#!/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 # 调试信息输出到stderr4. 性能优化
- 尽量用内置命令,少调用外部命令(内置命令更快)
- 减少管道中的命令数量
- 大文件处理用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.sh 或 sed -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"
done6. 算术运算不生效
原因: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命令行学习的核心要点:
- 掌握常用命令(ls、cd、cp、mv、rm、cat、grep、sed、awk、find、ps、top、netstat等)
- 理解管道(|)和重定向(>、>>、2>&1)
- 掌握文本处理三剑客(grep、sed、awk)
- 学会用man和--help查看帮助
- 多练多用,在实际工作中积累
Shell脚本学习的核心要点:
- 掌握基本语法(变量、条件、循环、函数)
- 理解特殊变量($0、$1、$#、$@、$?、$$)
- 学会写实用脚本(备份、监控、自动化部署)
- 遵循最佳实践(严格模式、引号、注释、调试)
- 注意性能和兼容性
Linux命令行和Shell脚本的学习,没有捷径,只有多练多用。在实际工作中遇到问题,尝试用命令行和脚本解决,慢慢就熟练了。希望本文能帮助你入门Linux命令行和Shell脚本,从入门到精通,成为Linux高手。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录