这个命令是干啥的
cd,change directory,切换当前工作目录。Unix/Linux 系统里最基础也是最常用的命令之一。
在我刚开始用 Linux 的时候,cd 就是从一个目录跳到另一个目录。cd /home、cd /var/log、cd /,没了。后来我慢慢发现,cd 其实藏着不少偷懒技巧。尤其是每天在不同路径之间来回切换的运维场景,用好 cd 能省下大把敲键盘的时间。
比如你在 /usr/local/nginx/conf/vhosts/ 这种深目录里干活,想去 /etc/nginx/sites-enabled/ 看一眼配置,再切回来。要是每次都手打完整路径,一天下来手指头都酸了。
基本用法(3分钟上手)
最基本的就不多说了:
# 进入指定目录
cd /var/log
# 回到家目录
cd
# 进入上一级目录
cd ..
# 进入上两级目录
cd ../..
但是大多数人不知道的小技巧来了。
回到上一次目录:
# 切到日志目录
cd /var/log/nginx
# 切回去刚才的目录
cd -
cd - 这个我每天用几十次。特别是在多个深度目录之间来回切换的时候,比 cd ../../.. 快太多了。
# 打印当前目录再切换
cd - && pwd
# cd - 本身就会打印路径,其实不需要额外pwd
cd -
cd - 执行的时候控制台会打印出路径,看着就很安心。
回到家目录的变体:
# 直接回家
cd ~
# 回到root用户的家目录
cd ~root
# 回去指定用户的家目录
cd ~zhangsan
cd ~user 这个在管理多用户机器时非常有用。看其他用户目录下的文件,不用先 ls /home/zhangsan 再看路径了。
用绝对路径和相对路径的混合:
# 当前路径 /usr/local/nginx/conf/
cd /etc/nginx/conf.d/
# 刚才的位置还在 $OLDPWD 里存着
echo $OLDPWD
$OLDPWD 是个环境变量,cd 进去一个目录后,系统自动把上一个目录存进去了。cd - 底层就是在读这个变量。
进阶骚操作
pushd / popd / dirs 目录栈:
这个是我后来才搞明白的,但一用就离不开了。
# 入栈,把当前目录压入栈中
pushd /etc/nginx
# 入栈另一个目录
pushd /var/log/nginx
# 看看栈里有什么
dirs
# 出栈,回到上一个压栈的目录
popd
目录栈可以压多个,用 pushd 压入,popd 弹出。适合来回在几个目录之间跳跃的场景。
# 显示栈里所有目录,每个一行
dirs -p
# 清空目录栈
dirs -c
带编号的切换:
# 切换到栈里的第三个目录(从0开始数)
pushd +2
# 切换到栈里最后一个
pushd -0
栈操作看起来复杂,但实际用起来顺手得很。后面实战部分会说具体用法。
cd 到符号链接的物理路径:
# 进入软链接目录后,看到的是链接指向的真实路径
cd -P /etc/alternatives
# 如果只想看物理路径,不加-P就留在软链接里
cd -L /etc/alternatives
这个 -P 和 -L 区别在 pwd 那篇文章里会细讲。总之记住:-P 走物理路径,-L 走逻辑路径。
autojump - 懒人必备:
# 如果系统装了autojump
j log
# 直接跳到最常访问的包含log的目录
# 不用记完整路径,autojump用权重算法记住你的习惯
autojump 是个第三方工具,装好之后你用 cd 去过的目录都会被记录下来。以后只要 j 关键字 就能跳过去。频率越高的目录权重越大,跳得越准。
类似的还有 z 和 fasd,挑一个习惯就行。
CDPATH 环境变量:
# 设置CDPATH
export CDPATH=:/etc:/var:/usr/local
# 然后直接 cd nginx,不用写完整路径
cd nginx
不加 CDPATH 的话,cd nginx 只在当前目录下找子目录 nginx。加了之后会在 CDPATH 列表里依次查找。适合经常跑 /etc、/var 等固定位置干活的人。
避坑指南
坑1:cd 到一个不存在的目录没提示?
# 这个不会报错
cd /tmp/doesnotexist 2>/dev/null
# 但目录不存在,pwd发现根本没跳转
pwd
如果你把 cd 写在脚本里,最好检查返回码:
cd /some/important/path || { echo "目录不存在"; exit 1; }
坑2:Cdpath 设了反而让你迷路
# 假设 CDPATH 里有 /etc
# 当前目录下也有一个 nginx 子目录
# 你 cd nginx 跳到了哪个?
# 答案是 CDPATH 里匹配到的第一个
CDPATH 是个双刃剑。设了之后 cd 行为变了,有些脚本可能会有预期之外的表现。我一般只在交互式 shell 里设置,不会写到 /etc/profile 里影响全局。
坑3:cd - 在脚本里不好使
#!/bin/bash
cd /var/log
# 干点事...
cd -
脚本里 cd - 的行为可能会被你执行时的目录结构影响。脚本里最好用绝对路径,或者用 subshell 来处理:
#!/bin/bash
(
cd /var/log
# 这里随便cd,外面的目录不受影响
)
# 这里还在原来的目录
subshell 里的 cd 不会影响父 shell,是个好习惯。
坑4:cd 到文件名有空格的地方
# 目录名是 "My Documents"
cd My Documents
# 这实际上 cd 了两次:先进 My,再进 Documents
# 正确的做法
cd "My Documents"
cd My\ Documents
实战场景(重点!结合真实运维场景)
场景一:排查多个服务配置文件
有一次线上 nginx 报错 502,我需要同时看 nginx 配置和 php-fpm 配置,还有后端服务的日志。
# 先用pushd把几个目录都压进去
pushd /etc/nginx/conf.d
pushd /etc/php-fpm.d
pushd /var/log/php-fpm
# 用popd来回切换
# 先到nginx配置
popd
# 改完
pushd /etc/nginx/conf.d
# 再到php日志
popd +1
来回切不用打长路径,效率高了很多。排查完事了一个 dirs -c 清空收工。
场景二:深层次项目目录的快速穿梭
项目目录结构:/data/projects/myapp/v2.3/src/main/java/com/company/module/controller/
经常要在 controller、service、dao 三个包之间切:
# 不是直接cd完整路径,而是用CDPATH
export CDPATH=.:/data/projects/myapp/v2.3/src/main/java/com/company/module
# 然后直接 cd controller、cd service、cd dao
配合 tab 补全,效率起飞。
场景三:自动化部署脚本中的目录管理
写部署脚本时,目录切换是最容易出问题的地方:
#!/bin/bash
APP_DIR=/data/app/myapp
BACKUP_DIR=/data/backup/$(date +%Y%m%d)
# 先切到备份目录
mkdir -p "$BACKUP_DIR" && cd "$BACKUP_DIR"
# 备份当前版本
cp -r "$APP_DIR" ./myapp_backup
# 切回app目录
cd "$APP_DIR"
# 在这里执行git pull或者解压新包
用 $() 定个变量,路径写清楚,比硬编码强多了。不然改个路径名,脚本全废。
场景四:用 cd 配合 find 批量操作
# 先到目标目录
cd /data/old_logs
# 批量处理文件
for f in *.log.gz; do
gunzip "$f"
done
# 搞完回到原来的位置
cd -
虽然用绝对路径也能写循环,但先 cd 过去再处理就省得每个命令前都带路径了。
今日作业
打开终端,试试以下步骤:
1. 用 pushd 压入 3 个不同的目录
2. 用 dirs -v 查看栈里的编号
3. 用 pushd +1 切换到第二个目录
4. 再用 popd 逐个弹出
然后说说看:dirs -v 输出里的编号从 0 开始还是从 1 开始?