Shell这东西我劝所有跟Linux服务器打过照面的人都老老实实把它学一遍。不管是写部署脚本、批量改文件名、还是排查线上日志你迟早要在命令行里和它正面相遇。很多人觉得Shell是运维的事写代码的人用不着这个想法我特别不认同——我自己见过太多开发同事因为不会写简单的循环和grep过滤把大量重复操作耗在手动敲命令上。这篇内容就是一本按实操场景组织的Shell手记。核心定位很明确让零基础的人能看懂Shell是什么让会一点的人能补齐变量、循环、grep、expect、参数处理这些高频痛点让所有人避开我在真实环境里踩过的那些坑。适合三类人刚接触Linux的命令行新手写过几个脚本但总是出bug的半吊子以及想用Shell替代日常重复操作的效率控。1. 先搞明白Shell是什么再谈学脚本1.1 Shell是“翻译官”而不是操作系统本身我经常碰到有人把Shell和Linux混为一谈其实Shell就是一个命令解释器它夹在用户和操作系统内核之间。你敲一行命令Shell把这行文字解析成内核能理解的系统调用然后把执行结果再翻译成人能看懂的输出。整个过程可以类比成餐厅里点菜你是客人后厨大厨是内核Shell就是那个拿着菜单来回跑的服务员。你不需要知道大厨怎么颠勺你只需要告诉服务员你要什么。为什么它叫“Shell”就是因为它在内核外面包了一圈像一个壳。有壳自然就有不同的壳Linux上常见的有bash、zsh、ksh、fishWindows那边还有PowerShell。我们平时说的“进入Shell”“命令行”“终端”本质上都是同一个东西的不同叫法。当你打开终端软件看到[userhost ~]$这个提示符时你就已经处在一个Shell环境里了。1.2 为什么教程都拿Bash当主角市面上的Shell教程十条有九条都在讲Bash这不是没有原因的。Bash是绝大多数Linux发行版默认的登录Shell从CentOS到Ubuntu从Debian到Fedora开机进系统默认就是它。这意味着你在任何一台Linux服务器上写的Bash脚本大概率都能直接跑。再加上网上社区沉淀的脚本资源几乎全是用Bash语法写的学它性价比最高。写脚本第一行通常会写#!/bin/bash这行叫shebang作用是告诉系统用哪个解释器来执行这个文件。有时候也会看到#!/usr/bin/env bash这种写法是先去PATH环境变量里找bash好处是脚本在自定义安装路径下也能运行坏处是稍微多一层解析。我个人习惯在正规的生产脚本里写#!/bin/bash简单直接。这里有个必须提醒的坑很多系统上的/bin/sh并不等于bash。比如在Debian系里/bin/sh是dash一个精简版解释器不支持[[ ]]、数组这些Bash特性。如果你在sh脚本里写了Bash专属语法换个环境就报错。所以自己写脚本时要么明确用bash要么老老实实只写POSIX兼容语法。1.3 学习Shell之前先把环境准备好我见过太多人“学Shell”就是打开手机看教程结果一动上手发现连验证的地方都没有。Shell这东西光看不练假把式你至少需要一台能跑Linux命令的机器。本地虚拟机、云服务器、WSL都行我个人的建议是WSL对Windows用户最友好装个Ubuntu子系统几行命令就把环境拉起来了。不过要注意WSL的文件系统和真实Linux有些细微差异写脚本时别太依赖它做性能测试。环境准备好后你还需要一个文本编辑器。服务器上最常用的是vim但新手用起来负担有点重可以先用nano或者本机用VS Code编辑好再传上去。写脚本文件时文件名别带空格后缀用.sh只是约定俗成真正决定解释方式的是第一行的shebang不是后缀。准备动手时先写一个最简单的脚本试试流程新建文件、写入#!/bin/bash和一句echo hello shell、用chmod x test.sh加执行权限、最后./test.sh执行。很多人第一次挂在这就是因为忘了加执行权限其实你用bash test.sh也一样能跑区别只是后者绕过了文件的可执行位。初学者阶段这两种方式随便用但到了生产环境建议都按标准流程来。2. 脚本地基变量、输入与参数处理2.1 变量赋值与引用等号两边为什么不能乱加空格Shell的变量规则和编程语言不太一样最让人抓狂的就是赋值语句。nametom这样写才对但你要是手一抖写成name tom脚本直接报错。原因在于Shell默认用空格分隔命令和参数name会被当成一条命令去执行和tom成了参数自然找不到这个命令。这个坑几乎每个新手都踩过而且报错信息还不直观。引用变量时用$name或${name}两种写法在大多数场景下等价但后者有不可替代的作用。比如你想拼接字符串${name}123如果写成$name123Shell会把name123当成变量名结果一片空白。又比如你需要在变量后面跟默认值${name:-默认值}这种写法在脚本里非常实用。所以我的建议是单变量用$name没问题一旦涉及拼接或复杂表达式一律用${}包起来。还有个细节是关于引号的。双引号里的$变量会被解析成实际值单引号里的$变量就是四个字符原样输出。比如echo $name输出tomecho $name输出$name。这个区别在写日志、拼路径时特别容易出问题一定要记牢。2.2 位置参数与特殊变量$0、$#、$、$?到底代表什么写脚本免不了跟参数打交道。$0是脚本自身的名字$1是第一个参数$2是第二个依此类推。后面的$#表示参数个数$和$*表示所有参数$?是上一条命令的退出码$$是当前脚本的进程ID。这几个变量是脚本里最常用的基础设施理解它们能解决你一半的困惑。$和$*的区别是经典考点。不加引号时两者几乎一样加了引号就有讲究$把每个参数当成独立的单词$*把所有参数拼成一个字符串。比如调用脚本时传了file 1.txt file 2.txt两个参数用$遍历能分别处理两个文件用$*得到的是file 1.txt file 2.txt这么一整串。所以循环处理参数时我永远用$。$?这个东西特别有用。Shell里命令成功返回0失败返回非0值。你有两种方式使用它一种是直接echo $?看上一条命令结果另一种是配合后面要讲的if语句做条件判断。我调试脚本时经常在关键命令后面加一句echo $?几秒钟就能定位到哪一步挂了。关于位置参数还有一个实用技巧$0能用来做脚本自诊断。比如在脚本开头检查$#是否等于预期值不够就直接echo 用法: $0 参数1 参数2然后退出。这个习惯能让你写的脚本给别人用时少挨骂。2.3 交互式输入read命令与后台运行时的密码难题脚本需要接收用户输入时最常用的就是read命令。read -p 请输入用户名: user会在屏幕上打印提示然后把用户输入的内容存进user变量。如果输入的是密码可以用read -s用户敲键盘时屏幕不显示任何字符避免密码被别人看到。配合read -t 5还能设置等待超时防止脚本挂在那里等用户半天。但这里有个很多人搜了又搜的问题脚本在后台运行比如加了或者通过计划任务调用时read命令根本不会等用户输入而是立刻失败或者读到空值。原因很简单后台进程没有关联终端没有东西能给它喂输入。我就见过一个同事把带交互的脚本放进cron结果每天定时任务都报错他排查半天才发现是卡在read上。解决思路有三种。第一种是把需要输入的内容改成脚本参数调用时直接传值第二种是写配置文件脚本运行时从文件读取第三种是今天后面专门讲到的expect工具它可以模拟人的交互自动往进程里送密码。做运维脚本时我强烈建议优先用前两种方案因为它们不依赖外部工具逻辑也最稳。2.4 shift命令不带参数的参数“左移术”shift这个命令知道的人不少真正熟练用的人不多。它的作用是把位置参数整体往左移一位原本的$2变成$1$3变成$2以此类推。没带数字时默认移一位shift 2可以一次移多位。配合while循环就能像队列一样逐个处理参数这是写命令行解析器的标准做法。看一个典型场景。假设你要写一个脚本支持-h显示帮助、-f 文件名指定文件、-v输出详细信息。用shift处理时代码大致长这样while [ $# -gt 0 ]; do case $1 in -h) echo 用法: myscript [-f 文件名] [-v] exit 0 ;; -f) file$2 shift 2 ;; -v) verbose1 shift ;; *) echo 未知参数: $1 exit 1 ;; esac done注意看-f后面带了值所以处理它时要shift 2把参数本身和它的值一起移掉-v后面没值shift一次就够了。这种写法的好处是循环次数永远和参数数量匹配不会乱。我封装日常工具脚本时都是这个模式加上case分支扩展新参数只需要增加一个分支维护成本很低。3. 循环与判断脚本逻辑从这里开始3.1 for循环的三种写法你至少得会用两种for循环是Shell脚本里出现频率最高的控制结构但很多人只会一种写法。先说第一种遍历列表for i in 1 2 3 4 5; do echo 数字: $i done注意in后面是用空格分隔的元素不是逗号。这种写法最简单直观适合元素数量确定的小场景。它后面接的甚至可以不是静态列表而是任意命令的输出比如for file in $(ls *.log)Shell会把ls的结果拆成一个个元素放进去循环。这里有个隐患后面会细说如果文件名带空格这种写法会出事。第二种是C风格循环和C语言的for长得几乎一样for ((i1; i10; i)); do echo 第 $i 次 done适合明确知道循环次数的场景。第三种其实是第一种的变种遍历通配符结果for file in *.txt; do echo 处理文件: $file doneShell在解析*.txt时会自动扩展成当前目录下所有匹配的文件名不用你手动列。日常写脚本第一和第三种最常用第二种在需要计数器时意义重大。三种都建议练熟因为读别人脚本时经常三种混着出现。3.2 while循环逐行读文件的标准姿势while经常用来做“直到某条件不满足才停”的循环但对我们日常来说最高频的场景是逐行读文件。标准写法是while IFS read -r line; do echo 当前行: $line done input.txt这里input.txt会在循环结束时被Shell重定向进来每次循环读取一行存进line变量。两个细节值得解释IFS是把字段分隔符设为空防止行内的空格被拆开-r表示保留反斜杠字符防止路径里的\被吃掉。如果你写脚本处理带目录路径的文本漏了任何一个都可能莫名出错。除了读文件while还能用来做死循环配合break和continue控制流程。死循环写法是while :或while true:是Shell内置的空命令永远返回0所以循环不会自己结束。监控进程、持续探测端口这种定时任务场景会用到写的时候记得加sleep不然CPU会被你的脚本打满。3.3 if判断与test方括号里的每一个空格都有意义Shell的if判断写起来真的很让人抓狂因为它对空格敏感。最简单的字符串比较if [ $name tom ]; then echo 是tom fi注意[和]前后都有空格两边也有空格少了任何一个都会报错。原因很简单[实际上是一个命令而不是语法符号命令和参数之间当然要用空格分隔。理解了这点再看到[报错就不会一头雾水了。判断数值大小用的是-eq、-ne、-gt、-lt注意不是和。比如[ $count -gt 5 ]表示count大于5。判断文件就要用-f、-d、-x、-e这些选项比如[ -f /etc/passwd ]检查文件是否存在且是普通文件[ -d /tmp ]检查目录。字符串比较和文件判断混合使用时容易记混我的速记法则是看到比字符串看到-eq比数字看到-f/-d看文件。在新版Bash里更推荐用[[ ]]替代[ ]因为[[ ]]是Shell关键字不做单词拆分变量没值也不会报语法错误还支持、||和正则匹配。不过要记住[[ ]]是Bash特性脆弱的sh环境不支持跨环境脚本别过度依赖。3.4 case分支比if链更清爽的多路选择当你需要判断一个变量是多个固定值之一时case比一串if elif清爽太多了。结构是case $1 in start) echo 启动服务 ;; stop) echo 停止服务 ;; restart|reload) echo 重启服务 ;; *) echo 未知操作 exit 1 ;; esac每个分支的结尾是两个分号;;表示这个分支结束了。restart|reload用竖线表示匹配两个值之一不用额外写一行。最后的*是通配相当于default兜底。case在模式匹配方面极其灵活值不限于固定字符串还可以用通配符比如case $file in *.txt)这是if很难优雅实现的。我在写服务管理脚本、命令行工具解析时几乎全用case。它不像if链那样嵌套深空格要求也没有[ ]那么苛刻可读性高很多。新手建议专门拿一到两个服务启停脚本来练手把case用熟了你再看很多开源项目的启动脚本就不会觉得天书一样了。4. grep在Shell脚本里的高频用法4.1 grep不是只能“筛行”还能当判断条件grep是Shell文本处理的三大剑客之一另外两个是sed和awk但大多数人的用法停留在“从文件里找一个关键词并显示出来”。实际上它在脚本里的价值远不止这些。当你需要判断某个文件里是否包含某个模式时grep -q配合if是标准姿势if grep -q ERROR /var/log/app.log; then echo 日志里有错误 fi-q是quiet模式它不输出任何内容只返回退出码。有匹配返回0没匹配返回1正好能被if判断。这个写法比grep ... | wc -l判断行数高效得多也比grep输出后手动看屏幕适合自动化。很多运维脚本里还常见这种组合ps -ef | grep java | grep -v grep。第一层grep从进程列表里筛出包含java的行第二层grep -v grep把结果里那些恰好包含“grep”字样的进程过滤掉。不带-v的话你自己执行的grep命令也会出现在进程列表里造成误判。当然真要看进程我建议直接上pgrep但在老系统或者没有pgrep的环境里这个组合还是很通用。4.2 用正则与选项搞定日志统计grep的正则支持是它最强大的部分。基础用法里grep error file是固定字符串匹配加上-E后你就能用扩展正则了比如grep -E error|warning file一次匹配多个关键词。-i忽略大小写-o只输出匹配到的部分而不是整行-c输出匹配行数--colorauto让匹配部分在终端高亮显示。这几个选项组合起来能解决绝大多数日志分析需求。举个例子统计一个访问日志里有多少行包含404状态码grep -c 404 access.log这里把空格也包含进模式是为了防止误匹配到1404这种数字。如果想统计某个接口的请求量可以配合awk取字段但单纯用grep也能做初步过滤。又比如你想提取日志里所有IP地址grep -oE [0-9]\.[0-9]\.[0-9]\.[0-9] access.log然后配合sort | uniq -c | sort -nr就能做一个简易的请求来源排行。这一步一步从原始日志到统计结果全程不到一屏命令却非常出活。4.3 实战进程检查与错误计数脚本我平时最常用的一个组合是先grep筛出关心的行再通过管道交给sort和uniq做统计。比如线上服务报错我需要快速知道各种错误类型各出现多少次grep ERROR /var/log/app.log | awk {print $2} | sort | uniq -c | sort -rn这里awk {print $2}取出每行的第二个字段sort排序是为了让相同值相邻uniq -c统计每个值的出现次数最后的sort -rn按次数降序排列。这个链式管道几乎是我排查线上问题的第一板斧比对着日志肉眼翻快太多了。再分享一个“进程是否存活”的检查脚本片段if pgrep -f myservice.jar /dev/null 21; then echo 服务运行中 else echo 服务挂了需要重启 fipgrep -f能匹配完整命令行比grep进程列表更干净。但如果你必须用grep记住同时用grep -v grep或者grep [m]yservice这种小技巧——把模式写成[m]yservicegrep命令本身的命令行里就包含这个模式的字面量但不会自我匹配这其实是老派运维传下来的一个妙招。5. expect自动化交互输入告别“卡在输密码”5.1 为什么普通Shell搞不定交互式程序很多程序运行时需要交互式输入比如ssh要密码、ftp要账密、passwd要你输两次新密码。普通Shell脚本处理这类程序非常别扭你以为用echo password | ssh ...能行实际经常失败。原因是这类程序不是简单的从标准输入读一行而是直接和终端设备交互检测到输入不是来自终端时甚至可能直接忽略。这时候就轮到expect出场了。它是专门为“模拟人输入”设计的工具通过设置期望的输出来决定何时发送内容。比如程序输出password:这个提示expect就匹配到“password:”然后send发送密码。这种“等待特征字符串→发送内容→继续等待”的模式能自动化绝大多数交互式操作。需要注意expect不一定默认安装Debian系用apt install expectRedHat系用yum install expect。装好后你可以用which expect确认路径和Bash脚本一样在脚本第一行写#!/usr/bin/expect。5.2 spawn、expect、send三件套写一个自动登录脚本一个最简单的expect脚本结构如下#!/usr/bin/expect set timeout 10 spawn ssh root192.168.1.10 uptime expect { *password: { send 你的密码\r } yes/no* { send yes\r; exp_continue } } expect eof逐行解释一下。set timeout 10设置最长等待时长单位是秒超时后脚本不会傻等。spawn启动一个命令expect开始接管它。expect后面跟着的是一个匹配模式当程序输出中出现password:时自动发送密码字符串加回车出现yes/no时发送yes并继续等待直到等到下一个提示这里用exp_continue实现。最后一行expect eof表示等命令结束。如果要保留交互环境不退出登录那就把expect eof换成interact这样命令执行完后会把人机交互权交还给你你能在那边继续敲命令。从自动化执行切换到人工操作靠的就是这个命令。5.3 安全提醒脚本里存放密码要谨慎expect最常用的场景是批量管理服务器、自动登录后抓取信息、自动配置网络设备。我自己用expect写过一组巡检小脚本定时登录几十台机器执行uptime、df -h把结果汇总下来效率确实高。但必须提醒一句expect脚本里明文写密码是一个安全隐患。脚本文件一旦泄露里面所有服务器的口令全暴露了。我的建议是能上密钥认证的地方优先用SSH密钥不存密码确实需要密码的把expect脚本权限设成chmod 700并把密码写进独立配置文件脚本只负责读取不要直接在代码里写死。另外expect只适合合法运维场景网上经常能搜到“反弹Shell”之类的词那类技术属于网络安全攻防的授权测试范畴和日常自动化脚本完全是两码事普通运维不用碰也不该碰。做脚本的底线是只自动化自己有权自动化的东西。6. 文件重命名从单文件改名到批量处理6.1 mv改名与跨目录移动别搞混了文件重命名在Shell里最基础的工具是mv。mv old.txt new.txt在同一个目录下就是纯改名不移动位置。但mv file.txt /tmp/file.txt就是跨目录移动了本质是复制加删除。很多人初期会忘记跨目录移动时目标必须写目录路径否则Shell不知道你是要移动还是改名。还有一个常见的半吊子操作把mv和find结合时脑子不清醒。find . -name *.tmp -exec mv {} /tmp/这个命令会把所有匹配文件都移去/tmp但如果原路径下存在同名文件后移动的会覆盖先移动的。批量操作前建议先find ... | head看看会命中哪些文件或者加上-print打印一下再执行别一把梭。6.2 批量重命名for循环加mv是最稳的组合热词里有“linux用shell重命名文件”我猜搜索的人多半是遇到了批量改名的需求。网上关于rename命令的教程很多但不同版本的rename语法差异很大Debian和RedHat的rename完全不是一个东西照抄一个教程很可能翻车。所以我更推荐用for循环加mv的组合它纯靠Shell内置能力没有跨平台差异。经典需求把所有.txt文件改成.md文件。for f in *.txt; do mv $f ${f%.txt}.md done${f%.txt}是Shell的变量扩展用法意思是把f变量末尾的.txt去掉然后再加上.md。这样README.txt就变成了README.md。如果你想给所有文件加前缀比如backup_开头那就mv $f backup_$f。这种用变量扩展处理字符串的方式比rename命令更灵活也更容易理解。换个复杂点的例子把report_20240101.csv这类文件里的日期拆出来新建文件夹再归类。这时可以用变量扩展提取中间部分再配合mkdir -p建目录。整个过程十几行代码却能处理上千个文件。脚本批量处理的核心思路其实就一句话先在少量文件上试跑打印出mv命令看效果确认无误再去掉注释正式执行。6.3 带空格的文件名是新手脚本的翻车点文件名里有空格是Shell脚本最经典的翻车现场。原因在于Shell用空格做单词分隔for f in *.txt在遇到my report.txt时会把它拆成my和report.txt两个元素。结果你的脚本循环到第一个元素时想打开一个叫my的文件当然报错。解决方法是把变量引用都用双引号包起来。前面我写的所有例子都用了$f这不是洁癖是刚需。多一个双引号带空格的文件名就能当成一个整体处理。另外一个相关问题是管道配合xargs默认情况下xargs也是按空格拆分输入处理带空格文件名会碎掉。如果你必须在xargs里用记得加-0参数配合find -print0。还有个小技巧用while read -r line处理文件列表时同样要加-r避免转义。我见过有人这样写先ls *.txt filelist然后for i in $(cat filelist)结果文件名里有空格就全乱了。正确姿势是while IFS read -r f; do mv $f ...; done filelist。这两个小细节能让你的脚本抗噪声能力上一个台阶。7. Shell脚本常见坑与排查经验7.1 空格、引号和变量空值引起的经典故障Shell脚本排错百分之六七十的问题都出在空格和引号上。最典型的是这个变量为空时[ $var x ]会展开成[ x ]语法直接错误但如果你写成[ $var x ]变量为空时变成[ x ]这就只是个正常的假判断了。所以写条件判断时变量引用永远加双引号这条规则能帮你避开一大半的莫名其妙报错。另一个常见问题是赋值语句、判断语句里多打了空格。age18是赋值age 18是调用命令。[ $age -gt 16 ]里每个令牌之间的空格都有讲究少了空格Shell会认为是一个整体直接报“未找到命令”。遇到这种报错第一反应就是检查空格别先去翻逻辑。我调试脚本时最爱用的方法就是在可疑行前加echo ----调试----看它到底执行到哪一步挂了比猜快得多。7.2 Bash与sh、cron环境变量换个环境就翻车一个在你终端里跑得好好的脚本放到cron里或者别的机器上就报错这是新手最常见的困惑也是老手也容易踩的坑。第一个原因是解释器的差异你本地可能用bash写的[[ ]]、数组、{1..10}但服务器上的/bin/sh是dash不支持这些语法。解决办法是脚本第一行写明白#!/bin/bash并且执行时也用bash script.sh。第二个原因是环境变量差异。cron任务执行时环境变量和交互终端完全不一样——最典型的是PATH里没有你日常那些目录导致脚本里写的java、python、mysql这些命令找不到。排查方法是在脚本里临时输出echo $PATH看看实际环境缺了什么。解决方式是写绝对路径或者在脚本开头手动export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin。生产环境脚本我几乎都会先source一下/etc/profile保证基础环境靠得住。还有个隐蔽的坑脚本里用了相对路径比如cd scripts而你是在别的目录下用cron调用的就会找不到目录。稳健的做法是脚本开头用cd $(dirname $0)把工作目录切到脚本自身所在位置这个技巧我几乎每个正式脚本都会用。7.3 调试三板斧bash -x、set -euxo pipefail、分步echo脚本出问题时最直接的调试方式是用bash -x script.sh运行它会打印每条命令执行前后的变量值用号开头标注。比如 nametom你能清清楚楚看到脚本每一步做了什么。这个命令配合bash -v打印输入行和bash -n只做语法检查不改动能把大部分语法问题揪出来。我第一次用-x时惊叹这工具比猜高效一百倍。另一个我强烈推荐的习惯是在脚本开头加上set -euxo pipefail。这四个选项的含义分别是-e只要有一条命令失败就立即退出不带着错误继续跑-u变量没定义就报错能避免拼错变量名-x就是上面说的打印执行过程-o pipefail让管道中的任何一条命令失败都算整个管道失败。默认情况下管道返回值只看最后一条命令这导致前面的命令挂了你还不知道。set -euxo pipefail一开脚本立刻变得“矫情”但也立刻变得安全。生产脚本里我基本必用虽然调试时输出会啰嗦一点但和它解决的问题相比完全值得。最后还有一个土办法在关键步骤后面加echo 步骤名: 完成或打印关键变量的值。这个办法虽然原始但有时候反而最快。因为-x的输出太详细处理大量数据时会刷屏而手动echo能精准定位到你想看的那一步。两个手段结合起来再顽固的bug也撑不了几分钟。7.4 Shell无处不在从vCenter到adb语法一样通吃聊到这里我想多说一句Shell的语法体系不只是Linux服务器在用。热词里提到的VMware vCenter Server进入Shell、Android调试桥adb shell这些场景本质上都是进入一个命令行环境然后执行Linux风格命令。你学会了基础语法换到这些场景时几乎无缝衔接。举个例子adb shell locksettings set-disabled true是关闭Android设备锁屏的命令adb shell wm size 1080x1920是设置屏幕分辨率adb shell uiautomator dump是把当前界面UI层级导出成XML。这些命令看起来是adb专属但它们都遵循同一个逻辑先通过adb shell进入设备里的Shell环境再在这个环境里执行一条普通命令。你如果把vCenter的Shell看成一台老式Linux终端把adb shell看成一台迷你Linux终端学起来就不费劲了。我个人的学习路径就是这样先彻底掌握Bash的变量、循环、判断、文件处理这四板斧再横向扩展到各种带Shell的环境。语法迁移远远比重新学一门工具的成本低。这也是我为什么坚持建议新手先不要急着背一堆命令而是把Shell脚本的核心逻辑吃透因为逻辑才是到处通用的。最后分享一点我的体会。Shell脚本这个技能不看代码量看的是解决问题是否干净利落。同样的需求新手可能写了三十行还带bug老手用管道拼三行就结束了。区别不在于记住多少命令而在于是否理解Shell的设计哲学每个小工具只干一件事通过管道把它们粘起来。学会这种思维方式比背一百条命令都管用。希望你练的时候多动手、多拆解别人的脚本踩几次坑以后你也会慢慢找到那种“命令行在手、效率我有”的感觉。
企业数字化 ERP 产品动态
相关推荐
Shell脚本从入门到实战:变量、循环、后台执行与ADB调试 Shell 这东西,说实话,刚开始接触的人容易把它想得特别简单,觉得不就是个黑乎乎的终端窗口,敲几个命令嘛。但真正用起来,你会发现它是整个 Linux 生态里最抗造、最省命的一块基石,也是让你从“只会点鼠标”走… · 2026/9/26 5:04:06
如何看懂sprix-sage-router嵌套束搜索:团队组建与角色分配联合搜索的完整原理 如何看懂sprix-sage-router嵌套束搜索:团队组建与角色分配联合搜索的完整原理 【免费下载链接】sprix-sage-router Sprix AI at 屿智同行 — state-aware SELF/COLLABORATE/HANDOFF routing for A2A agent networks. 项目地址: https://gitcode.com/gh_mirrors/sp… · 2026/9/26 5:04:06
如何实现闲鱼自动回复与客服自动化?C++级指纹伪装深度,连系统调用层都查不出 如何实现闲鱼自动回复与客服自动化?C级指纹伪装深度,连系统调用层都查不出
在电商圈混久了就会发现,闲鱼的自动回复与客服,是店群运营中最耗人力也最容易出错的环节。
店群客服是纯人力消耗战。一个店日均50条咨询,20个… · 2026/9/26 5:04:00
DBeaver转储备份迁移三类操作原理与避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 5:41:47
第一个PPT网站就用快马AI:零基础快速生成网页版PPT实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 5:41:47
机器学习增强的电商用户行为预测:从标签到上线全流程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 5:41:41
不想在多个网盘来回搜?用 Pansou 在 fnOS 搭一个统一搜索入口 前言
我在 NAS 上整理资料时,最浪费时间的往往不是下载,而是搜索入口太散:一个关键词要在不同网盘、不同频道和不同搜索页里反复试,最后还得自己判断哪些结果重复、哪些已经失效。Pansou 吸引我的地方就在这里,它不是新… · 2026/9/26 5:41:35
占位符‘asdfasdf‘引发的数据污染:从排查到防护的完整指南 聊个现象。前两天帮人排查接口数据,发现数据库里有一条用户名叫“asdfasdf”的注册记录,头像、手机号、邮箱全是同一套乱敲的字符串。我盯着这行数据看了三秒,差点笑出声——这串字符太眼熟了,十个人里有八个在联调时都往输入框里… · 2026/9/26 5:41:35
UML序列图实战指南:从接口联调到架构评审的完整解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 5:41:35
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46