先说说我对方才那个标题的直观感受。很多新手翻Linux的书,前面几章讲命令,后面几章忽然就跳到Shell脚本,中间衔接得很生硬,学完还是不知道Shell编程到底解决什么问题。我自己刚工作时也踩过这个坎:命令背了不少,一到写脚本就发怵,总觉得“编程”两个字很高深。后来被线上一次日志清理逼着写了第一个脚本,才发现Shell编程说白了就是“把你在终端里一条条敲的命令,组合成一整段可以重复执行的逻辑”,它在Linux里的角色,相当于你这个系统管理员的私人助理。这篇文章,就是按这个思路,把Shell是什么、为什么值得学、怎么上手、以及我踩过的一些坑,一次讲清楚。
如果你是刚接触Linux没多久、想知道怎么用脚本来偷懒的新人,或者已经会用一些命令、但写脚本总是四处报错、想系统梳理一遍的准中级用户,这篇文章都适合你。我先从概念讲起,然后逐步拆解脚本骨架、实战案例和排查技巧,保证每一步都能直接在终端里尝试。
1. Shell编程到底解决什么问题
1.1 Shell不是一个“软件”,而是一个解释器
说起Shell,很多人第一个问题就是:它到底是命令还是编程语言?这个困惑我也有过。简单说,Shell是一个程序,它最重要的职责是解释你输入的命令,然后调用Linux内核去完成实际操作。你可以把内核想象成一个公司后台,它管着文件、进程、设备这些核心资源,但它不会直接跟人对话,你需要一个前台。这个前台就是Shell,你告诉前台“帮我看看当前目录有什么”,前台转头跟后台说“给我列出当前目录的文件”,然后把结果显示给你。
在Linux里,这个前台程序可以是bash、zsh、fish或者sh中的任意一个。大多数发行版默认打开终端时运行的,都是bash。你平时敲的ls、cd、cat、grep这些命令,敲回车之后,Shell都会负责去找对应的程序、传递参数、把输出打印到屏幕上。而Shell编程,就是利用这个解释器自身的语法,把这些命令串起来,实现判断、循环、变量和函数调用,最终达到自动化的效果。
理解这一点非常关键。你不需要像学C语言或Java那样,先理解内存、指针、类型系统,再开始写东西。Shell编程的门槛在于“命令要熟”,而不是“语法要深”。一旦你熟悉了ls、find、awk、sed、grep这些命令,脚本其实是在调用它们来完成复杂流程,而不是每一个功能都得从头写。
1.2 常见的Shell家族:bash、sh、zsh、fish怎么选
市面上常见的Shell有几种,我列一个对照表方便你理解:
| Shell名称 | 全称/出身 | 特点 | 常见场景 |
|---|---|---|---|
| bash | Bourne Again SHell | Linux默认Shell,兼容性好,资料最多 | 绝大多数服务器脚本、系统运维 |
| sh | Bourne Shell | 最老的Shell,语法最基础 | 嵌入式环境、POSIX兼容要求高的场景 |
| zsh | Z Shell | 交互体验好,补全强大,兼容bash大部分语法 | 个人开发机的日常使用 |
| fish | Friendly Interactive SHell | 开箱即用,语法更友好,但bash兼容性差 | 个人终端体验优化,不建议做脚本 |
我个人建议,如果你刚开始学,或者要写跑在服务器上的脚本,直接选中bash,别犹豫。原因有三个:第一,bash是绝大多数Linux发行版默认的Shell,写出来的脚本到别的机器上,大概率直接能跑;第二,网上关于bash的教程、问答、排错经验是最多的,遇到问题搜得到答案;第三,即使你以后切换到zsh,bash的语法照样是通用的底子。
顺便提一句,很多人喜欢在脚本开头写上#!/bin/bash,这个叫shebang,作用是明确告诉系统“用/bin/bash这个程序来解释后面的脚本内容”。即使你当前交互式Shell是zsh或者fish,脚本文件里的shebang也能保证脚本用bash执行,这是一个很重要的习惯。
1.3 交互式命令 vs Shell脚本:思维方式的转变
你打开终端,敲一行命令回车,看到输出,这叫“交互式使用”。这种模式下,你是在跟Shell对话,每敲一次就执行一次。但真正的自动化,是把一系列命令提前写好,让Shell一次性按顺序执行,这就是脚本。
举个例子:你要在项目目录里把所有的.tmp文件删掉,再统计一下还剩多少文件。交互式操作要敲两次,第一次find . -name "*.tmp" -delete,第二次ls | wc -l。如果只是做一次,没问题。但如果你每天都要做一次,每次还希望保留删除记录、出错时能停下报警,那交互式命令就远远不够了。
脚本的好处是:把流程固定下来、可以传参数、可以判断条件、可以处理出错情况、可以搭配定时任务自动运行。本质上,Shell编程和普通编程一样,讲究的是三种核心结构:顺序执行、条件分支、循环。再加上函数封装和变量传递,就能胜任绝大多数日常操作类任务了。
另外一个常见的思维误区是:Shell脚本必须写得很“高级”。其实不是,一个优秀的脚本,往往是简单命令加上清晰的逻辑。很多运维高手的脚本,看起来一点都不花哨,重点在稳定和易理解。
2. 为什么说Shell编程是Linux的“第一生产力”
2.1 自动化:把重复劳动变成一句话
我最早体会到Shell编程的威力,是在一次日志迁移的过程中。当时的场景是:线上有十几台服务器,每台都有几十GB的日志文件,需要按日期归档、保留最近7天、删除更早的压缩包。如果一台一台手工操作,估计要忙上大半天。
后来我把命令拼成一个脚本,通过循环处理服务器列表,大概长这样:
for host in web01 web02 web03; do ssh "$host" 'find /var/log/app -name "*.log.*" -mtime +7 -delete' done当然,这个例子涉及远程连接,具体细节不展开。核心意思是,原本重复性很强的操作,一旦固化成脚本,以后每次只要运行一条命令,就完成了整批操作。Shell脚本在系统管理中的价值,一大半来自这种“批量处理”能力。
你不需要每次都写复杂的程序来处理文件、进程、服务和日志,Linux本身已经提供了大量好用的命令。Shell编程真正做的事,是把这些命令组织成一套可复用的流程。这就像是做菜:命令是已经处理好的食材,Shell脚本是你的菜谱。
2.2 组合哲学:小而美的命令是积木
Linux世界里有一个很有名的理念:一个程序只做好一件事,然后通过管道把它们组合起来。Shell脚本天然就是实践这个理念最好的场所。
举个最常见的例子:统计日志里某个接口的请求次数。
grep "GET /api/user/info" access.log | wc -l这一行同时用到了grep和wc两个命令,通过管道|连接。grep负责从日志中筛出符合条件的行,wc负责统计行数。它们各自都足够简单,但组合起来就完成了一个相对复杂的统计需求。
再比如说,找出当前目录里最大的5个文件:
ls -lS | head -6或者按时长列出最近修改的文件:
find . -type f -mtime -7 -exec ls -lh {} \;这些操作,如果放到Python里写,代码量和思考成本都会明显增加;但在Shell里,往往就是一行命令的功夫。Shell编程的高效,某种程度上来自于这种“站在命令的肩上”。每个命令都像一块积木,你要练的不是怎么自己造积木,而是怎么熟练地拼接它们。
2.3 和Python、C相比,Shell的优势到底在哪
经常有人问,既然Python也能写脚本,还会写复杂逻辑,为什么还要学Shell?这个问题我当初也纠结过。直接说结论:Shell不是万能的,但在系统管理和快速任务处理上,它比Python更直接。
我给你做一个简单的对比:
| 语言 | 适合场景 | 不适合场景 |
|---|---|---|
| Shell | 文件批量操作、进程管理、环境配置、命令行工具封装 | 复杂数据处理、算法实现、大规模Web应用 |
| Python | 数据分析、Web开发、自动化测试、需要面向对象的脚本 | 需要在命令行里直接操作大量Linux系统状态时,不够“原声” |
| C语言 | 系统底层开发、性能敏感场景 | 日常脚本开发投入产出比太低 |
Shell和Python不是取代关系,而是配合关系。我在实际工作中,一次性任务十有八九用Shell,因为打开终端就能写,不用想模块、依赖和虚拟环境;但是如果数据量比较大、要做统计图表,我就会把Shell筛选出来的结果放进Python继续处理。
有一个很现实的判断标准:如果你的任务主要是“操作系统资源”和“调用系统命令”,优先用Shell;如果你的任务主要是“处理业务逻辑和数据结构”,优先用Python。把命令、管道、重定向用到极致,Shell就是最高效的“胶水”,能快速把各种工具黏合在一起。
2.4 定时任务加监控:运维场景里的黄金搭档
Shell脚本在Linux里另一个特别常见的用法,是和crontab搭配实现定时自动化。所谓crontab,就是Linux内置的定时任务表。你可以指定每天的某个时刻、每周的某一天、或者每月的某一天运行某条命令。
我在实际项目里,很多巡检工作都是这么做的:每天早上8点,跑一个Shell脚本,检查磁盘使用率、内存占用、关键进程是否存活,然后把结果写入日志文件。如果发现异常,脚本会调用一个发消息的接口通知值班人员。
#!/bin/bash # 每天检查磁盘使用率,超过80%就输出警告 usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%') if [ "$usage" -gt 80 ]; then echo "警告:根分区使用率已达到 ${usage}%" >> /var/log/disk_check.log fi配合crontab的配置,只需要一行:
0 8 * * * /usr/local/bin/check_disk.sh这种组合用法,是Linux运维体系里的基础配制。系统管理员的日常,本质上就是“用脚本去管理机器”,而不是每天手动登录服务器去敲重复命令。学Shell编程,其实就是学会用机器的方式管理机器,把人的精力省下来处理真正需要判断的事情。
3. 从零搭建你的第一个Shell脚本
3.1 脚本骨架:shebang、编码、执行权限一个都不能少
我第一次写脚本,犯过两个很基础的错误:一是不知道文件开头该写什么,二是辛辛苦苦写完脚本,执行时报“Permission denied”。后来才明白,写Shell脚本有个固定骨架,三个要素缺一不可。
第一,文件开头写上shebang:
#!/bin/bash第二,内容用UTF-8编码,避免中文注释出现乱码。第三,给脚本加上执行权限:
chmod +x myscript.sh为什么需要shebang?因为当你在终端里执行./myscript.sh时,系统需要知道用什么解释器来读取文件内容。虽然bash是默认Shell,但显式声明更保险。尤其是你用了zsh或其他Shell的环境,服务器上可能并没有这个解释器。写成#!/bin/bash就明确要求用bash,无论当前交互式Shell是什么。
执行的三种方式,这里也一并说清楚。第一种是bash myscript.sh,显式用bash解释执行,不依赖执行权限也不需要shebang;第二种是./myscript.sh,依赖文件的可执行权限,同时依赖shebang指定的解释器;第三种是source myscript.sh或者. myscript.sh,这种执行方式比较特殊,它是在当前Shell进程中运行的,等同于手动输入脚本里的每一行,所以脚本中对环境变量的修改会保留下来,而前两种方式是启动子Shell执行,脚本里的环境变量修改不会影响到当前终端。
大多数情况下,推荐写通用脚本用第二种方式,因为更接近“运行程序”的体验;但如果你写的是需要改变当前终端环境(比如切换目录、设置环境变量、进入虚拟环境)的工具脚本,就得用第三种source方式。
3.2 变量、引号与字符串处理:Shell脚本的地基
变量是任何编程语言的基础,Shell里的变量有几个很容易踩的坑,我一个个说。
定义变量要注意“等号两边不能有空格”:
name="张三" # 正确 name = "张三" # 错误,会被解析为命令加参数使用变量时,推荐加花括号:
echo "${name}是项目负责人"为什么不直接用$name?因为如果变量名后面紧跟其他字符,Shell会把整个单词当变量,比如$name123会被解析为变量name123,而${name}123可以明确表示变量name加上常量123。写复杂字符串拼接时,花括号能少很多莫名其妙的bug。
引号的使用更是Shell里的重头戏。双引号里的变量会被展开,单引号里的内容则原样保留:
name="张三" echo "你好,$name" # 输出:你好,张三 echo '你好,$name' # 输出:你好,$name反引号和$( )用来做命令替换,也就是把命令的输出结果保存到变量里:
now=$(date +%Y-%m-%d) echo "今天是${now}"现在主流写法已经不太推荐反引号了,因为$( )嵌套性更好,比如$(dirname "$(pwd)")这种写法,反引号基本做不到。而且$( )里外引号的区别看得更清楚,不容易搞混。
字符串连接在Shell里很简单,直接写就行:
prefix="log" suffix="20250411" filename="${prefix}_${suffix}.tar.gz"这里再重提一遍花括号的作用:prefix_suffix会被当成一个变量名,而${prefix}_${suffix}才能得到log_20250411。这个细节,几乎每个写Shell脚本的人都会踩到。
3.3 分支与循环:让脚本具备真正的“智能”
有了变量,脚本还只是顺序执行的一堆命令。真正让脚本有逻辑的,是分支和循环。最常用的分支就是if判断:
if [ -f /etc/passwd ]; then echo "系统用户文件存在" else echo "文件不存在,需要注意" fi这里很容易出问题的地方是空格。[和]两边必须有空格,[ -f /etc/passwd ]写成[-f /etc/passwd]就会报错。我第一次写脚本时,在if行后面少了空格,直接报command not found: [-f,排查了半天才回过神。
循环也是脚本常用的结构。最经典的是for循环遍历文件:
for file in /tmp/sh/*.txt; do echo "正在处理:${file}" wc -l "${file}" donewhile循环一般配合读取文件内容使用:
while read -r line; do echo "日志行内容:${line}" done < /var/log/messages.log循环里经常需要跳过条件,break是跳出整个循环,continue结束本次循环进入下一次。实际写脚本时,我个人习惯在循环体内尽量多写注释,因为循环逻辑一多,隔几天再看就想不起当时为什么要做某个判断了。
3.4 函数封装:脚本也能做到“模块化”
当脚本的逻辑越来越长,直接堆在主体里会很混乱,而且重复代码会变多。这时候就需要函数。
function check_dir() { local dir=$1 if [ -d "${dir}" ]; then echo "目录存在:${dir}" else echo "目录不存在,正在创建:${dir}" mkdir -p "${dir}" fi } check_dir "/data/backup" check_dir "/data/logs"这里注意local关键字,它用来声明局部变量,只在当前函数内有效。Shell里如果不用local,定义出来的变量全部是全局的,函数间互相影响,调试时特别头疼。我自己写函数时习惯全都加上local,这也算是一个保命习惯。
返回值的概念也和很多语言不同。Shell函数里的return返回的是退出状态码,0表示成功,非0表示失败,而不是真正的“返回数据”。如果需要函数“返回值”,一般有两种方式:一是通过echo输出,然后在外面用$(func_name)捕获;二是通过全局变量传递,比如:
get_status() { local code=$1 if [ "${code}" -eq 0 ]; then status_str="成功" else status_str="失败" fi } get_status 0 echo "本次操作${status_str}"这种方式虽然看起来不够优雅,但在Shell脚本里非常实用。函数封装让脚本结构更清晰,也让复用变得容易。比如上面那个检查目录的函数,后续所有需要建目录的脚本,都可以复制这段过去用。
4. 实战:日志清理脚本一步步写出来
4.1 需求分析:脚本要做什么,不做什么
理论讲了一堆,下面实际操作一遍。假设我们有一个典型的业务日志目录/data/applogs,里面的日志文件会按小时切分,比如app_2025041108.log。时间一长,这个目录占用越来越大,磁盘空间经常报警。现在需要写一个脚本,完成这几个目标:
- 删除目录中修改时间超过7天的
.log文件; - 删除完成后,在本地记录一份删除清单;
- 如果磁盘空间仍然不足,额外输出提示;
- 脚本本身不能出现误删,不能动目录下的其他文件。
写脚本之前先想清楚“不做什么”,这比“做什么”还重要。日志清理最怕的就是误删。所以这个脚本里,我宁愿让它老实地只删.log后缀的文件,也不要自作聪明地去处理压缩包。压缩包的删除逻辑应该由另一个单独脚本负责,这样责任边界清晰,排错也容易。
4.2 先手动跑通命令,再固化成脚本
写任何脚本,我的习惯都是先把核心命令在终端里手动试通。这里最核心的命令是找出符合条件的文件并且删除:
find /data/applogs -type f -name "*.log" -mtime +7 -delete我来拆一下这条指令。find是Linux中功能很强的查找命令,-type f限定只看普通文件,避免碰到目录;-name "*.log"限定文件名后缀;-mtime +7是指修改时间大于7天的文件;最后的-delete表示找到后直接删除。
这里有一个词需要特别解释,就是-mtime +7的含义。它表示“文件的最后修改时间到现在,已经被7个24小时周期还要长”,通俗讲就是修改时间在7天之前。测试的时候,我们可以用ls -l先看一下文件的修改时间,再用find配合-mtime查找,确认结果符合预期再实际删除。如果担心-delete直接删掉没有提示,可以把-delete换成-exec ls -l {} \;先看一眼:
find /data/applogs -type f -name "*.log" -mtime +7 -exec ls -l {} \;这样只是列出,不会删除。确认列表没问题,再换成-delete执行。
4.3 安全加固:先dry-run再真正执行
find -delete是挺狠的,删了就找不回来。所以正式脚本里,我会加一道保险:默认处于试运行模式,只有传了真正执行的参数才会实际删除。改动如下:
#!/bin/bash log_dir="/data/applogs" days=7 dry_run=true if [ "$1" == "--delete" ]; then dry_run=false fi if [ "${dry_run}" == true ]; then echo "【试运行】以下文件将被清理:" find "${log_dir}" -type f -name "*.log" -mtime +${days} -exec ls -lh {} \; else echo "【正式执行】开始清理超过${days}天的日志文件..." find "${log_dir}" -type f -name "*.log" -mtime +${days} -delete fi这个脚本设计的思路是:默认情况下执行安全模式,只是列举出会被删除的文件,方便人工确认。等确认无误,再追加--delete参数让脚本真正干活。把“能不能删”这个决定权留给人,而不是让脚本自作主张。这是我写所有“有破坏性”脚本时的一个底线原则。
4.4 记录删除日志与处理退出码
光能删还不够,脚本最好能告诉我们“删了什么、删了多少、过程有没有报错”。给脚本加上计数和日志记录:
#!/bin/bash log_dir="/data/applogs" days=7 dry_run=true log_file="/var/log/log_clean.log" if [ "$1" == "--delete" ]; then dry_run=false fi if [ "${dry_run}" == true ]; then echo "【试运行】以下文件将被清理:" find "${log_dir}" -type f -name "*.log" -mtime +${days} -exec ls -lh {} \; exit 0 fi # 正式开始前记录时间 echo "$(date '+%F %T') 清理开始" >> "${log_file}" # 统计删除前的文件数量 count_before=$(find "${log_dir}" -type f -name "*.log" | wc -l) # 执行删除 find "${log_dir}" -type f -name "*.log" -mtime +${days} -delete # 检查上一条命令是否执行成功 if [ $? -eq 0 ]; then count_after=$(find "${log_dir}" -type f -name "*.log" | wc -l) echo "$(date '+%F %T') 清理完成,删除前${count_before}个,删除后${count_after}个" >> "${log_file}" else echo "$(date '+%F %T') 清理过程出现错误,请检查脚本" >> "${log_file}" exit 1 fi这里用到$?,表示上一条命令的退出状态码。命令执行成功时返回0,失败返回非0。通过判断它,我们能在脚本里获得“上一步是否成功”的信息,这也是Shell编程里比较基础的错误处理方式。还有exit 1,用来主动让脚本以非0状态退出,这样外部调用方(比如监控系统)就能从退出码发现脚本执行失败。
最后说一下这个脚本的部署思路:把它放到/usr/local/bin/下,命名为clean_logs.sh,加执行权限,再通过crontab每天凌晨运行一次,比如:
30 2 * * * /usr/local/bin/clean_logs.sh --delete如果哪天发现磁盘空间提前报警,也可以手动跑一次脚本排查。这个案例的完整流程,从分析目标、设计命令、加固、加日志到定时部署,就是我平时在实际系统中处理重复运维任务的标准思路。
5. 用Shell编程时最常见的坑和排查思路
5.1 变量赋值与空格问题:一半的语法错误都源于此
Shell对空格极其敏感,这跟大多数现代编程语言很不一样。变量赋值时等号两边不能有空格name="hatlonely",而条件判断里的空格一个都不能少[ "$name" = "hatlonely" ]。
我在培训新人时,经常看到这样的错误:
if [$name == "test" ] # 缺少[和变量之间的空格 if [ $name=="test" ] # 等号两边缺空格第一种会报command not found: [name,因为Shell把[$name当成一个命令名了。第二种不会直接报错,但条件判断逻辑会出错,比较的是整个字符串"abc==test"这种内容,而不是abc和test这两个值。
我的经验是,写if语句时习惯性地在心里默念一遍:左括号空格变量空格运算符空格变量空格右括号。这样能减少八成低级错误。
5.2 文件名带空格导致的“裂开”问题
假设当前目录下有这样一个文件:my report.txt。很多人会写出这样的命令:
for file in $(ls *.txt); do echo "${file}" done这个脚本表面看没问题,实际上在shellcheck的严苛检查下会被打几个警告。因为如果文件名里有空格,$(ls *.txt)会把这个文件拆成两个词:my和report.txt,然后循环会执行两次,第二次还报错说找不到report.txt这个文件。
比较稳的写法是利用Shell自身文件名展开的特性:
for file in *.txt; do echo "${file}" done这种方式不会对文件名做分词,即使包含空格也能正确遍历。这个坑很经典,也是我早期排查“明明目录里有文件,脚本却找不到文件”这类问题的常见原因。
5.3 换行符CRLF问题:Windows写脚本的经典报错
如果你习惯在Windows上用一些编辑工具写脚本,然后把文件扔到Linux服务器上运行,很可能会遇到一个非常隐蔽的报错:bad interpreter: /bin/bash^M: no such file or directory。
这个^M就是Windows和Linux换行符差异导致的。Linux下文本文件的行由LF结尾,而Windows下是CRLF结尾。多出来的CR字符,让系统在读取#!/bin/bash这行时,把/bin/bash^M当成了解释器的路径,自然就找不到了。
解决方案有两个:一个是在Windows编辑器里设置保存为Unix换行符(也就是LF);另一个是到Linux服务器上用命令转换:
sed -i 's/\r$//' myscript.sh以后如果遇到脚本报一些奇怪的错,先检查一下是不是换行符问题。我经常线上排查脚本问题,第一件事就是看文件编码和换行符。这种坑莫名其妙,但只要知道机制,一分钟就能解决。
5.4[ ]和[[ ]]的差别与使用选择
Shell里条件判断可以用单中括号[ ],也可以用双中括号[[ ]]。它们的差异不只是多一个括号那么简单。
单中括号是兼容POSIX的老式写法,里面用比较符号时需要特别小心。比如>和<在单中括号里不是字符串比较,而是输出重定向和输入重定向,很可能造成文件被改写,这是很危险的操作。而双中括号是bash的扩展特性,里面>和<直接表示字符串比较,更符合多数人的预期,还支持&&、||这些逻辑操作符:
if [[ "$name" == "hatlonely" && "$age" -gt 18 ]]; then echo "满足条件" fi我的建议是:如果你明确知道自己用的是bash,就尽量写[[ ]],可读性更好,还能少踩一些坑;但如果脚本要求兼容sh,就必须用[ ],并且注意不要在里面使用bash专有语法。这种兼容性判断,在写需要部署到很多环境里的脚本时尤其重要。
5.5 排查技巧:set -x和echo大法谁更好用
写脚本一时爽,排查火葬场。我自己排查Shell脚本问题,通常分两步走。
第一步,在脚本开头加上set -x。这个选项会把脚本里每一条实际执行的命令连同展开后的结果都打印出来。比如脚本里写了echo "${file}",set -x会把这一行展开成实际内容,一目了然地看清变量是否赋值正确、文件名是否被拆分。等排查完,把set -x注释掉就行。
第二步,如果问题比较复杂,我会在关键节点手动加echo输出中间变量:
echo "当前处理的文件:${file}"很多人管这叫“echo大法”,虽然原始,但确实有效。尤其遇到循环逻辑复杂的情况,在循环里打印一行状态信息,比盯着代码干想快得多。
还有一个好用的静态检查工具,叫shellcheck,它可以指出脚本里的语法问题和常见不良实践。我在写完稍微复杂一点的脚本后,都会用它跑一遍:
shellcheck myscript.sh操作类任务之外的脚本,加一个静态检查步骤,能大幅减少上线后才发现问题的概率。
最后分享一点个人的使用体会
这篇文章写着写着,又让我想起自己第一段和Shell脚本打交道的日子。回头看,我觉得最受用的经验只有一条:先能在命令行里手动敲通,再固化成脚本;先保证安全,再追求速度。很多新手一上来就想写一段“惊艳”的脚本,结果被各种细节绊倒。其实Shell编程带给人的信心,恰恰来自那些看起来很笨的尝试——你亲手删了一个文件,亲手写错过一个变量名,亲手排查过一次诡异的空格报错,下一次自然就会长记性。希望这篇文章能帮你少走一些我走过的弯路。从今天起,试着把终端里那些重复出现的命令,固化成你的第一个脚本吧。