十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

创建并进入目录只写一遍名:mkdir、cd与$_的实战技巧

创建并进入目录只写一遍名:mkdir、cd与$_的实战技巧 从日常敲命令的人应该都干过这种事明明只是想在某个路径下新建一个文件夹然后进去结果手指得忙活两遍——mkdir project敲完回车看到光标换行了条件反射地又开始敲cd project第二遍目录名打得人火大尤其是项目名一长串还带大小写和横线的时候。标题里的这组关键词我惦记很久了mkdir、cd、$_、创建并进入目录、只写一遍目录名。今天就把这套“少打一次目录名”的组合技彻底讲透顺带把背后那些你在文档里翻不到、但实际极其好用的 shell 小知识一并盘出来。不管你是刚接触命令行的小白还是已经写了三年脚本的老手这篇文章里至少有一两个技巧是你以前没注意过的。1. 先搞清楚最基础的方案为什么常见写法是mkdir xx cd xx很多教程喜欢让人直接背mkdir xx cd xx这条命令说这是“创建并进入目录”的标准写法。这话没错但它只解决了“能不能做到”的问题没解决“能不能少打一遍”的问题。而且很多新手压根没搞懂中间那个是什么就把命令背下来了一旦遇到目录创建失败的情况整个人就懵了。1.1背后的执行逻辑在 shell 里的语义是“逻辑与”意思是左边的命令执行成功退出码为 0才执行右边的命令。放到mkdir xx cd xx这条命令里翻译成人话就是先执行mkdir xx创建目录。如果创建成功退出码为 0shell 继续执行cd xx。如果创建失败比如目录已经存在或者当前目录没有写权限shell 直接停住不执行cd。提示这也是为什么写自动化脚本时cmd1 cmd2比cmd1; cmd2更安全。后者不管前面成败都会执行后面的命令写错了目录名也没人拦你。有些教程会写mkdir xx; cd xx用分号连接。分号的含义就是“按顺序执行互不关心结果”。像cd xx这样的命令如果xx压根没创建成功执行之后就停留在原地而且cd会报错。虽然错误信息会提醒你目录不存在但当手速快、终端滚动快的时候很容易忽略这个报错还以为自己已经进入目录了后面再执行别的命令就开始连锁出问题。1.2 为什么是比;更合理的默认选择我的习惯是凡是“前一步成功后一步才有意义”的场景一律用不用;。mkdir cd就是最典型的场景创建目录都没成功我进一个不存在的目录干什么呢这个习惯在脚本里尤其重要。比如部署脚本里经常有这样的片段mkdir -p /opt/app/logs cd /opt/app/logs如果mkdir因为权限问题失败了会直接把脚本的后续执行卡住避免你在一个错误路径下继续跑wget、tar之类的命令最后把文件解压到莫名其妙的地方。但基础写法解决了“安全”问题没解决“效率”问题。你仍然要把目录名打两遍。打两遍还不算完如果目录名特别长或者项目名是从文档里复制过来的第二遍很容易打错。我见过有人在复制粘贴长目录名时第二个cd后面的名字少了一个字母结果 cd 失败人还愣了半天。接下来要解决的问题就是如何让这个目录名只打一遍。2. 只写一遍目录名的两套方案$_与!$的完整对比只打一遍目录名的思路本质上是第一次输入目录名时让 shell 把名字“记住”第二次使用目录名的位置直接用某种语法把它“取出来”。Shell 里有两套机制可以做到这件事一套是 Bash 的特殊变量$_另一套是历史展开history expansion里的!$。这两个东西长得像实际原理完全不同。2.1$_永远指向“上一条命令的最后一个参数”Bash 里$_是一个特殊变量它的值是“上一条命令的最后一个参数”。这玩意不需要你手动赋值每次命令执行完shell 会自动把最后一个参数塞进去。于是就有了这样的操作mkdir myproject cd $_第二条命令执行前$_已经被上一次执行mkdir myproject的myproject填满了。cd $_展开之后等于cd myproject你不需要重新打第二遍目录名。但要注意一个细节我这里用的是两条独立命令中间没有没有分号。mkdir myproject cd $_如果你写成这样看起来也像那么回事实际执行时$_的值在mkdir myproject执行完之后才更新成myproject然后再执行cd $_。所以这条命令也能正常工作。问题在于如果你写成这样mkdir myproject; cd $_分号在两句话之间加了一个“完整的命令边界”$_的更新时机依然在mkdir myproject执行完之后所以这条也没问题。那真正的坑在哪在于“上一条命令”的定义。$_的更新是在上一条命令执行的过程中完成的如果你的中间穿插了别的东西$_的内容可能就不是你想要的了。我把它整理成几个典型场景场景命令$_的值结果标准两连mkdir aaa回车cd $_回车aaa正常工作加了mkdir aaa cd $_aaa正常中间夹了 echomkdir aaa、echo hi、cd $_hicd 失败不是你想要的效果在脚本里用脚本中的$_取决于脚本上下文不推荐第二行那个“中间夹了 echo”的场景我在实际工作中踩过不止一次。你在准备进入目录之前习惯性地想打印点什么提示信息比如echo done结果$_就变成了donecd $_等于cd done目录不存在直接报错。实操心得在使用$_时从输入mkdir到输入cd之间不要执行任何不相关的命令。这个限制听起来很基础但真到了终端前人一忙就会忘。2.2!$历史命令展开一个被许多人忽略的古早功能!$是历史展开history expansion语法它的含义是“上一条命令的最后一个词word”。历史展开是 Bash 从 C Shell 继承过来的老功能默认在交互式 shell 里开启但在 shell 脚本里是默认关闭的。使用方法更简单mkdir myproject cd !$第一次执行mkdir myproject之后输入cd !$Bash 会在执行前把!$展开屏幕上你会看到整条命令变成cd myproject然后执行。!$和$_最直观的区别有两个第一!$展开的是“历史记录”里的最后一个词而$_是“变量存储”的值。历史记录天然容忍你在两条命令之间执行别的操作比如你mkdir aaa之后中间echo hi、ls、pwd了一堆命令最后一个历史词的判定依然是你当前输入行的上一条命令。当然如果你中间的echo hi是在mkdir之后输入的hi会变成最后一个词!$抓到的也是hi。所以“中间夹了命令”这个坑对!$来说依然存在只是判定逻辑不同。第二!$在按下回车之前就能看到展开结果。因为 Bash 在做历史展开时会先把展开后的结果显示在命令行上再让你确认执行。这样能提前发现展开错了按 CtrlC 取消就行。$_是变量在执行前你看不到它变成了什么只能等命令执行完从报错里猜。小贴士历史展开还有几个高级关键词实际作用很大历史展开含义!!上一条完整命令!$上一条命令的最后一个参数!^上一条命令的第一个参数!*上一条命令的所有参数不含命令名!mk最近一条以mk开头的命令!!在权限不足的时候特别常见。你输入mkdir /usr/local/project提示 permission denied这时候不用重新打一遍直接输sudo !!shell 会把它展开成sudo mkdir /usr/local/project。2.3 终极对比$_、!$、常规写法该怎么选三个方案放在一起对比一下方案是否少打目录名跨命令容错脚本可用学习成本mkdir xx cd xx否高可用低mkdir xxcd $_是低不推荐低mkdir xxcd !$是中不可用中如果只是偶尔在终端里手动建目录哪个顺手用哪个。我更推荐!$原因是“回车之前能看到展开结果”这个特性实在太香了能防呆。如果是写脚本别用这两个用函数封装下一节详细说。3. 把技巧升级成命令亲手写一个mkcd函数前面讲的都是“临时用一下”的技巧。但作为每天要敲大量命令的人我不想每次都进mkdir xx cd $_这种流程。我的做法是写一个函数扔进 shell 配置文件里以后直接敲mkcd 目录名一条命令搞定目录名只写一遍。3.1 基础版两行代码效果立竿见影以 Bash 为例编辑~/.bashrcZsh 用户编辑~/.zshrc在文件末尾加上mkcd() { mkdir -p $1 cd $1 }保存后执行source ~/.bashrc让配置生效。用法mkcd node_modules_gp执行后同时完成“创建目录”和“进入目录”目录名只写一遍。注意mkdir -p这个参数。-p表示“parents”即自动创建中间目录。我输入mkcd a/b/c如果a和a/b都不存在mkdir -p会一口气全部创建出来然后cd直接进到a/b/c。对于“创建并进入多级目录”的需求来说这是一个天然的好功能。如果你不希望自动创建多级目录比如你更习惯让命令在目录路径不存在时直接报错以免手滑写错路径可以把mkdir -p改成mkdir。这个看个人习惯我日常用-p因为确实省事。3.2 进阶版加上防错校验应对真实场景基础版能用但有隐患。如果目录创建失败了比如权限不够或者路径里有个无法创建的字符函数依然会执行cd $1结果就是报一个 “No such file or directory”人还得自己去排查。稍微改进一下mkcd() { if [ -z $1 ]; then echo 用法: mkcd 目录名 return 1 fi mkdir -p $1 cd $1 }加了一个参数判空再在中间用保证cd只在mkdir成功时执行。两行代码的事防呆效果明显提升。还有一个容易忽略的点目录名包含空格的情况。比如mkcd my project如果不加引号$1拿到的是mymkdir -p创建的目录叫my然后cd进入的也是my你以为你创建了my project结果搞出了一个半截目录容易当场懵掉。解决办法取决于你的使用习惯两个方案方案一命令带上引号让整个目录名作为一个参数传进函数。mkcd my project方案二在函数里把$1换成$*。$*会把所有参数拼接成一个字符串中间用空格分隔。这样你直接输mkcd my project也能得到正确结果但代价是被$*拼接后的字符串本来就是你想要的还是得有引号意识。更稳的方法其实是在cd命令里面也加引号mkcd() { mkdir -p $1 cd $1 }因为$1位于双引号内部就算传入的目录名是my project也会作为一个整体字符串传给mkdir和cd不会分词。踩坑记录$1不加引号和加引号行为天差地别。凡是在函数里用位置参数拼接路径的命令一律加双引号。不加引号的路径参数一旦含空格轻则目录名被切断重则执行了你根本没想到的命令。这个教训我在写部署脚本时亏过两次一次是目标目录名里带空格一次是文件名以-开头被命令误认为选项。3.3 把mkcd玩出花追加目录栈、自动展开路径有了函数你可以尽情扩展。比如我后来给mkcd加了两个小功能第一个是自动把当前目录加入目录栈directory stack方便后面按popd快速返回。改动很小mkcd() { pushd $1 /dev/null 21 || { mkdir -p $1 pushd $1 /dev/null } }这段的意思先尝试pushd进入目录如果目录不存在会失败此时自动创建目录再进入。pushd与popd一起用可以在多个目录之间快速跳来跳去适合一次处理多个项目文件的场景。第二个是自动展开波浪号。比如你输入mkcd ~/new_projectmkdir -p ~/new_project能正常展开波浪号cd ~/new_project也能展开所以函数默认就能支持。3.4 不同 Shell 的移植写法如果你换到 Zsh函数写法几乎一样只是配置文件换成~/.zshrcmkcd() { mkdir -p $1 cd $1 }如果用的是 fish shell语法有些差异需要放在~/.config/fish/functions/mkcd.fish文件里function mkcd mkdir -p $argv[1]; and cd $argv[1] endfish 的$argv[1]相当于 Bash 的$1and相当于。写完之后直接在交互式 fish 里输入mkcd 目录名即可。Windows 上的 PowerShell 用户也不用羡慕同样可以写一个函数放在$PROFILE里function mkcd { mkdir $args[0] cd $args[0] }不过 PowerShell 在mkdir成功后返回的结果是一个对象不一定能顺着管道把状态传给cd所以上面这个简单写法在mkdir失败时会继续执行cd。改进版function mkcd { if (mkdir $args[0]) { cd $args[0] } }这段在创建成功后才执行cd和 Bash 版逻辑等价。4. 除了mkcd还有几个目录相关的效率小抄mkdir和cd组合起来能玩的花样不少。下面几个是我日常高频使用且和“创建并进入目录”相关的技巧一并整理给你。4.1cd -在两个目录间快速切换cd -的意思是“回到上一个目录”。这不算创建目录的技巧但和mkdir、cd组合起来效率奇高。比如你正在/home/user/project-a下工作突然要处理/home/user/project-b的新目录于是执行mkcd ../project-b进去处理完任务想回到 project-a一个cd -就回去了。这段操作如果不用cd -你就得重新打一遍绝对路径或相对路径时间成本会翻好几倍。有人会问那cd ..不行吗cd ..和cd -语义不同。cd ..是去上一级目录cd -是去上一个所在目录。假设项目路径在/data/apps/web/src你想从src里跳出去看web目录的东西再用cd -跳回来这个来回操作是cd ..做不到的。4.2 pushd/popd 组合如果你要在多个目录之间反复横跳cd -只管上一次不够用。这时候上目录栈pushd /data/log popdpushd进入新目录的同时会把当前目录压入栈中popd从栈顶弹出目录并直接进入。我用这套逻辑在“源码目录”和“日志目录”之间来回查问题效率比cd加cd -要高因为它支持多目录切换不用一直依赖“上一个”这样的单级记忆。4.3 空目录创建mkdir -p与{a,b,c}批量展开Bash 的花括号展开是隐藏的建目录神器。一次生成多个同层目录mkdir -p project/{src,docs,tests}执行后生成project/src、project/docs、project/tests三个目录。如果你再配合cd project进去一行命令就准备好了整个项目骨架的父目录。花括号可以嵌套mkdir -p project/{src/{components,utils},docs,tests}这条命令一口气创建了project/src/components、project/src/utils、project/docs、project/tests六个目录一次到位。对于初始化一个新项目来说这是省力到极致的一行操作。花括号展开的语法要点是花括号里的逗号分隔项之间不能有空格否则会解析失败。我第一次写{src, docs, tests}时被这个空格坑了一次命令没报错但实际只生成了src和docs等不同名字的目录项目结构全乱了。4.4mkdir -p的幂等性用于脚本里的目录初始化mkdir -p有一个隐蔽特性当目录已经存在时它不会报错退出码依然是 0。这个特性在自动化脚本里特别有用。脚本开头经常会写mkdir -p /var/log/myapp cd /var/log/myapp因为有了-p就算/var/log/myapp已经在系统里存在脚本也不会因为“目录已存在”而中断。设计一套需要反复执行的部署脚本时这是一个基础但极其重要的幂等保证。5. 操作中避不开的坑$_失效、目录名带空格、自动化场景写到这里我把这套技巧背后容易踩的坑集中整理一下。很多细节在教程里根本不会提但我实战中几乎全都遇到过。5.1$_在自动化脚本里的失效问题前面提到的$_技巧在交互式终端里用得很顺手但如果你尝试把它写进 shell 脚本会发现它不按预期工作。比如你写一个脚本内容是这样#!/bin/bash mkdir mydir cd $_ pwd保存为脚本执行你可能会觉得cd $_会进到mydir。实际结果取决于脚本的具体上下文$_在脚本里的值一般不会是上一个命令的最后一个参数它可能是脚本解释器的路径也可能是当前脚本的名字。这是 Bash 对$_在非交互环境下实施的不同规则导致的。结论$_只适合交互式终端的临时使用写脚本需要用变量显式传递dir_namemydir mkdir $dir_name cd $dir_name5.2mkdir xx; cd $_中分号和的选择如果把mkdir和cd放在同一行用分号和用的区别很明显mkdir xx; cd $_ mkdir xx cd $_用分号时两条命令都会执行。如果xx创建失败cd $_必然跟随报错。用时mkdir失败则cd不会执行。虽然这种差异在大多数情况下都能被察觉可一旦你在非交互环境比如 GitHub Actions 或 CI 流水线的某个阶段执行这段命令失败状态可能不会立即停止运行而是把错误吞掉直到后续真正需要该目录时才爆雷。注意在 CI 流水线脚本里不如直接写mkdir -p $DIR cd $DIR把目录名放进变量里。别用$_也别用!$这两个都是交互式 shell 的语法放进 CI YAML 里的run步骤时行为不可预期。5.3 目录名中的特殊字符空格、-开头的名字目录名带空格是新手最容易中招的场景mkdir New Folder你以为创建了New Folder实际创建了两个目录New和Folder。必须加引号mkdir New Folder cd New Folder用$_也有类似问题。你的mkdir命令如果是mkdir New Folder$_的值会是去掉了引号的New Folder然后执行cd $_时因为$_没有保留引号信息shell 会按空格把它拆成两个参数导致 cd 失败。这就是我坚持建议用函数封装的原因函数参数传递天然规避了分词问题。目录名以-开头的情况更隐蔽。比如mkdir -newdir这个命令会被解析成“创建名字叫newdir的目录并开启n、e、w等选项”几乎肯定报错。解决办法是在命令后加--表示“选项从这里结束”mkdir -- -newdir cd -- -newdir--是 GNU 命令行工具的标准约定意为后面所有内容都视为位置参数不再解析选项。我自己直到有一次部署脚本里出现了一个以-v开头的目录名才意识到这个东西的重要性。5.4 终端复制的坑不可见字符还有一类坑和目录名本身无关而是终端会话。在 Linux 服务器上粘贴从网页复制的目录名时偶尔会带入一个不可见的零宽空格Zero Width Space肉眼完全看不出来命令执行却一直失败。检查的方法是执行mkdir -p test$(cat)之类的不太容易说清楚更实用的方法是用echo $PWD | xxd | head或者ls -ab看输出里的\u200b。我一向建议如果发现mkdir、cd命令连续报错而目录名看起来完全正常先怀疑不可见字符。用wc -c对比你肉眼数出的目录名字节数如果对不上基本就是这个原因。我曾经排查过同事一个“诡异问题”目录名明明存在cd却始终提示 No such file or directory最后发现就是多了一个零宽空格。5.5 幂等检查目录已存在时怎么办如果你的工作流里经常需要“先建目录再进入”而该目录可能已经被创建过了mkdir -p会静默跳过不会报错所以函数方案天然支持幂等。但如果你没用-p还有目录已存在的情况mkdir xx会报 File exists。这时候如果你写的命令是mkdir xx cd xxcd不会执行。如果你用的是mkdir xx; cd $_cd会执行但是$_的值是xx还是什么依然符合之前的规则。一个简单的测试习惯先ls -d xx看看目录是否存在再决定是否执行mkdir。不过日常操作中没人会这么严谨。最省事的做法还是让命令幂等这也是我为什么在mkcd函数里坚持用mkdir -p。6. 把这套技巧落到配置里一份可直接抄的 shell 配置示例说了这么多最后直接给一份配置把今天涉及的主流 shell 的mkcd函数都整理出来你复制到自己的配置文件就能用。Bash~/.bashrc:mkcd() { if [ -z $1 ]; then echo 用法: mkcd 目录名 return 1 fi mkdir -p $1 cd $1 }Zsh~/.zshrc和 Bash 几乎一致:mkcd() { [[ -z $1 ]] { echo 用法: mkcd 目录名; return 1; } mkdir -p $1 cd $1 }fish~/.config/fish/functions/mkcd.fish:function mkcd if test -z $argv[1] echo 用法: mkcd 目录名 return 1 end mkdir -p $argv[1]; and cd $argv[1] endPowerShell$PROFILE:function mkcd { if ($args.Count -eq 0) { Write-Host 用法: mkcd 目录名 return } if (mkdir $args[0]) { cd $args[0] } }用source ~/.bashrcZsh 用source ~/.zshrc重载配置文件后就能生效。fish 和 PowerShell 保存文件后新开的会话自动可用。配置好了之后今天的所有技巧就都沉淀到你的日常工作流里了。以后建目录进目录手落下去的时候只需要想一个名字剩下的交给函数处理。我个人用这套mkcd的时间超过五年从最初的mkdir; cd $_到现在的带校验函数最大的体会是命令行技巧不怕基础怕的是只会背命令不懂原理。理解了$_的更新时机、与;的语义差异、$1和$1的区别你在任何一台机器上遇到奇怪行为时都有能力自己排查而不是搜一个命令抄过来就行。最后再分享一个小技巧如果你经常需要在一个深层级路径下快速建目录并进入可以给mkcd再加一个自动补全。Bash 用户在~/.bash_completion里或.bashrc里加一行complete -A directory mkcd这样在输入mkcd之后再按 Tab它可以自动补全当前目录下的子目录名或者路径配合mkdir -p自动创建多级目录的特性效率还能再上一个台阶。
返回列表