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

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

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-20250908223947089image-20250908223947089

基础语法和变量变量定义和使用Shell中的变量定义很简单,直接写就行了:

代码语言:javascript复制name="张三"

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

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

代码语言:javascript复制echo $name

echo "我的名字是$name,今年$age岁"还有一种更安全的写法,用花括号把变量名括起来:

代码语言:javascript复制echo "我的名字是${name},今年${age}岁"这样做的好处是避免变量名和其他字符混在一起造成歧义。

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

• $0 - 脚本名称• 1, 2, • $# - 参数个数• $? - 上一个命令的退出状态• $$ - 当前进程ID举个例子,如果你有个脚本叫test.sh,然后这样运行:

代码语言:javascript复制./test.sh hello world那么在脚本里面:

• $0 就是 ./test.sh• $1 就是 hello• $2 就是 world• $# 就是 2这些特殊变量在写脚本的时候特别有用,特别是处理命令行参数的时候。

条件判断和流程控制if语句Shell的if语句语法稍微有点特别:

代码语言:javascript复制if [ condition ]; then

# 执行的命令

elif [ another_condition ]; then

# 另一个条件

else

# 默认执行的命令

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

常用的条件判断:

代码语言:javascript复制# 数字比较

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复制case $1 in

"start")

echo "启动服务"

;;

"stop")

echo "停止服务"

;;

"restart")

echo "重启服务"

;;

*)

echo "未知参数"

;;

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

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

代码语言:javascript复制# 遍历列表

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复制count=1

while [ $count -le 5 ]; do

echo "第${count}次循环"

count=$((count + 1))

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

代码语言:javascript复制while read line; do

echo "读到一行:$line"

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

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

代码语言:javascript复制function hello() {

echo "Hello, $1!"

}

# 或者更简单的写法

hello() {

echo "Hello, $1!"

}

# 调用函数

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

代码语言:javascript复制check_file() {

if [ -f "$1" ]; then

return 0 # 成功

else

return 1 # 失败

fi

}

if check_file "/etc/passwd"; then

echo "文件存在"

else

echo "文件不存在"

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

代码语言:javascript复制get_date() {

echo $(date +%Y-%m-%d)

}

today=$(get_date)

echo "今天是:$today"数组操作Shell也支持数组,虽然功能比较简单:

代码语言:javascript复制# 定义数组

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复制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复制# 创建文件

touch newfile.txt

# 复制文件

cp source.txt dest.txt

# 移动/重命名文件

mv oldname.txt newname.txt

# 删除文件

rm unwanted.txt

# 创建目录

mkdir newdir

# 删除目录

rmdir emptydir

rm -rf nonemptydirI/O重定向重定向是Shell的一个重要特性:

代码语言:javascript复制# 输出重定向

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复制#!/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复制#!/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复制#!/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复制# 开启调试模式

set -x # 显示执行的每一条命令

set -e # 遇到错误立即退出

set -u # 使用未定义变量时报错

# 或者在脚本开头加上

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

错误处理代码语言:javascript复制# 检查命令执行结果

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复制# 避免在循环中调用外部命令

# 不好的写法

for file in *.txt; do

lines=$(wc -l < "$file")

echo "$file: $lines lines"

done

# 更好的写法

wc -l *.txt代码语言:javascript复制# 使用内置命令替代外部命令

# 不好的写法

result=$(echo $string | cut -c1-5)

# 更好的写法

result=${string:0:5}代码规范代码语言:javascript复制#!/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复制# 错误的写法

if [ $var= "hello" ]; then # 等号前后不能有空格(在变量赋值时)

echo "world"

fi

# 正确的写法

var="hello"

if [ "$var" = "hello" ]; then # 比较时等号前后要有空格

echo "world"

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

代码语言:javascript复制name="张三"

# 单引号:原样输出,不解析变量

echo '我的名字是$name' # 输出:我的名字是$name

# 双引号:解析变量

echo "我的名字是$name" # 输出:我的名字是张三

# 不加引号:可能会有问题

echo 我的名字是$name # 如果name包含空格就会出问题路径问题处理文件路径时要特别小心:

代码语言:javascript复制# 不好的写法

cd /some/path

rm -rf * # 如果cd失败,这条命令会在当前目录执行!

# 更安全的写法

cd /some/path || exit 1

rm -rf *

# 或者使用绝对路径

rm -rf /some/path/*数组和字符串混淆代码语言:javascript复制# 定义数组时的常见错误

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复制# 命令替换

current_date=$(date +%Y-%m-%d)

file_count=`ls | wc -l` # 旧式写法,不推荐

# 进程替换

diff <(sort file1.txt) <(sort file2.txt)参数扩展代码语言:javascript复制filename="document.pdf"

# 获取文件名和扩展名

basename=${filename%.*} # document

extension=${filename##*.} # pdf

# 设置默认值

config_file=${CONFIG_FILE:-"/etc/default.conf"}

# 参数长度

echo ${#filename} # 12正则表达式代码语言:javascript复制# 使用=~操作符进行正则匹配

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"

fiHere Document代码语言:javascript复制# 生成配置文件

cat > /etc/myapp.conf << EOF

server_name = $SERVER_NAME

port = $PORT

debug = true

EOF

# 多行字符串

message=$(cat << 'EOF'

这是一个

多行的

消息内容

EOF

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

与数据库交互代码语言:javascript复制# 连接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复制# 使用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复制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. 先熟悉基本的Linux命令,像ls、cd、grep、awk、sed这些,这是基础2. 然后开始写一些简单的脚本,比如文件备份、日志清理之类的3. 遇到问题多查文档,man命令是你的好朋友4. 看看别人写的脚本,学习他们的思路和技巧网上有很多不错的学习资源,比如《Shell 编程范例》《Shell编程基础》这本书,内容很全面。还有一些在线的教程网站,像菜鸟教程、实验楼等等,都有不错的Shell教程。

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

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

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

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

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

公众号:运维躬行录

个人博客:躬行笔记

相关阅读

阿里巴巴能借钱吗?真实体验+避坑指南分享
久发365电子游戏网址多少

阿里巴巴能借钱吗?真实体验+避坑指南分享

🕒 10-26 👁️‍🗨️ 1573
鱸的解释
365bet官网赌场

鱸的解释

🕒 11-05 👁️‍🗨️ 1705
性格闷是什么意思
365bet官方投注网址

性格闷是什么意思

🕒 10-05 👁️‍🗨️ 6850
和包怎么充值余额,如何使用和包APP充话费的简单步骤
365bet官方投注网址

和包怎么充值余额,如何使用和包APP充话费的简单步骤

🕒 07-30 👁️‍🗨️ 6885
聚焦《执剑江湖》开服时间,踏上武侠之旅
365bet官网赌场

聚焦《执剑江湖》开服时间,踏上武侠之旅

🕒 06-28 👁️‍🗨️ 1936
完美世界:石昊的有几位挚友兄弟,分别是谁?他们的结局如何?
365bet官方投注网址

完美世界:石昊的有几位挚友兄弟,分别是谁?他们的结局如何?

🕒 08-31 👁️‍🗨️ 2888
爱卡族金融一体机是真的吗?爱卡族金融一体机靠谱吗?[图]
365bet官方投注网址

爱卡族金融一体机是真的吗?爱卡族金融一体机靠谱吗?[图]

🕒 08-22 👁️‍🗨️ 3886
DNF遗忘的陨石哪里掉率最高
久发365电子游戏网址多少

DNF遗忘的陨石哪里掉率最高

🕒 07-28 👁️‍🗨️ 1079
怎么折纸立体菠萝和叶子的折法图解步骤
365bet官方投注网址

怎么折纸立体菠萝和叶子的折法图解步骤

🕒 10-19 👁️‍🗨️ 8434