上一篇我们把 ls、cd、cat 这些最基础的指令过了一遍,有朋友留言说:看完觉得懂了,可真到自己上手配置环境、部署服务的时候,还是不知道要敲什么。其实这不是你记性差,而是“会看命令输出”和“会用命令干活”之间还隔着一段实操距离。这篇《续》就把这段距离补上,围绕 Linux 基础指令里真正高频、真正容易出问题的场景展开,顺手把 find、scp、useradd、systemctl 这些常用命令串进实际任务里。不管你是刚转过来做运维,还是写 Java、Python 但天天要跟服务器打交道,这篇应该都能帮你少踩几个坑。
1. 从“能看懂”到“会干活”:文件操作里那些没人提醒的细节
1.1 删除文件夹:rm -rf 为什么危险,正确的删除节奏
很多教程提到删除目录就会甩给你一句 rm -rf,但很少有人讲清楚这个命令的风险边界。rm -rf dir 的含义是:不加提示地递归删除 dir 这个目录及里面所有东西。问题不在于 -r,而在于 -f 会忽略所有确认和权限提示。一旦路径写错、变量为空、或者手滑多敲了一个空格,后果就是不可逆的。
我之前见过一个真实事故:有人在脚本里写了 rm -rf $DIR/,结果 $DIR 没赋值,命令解析出来变成了 rm -rf /。如果不是系统有快照,那台机器基本就废了。所以我的建议是:如果不是在脚本里处理明确路径,交互式命令行里尽量用 rm -i,让它逐个确认;删除大目录时可以先 ls 看清楚,再执行 rm -r dir,把 -f 加上之前先问自己一句“这个路径真的是我想删的吗”。
还有一个小技巧:在重要目录上做操作前,先敲一下 echo 或者 ls 看一眼路径是否展开正确。比如你想删除 build 目录下所有 .log 文件,可以先执行 ls build/*.log 看看匹配到的文件列表,确认无误再删。这个习惯成本很低,但能救命的次数真的不少。
1.2 cp/mv 的真实行为:跨磁盘时是“复制+删除”
很多人以为 mv 就是单纯“移动”,性能一定比“复制+删除”快。实际上,mv 在同一文件系统内只是修改目录项,瞬间完成;但如果源目录和目标目录不在同一个挂载点,比如从 /home 挪到 /data,mv 会退化成先 cp 再 rm 的完整数据搬运。这时候如果你以为它瞬间完成,等几分钟还没结束,就会开始怀疑是不是卡死了。
这个问题在复制大文件、迁移数据时特别明显。建议是:跨文件系统搬运大量数据,直接用 rsync,它能显示进度、支持断点续传,中途断了也不用从头再来。日常小文件无所谓,但一旦涉及几十 GB 的数据,先 df -h 看一眼源和目标是否在同一个磁盘分区,比盲目等 mv 结束要靠谱得多。
cp 也一样。平时 cp file1 file2 很简单,但复制目录必须加 -r,否则会报 omitting directory。如果你想保留文件时间戳、属主这些属性,可以用 cp -a(等价于 -dpR),尤其在备份配置目录时非常有用。
1.3 通配符展开:为什么 rm *.txt 可能误删
Shell 的通配符是在命令执行前由 Shell 自己展开的,不是你敲什么它就原样传给 rm。比如你执行 rm *.txt,Shell 会先把当前目录下所有以 .txt 结尾的文件名展开成一个列表,再交给 rm。这带来一个隐蔽的坑:如果当前目录一个 .txt 文件都没有,很多 Shell 会默认把 *.txt 当成一个普通字符串传给 rm。结果就是 rm 收到一个名为 *.txt 的路径,找不到文件,报错。
这种时候,你的命令看起来是“删所有 txt”,实际只是报了个错,但如果你本意是想清理文件却误以为成功了,就会影响后续流程。更危险的是 find . -name "*.txt" -exec rm {} \; 这类写法,如果引号使用不当,会让 find 匹配到意外范围。我的习惯是:凡是涉及通配符的删除操作,先 echo 一下展开结果,比如 echo *.txt,确认列表符合预期再动手。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用户、权限与进程:新手最容易翻车的三座山
2.1 新建用户:useradd 与 adduser 的区别
在 Linux 上新建用户,你可能会看到两种命令:useradd 和 adduser。这俩名字看着像,行为却很不一样。useradd 是底层命令,参数非常“原生”:默认很多发行版下,执行 useradd zhangsan 并不会自动创建家目录,也不会问你密码,得你自己补 -m 和 passwd。而 adduser 在 Debian/Ubuntu 系列里是一个 Perl 脚本,它会交互式地帮你创建家目录、设置密码、填用户信息,对新手友好得多。
所以我的建议是:Ubuntu/Debian 上直接用 adduser 就好;CentOS/RHEL 上 adduser 通常只是 useradd 的软链接,这时候你得记牢 useradd -m -s /bin/bash zhangsan 这种完整写法。-m 是创建家目录,-s 是指定登录 Shell,不指定的话有的系统会默认成 /bin/sh,用起来很别扭。
建完用户以后还要处理权限。如果想让这个用户能执行 sudo,Ubuntu 下要把它加进 sudo 组:usermod -aG sudo zhangsan;CentOS 下是 wheel 组:usermod -aG wheel zhangsan。注意 -aG 的 -a 不能丢,它表示“追加”,不加的话会把用户从其他附属组里踢掉,这是很多人没注意到的一个坑。
2.2 chmod/chown:权限位的计算和目录权限的坑
权限这块,新手一般都知道 chmod 755 代表 owner 可读可写可执行、group 和其他人只读可执行。但数字是怎么算出来的,很多人没真正理解。r=4,w=2,x=1,每个角色的权限就是这三个数相加。比如 chmod 644 就是 owner 读写,group 读,其他人读,这是文件最常见的权限设置。
但里面有个容易被忽略的点:目录的执行权限(x)不是“执行”的意思,而是“是否可以进入这个目录”的通行证。如果你给一个目录设置了 chmod 644,没有 x 权限,那你 cd 进去会报 Permission denied,就算你能看到文件名列表,也无法访问里面的文件。常常有人把整个目录 chmod -R 777 来解决,权限是开了,但也埋下了安全隐患。
chown 用来改属主和属组,比如 chown zhangsan:devops app.jar,把属主改成 zhangsan、属组改成 devops。这里有一个经验:复制文件到另一个用户目录时,文件属主可能还是 root,导致那个用户没法写。遇到这种情况不是去 chmod 777,而是应该 chown 给正确的用户。
还需要提一下 SUID 位。如果你用 find / -perm -4000 看到一堆带 SUID 的可执行文件,不用太紧张,系统里某些程序确实需要提权运行。但如果你自己部署的应用目录里出现了 SUID 文件,那就要警惕了,因为 SUID 会让普通用户以文件属主的身份运行程序,经常被用来做权限提升的入口。
2.3 排查进程:ps、top、kill、lsof
排查进程是日常操作里频率很高的场景。最常用的命令组合是 ps -ef | grep java,把 Java 进程找出来;或者 ps aux 查看完整信息。但很多人不知道 ps -ef 和 ps aux 有一个细微差异:-ef 是 System V 风格,aux 是 BSD 风格,在大多数 Linux 发行版上输出差别不大,但在脚本解析时,可能需要固定用一种。
top 能看实时负载,但要找具体某个进程的 CPU 占用,我更习惯用 top -p $(pgrep -d, java)。pgrep 可以按名字找进程号,比 ps -ef | grep 再 awk 提取 PID 要安全得多,因为 grep 本身也会出现在进程列表里。
kill 的坑主要集中在信号上。默认 kill PID 发的是 SIGTERM(15),给进程一个优雅退出的机会;kill -9 PID 是 SIGKILL,内核直接强制干掉进程,不给你清理资源的时间。能用 -15 就别急着 -9,除非进程真的无响应。我见很多新手一上来就 kill -9,结果进程是没了,端口还没释放,或者数据写了一半,反而更麻烦。
还有一个高频命令是 lsof -i:8080,用来查看端口被哪个进程占用。部署服务时经常收到 “port already in use”,这时候第一反应不应该是去 netstat -tlnp | grep 8080,而是用 lsof -i :8080 看 PID,然后 kill 掉对应进程。如果执行 lsof 报 command not found,可以用 ss -lntp | grep 8080,ss 是 netstat 的现代替代品,很多新系统默认带。
3. 查找、筛选与远程拷贝:find、grep、scp 组合起来才是生产力
3.1 find:不仅仅是按名字找文件
find 绝对是 Linux 基础指令里被低估的一个。很多人只会 find / -name "xxx",然后看着屏幕刷屏,等到花儿都谢了。其实 find 的过滤条件非常丰富:-type f 只找文件、-type d 只找目录、-mtime -7 找七天内有改动的、-size +100M 找大于 100MB 的文件,这些条件可以组合。
比如你想找出 /var/log 下三天内修改过的日志文件,可以用:
bash复制find /var/log -type f -mtime -3 -name "*.log"
如果你想找出当前目录下大于 500MB 的文件并列出大小:
bash复制find . -type f -size +500M -exec ls -lh {} \;
这里要注意 -exec 的写法:{} 是 find 找到的文件的占位符,\; 表示命令结束。-exec 后面的命令是逐个文件执行的,如果文件数量很多,性能会变差。一种更高效的做法是配合 xargs:
bash复制find . -type f -name "*.tmp" -print0 | xargs -0 rm -f
-print0 和 xargs -0 是配套的,能正确处理文件名里的空格和特殊字符。不用 -0 的话,遇到一个带空格的文件名就会出幺蛾子,这是 find 和 xargs 配合时最经典的坑。
用 find 的另外一个建议:尽量限制搜索路径。find / -name 会遍历整个文件系统,包括 /proc、/sys 这些虚拟目录,不仅慢,还可能产生大量无意义输出。通常先 cd 到目标目录再执行,或者用 -xdev 限制不跨文件系统,能快很多。
3.2 grep:过滤日志和定位问题
grep 是文本过滤的王者,但大多数人的用法只停留在 grep "keyword" file。实际排查问题的时候,我更常用的是递归搜索加文件类型过滤:
bash复制grep -rn --include="*.java" "Exception" ./src/
-r 是递归,-n 是显示行号,--include 只搜索指定模式的文件。如果你想在日志文件夹里找出所有包含 ERROR 的行,同时排除掉 .gz 压缩包,可以这样:
bash复制grep -r --exclude="*.gz" "ERROR" /var/log/
还有几个参数比较实用:-v 反向匹配,-c 统计匹配行数,-i 忽略大小写。比如统计一个日志文件里有多少行包含 timeout,不区分大小写:
bash复制grep -ci "timeout" app.log
管道组合也很重要。前面提到 ps -ef | grep java 会把自己也匹配进去,一个简单的技巧是加一个字符类:ps -ef | grep "[j]ava",这样 grep 本身的命令行里包含 [j]ava,但匹配的字符串是 java,于是 grep 进程不会被匹配到。这是老运维常用的写法,虽然现在我会直接上 pgrep,但看懂别人的脚本时这个技巧很管用。
3.3 scp/rsync:跨机器传文件
跨机器传文件是部署环节里绕不开的动作。scp 是最直观的:
bash复制scp app.jar root@192.168.1.10:/opt/app/
从远程拉文件回来则反过来:
bash复制scp root@192.168.1.10:/opt/app/app.log ./logs/
scp 传目录要加 -r。另外,scp 默认走 SSH 的 22 端口,如果目标机器的 SSH 端口改过,比如改成 22022,要写 -P 22022,注意是大写 -P(ssh 命令里是小写 -p,这个大小写差别我一开始经常搞混)。
文件多、文件大的时候,scp 就不够用了,这时候上 rsync。rsync 最大的优势是增量同步:只要文件没有变化,第二次执行会非常快。我最常用的写法是:
bash复制rsync -avz --progress ./dist/ root@192.168.1.10:/opt/app/
-a 归档模式保留权限和时间戳,-v 显示详情,-z 传输时压缩。--progress 显示进度。目录后面的斜杠有讲究:dist/ 表示把 dist 目录里的内容同步过去,而 dist 不带斜杠会把 dist 这个目录本身也带过去。这个区别我当初也踩过坑,部署出来的目录结构跟预期完全不一样。
如果传输中断了,rsync 可以在下次执行时接着传,而 scp 就只能从头再来,这也是大文件传输首选 rsync 的原因。
4. 软件安装与服务管理:从 nginx 到 docker 的常用姿势
4.1 包管理器:apt/yum/dnf 的本质
Linux 发行版很多,软件安装方式也各有差异。Debian/Ubuntu 系用 apt,CentOS 7 用 yum,CentOS 8 及以后用 dnf。但它们的核心逻辑是一样的:从一个软件仓库索引里找到软件包,自动解决依赖,下载安装。所以第一步永远是更新本地索引:
bash复制apt update
或者:
bash复制yum makecache
很多刚接触 Linux 的人最大的问题是:装不上某个软件就先不管三七二十一去网上找源码编译,结果依赖一塌糊涂。实际上绝大多数软件都可以直接从官方仓库安装,比如 apt install nginx。如果仓库里没有你想要的新版本,再去考虑加第三方源或者源码编译。
这里有一个实用经验:不要在服务器上执行 apt upgrade 这种全量升级,尤其在生产环境。全量升级会把内核、glibc 这些底层组件也升掉,很可能导致正在运行的服务出现兼容性问题。你只需要在安装软件时让它把必要的依赖拉起来就足够了,比如 apt install nginx 会自动升级 nginx 依赖的库,不会动无关的系统包。
4.2 安装 nginx 并用 systemctl 管理
用 nginx 来举个例子。Ubuntu 上一条命令装完:
bash复制apt install -y nginx
装完之后,nginx 服务通常不会自动启动。现代 Linux 发行版用 systemctl 管理服务,你可以这样:
bash复制systemctl start nginx
systemctl enable nginx
systemctl status nginx
start 是立刻启动,enable 是开机自启,status 查看运行状态。很多人执行完 start 没报错,但浏览器访问还是不通,这时候第一件事是看 status 输出里有没有 Active: failed,再用 journalctl -u nginx 看日志。journalctl 是 systemd 的日志工具,比到处翻 /var/log 里面的文件要统一得多。
改了 nginx 配置之后,推荐先执行 nginx -t 做语法检查,确认 OK 再执行:
bash复制nginx -s reload
reload 会平滑重载配置,不中断正在处理的请求。这个优雅程度比 systemctl restart nginx 高得多,生产环境里要改配置时优先用 reload,而不是 restart。
4.3 安装 Python:注意不要破坏系统 Python
开发机上装 Python 是很常见的需求。Ubuntu 20.04 默认带 Python 3.8,如果你需要一个新版本,第一反应别是去 python.org 下载源码编译,除非你真的很需要特定版本。很多场景下直接装系统包就够了:
bash复制apt install -y python3 python3-venv python3-pip
不建议手动卸载或替换系统自带的 /usr/bin/python3,因为系统的很多工具(比如 apt 本身)依赖它。如果你需要多个 Python 版本共存,可以用 update-alternatives 管理默认命令,或者用 pyenv 这类的版本管理工具。我曾经为了装新版本,直接把系统的 python3 软链接改掉,结果 apt 直接出问题,那个教训让我再也不碰系统 Python 了。
pip 安装依赖时,最好在虚拟环境里做:
bash复制python3 -m venv venv
source venv/bin/activate
pip install flask
虚拟环境能隔离项目依赖,避免不同项目之间互相污染。这也是我现在处理 Python 项目的默认姿势。
4.4 安装 Docker:从源到 daemon 配置
Docker 的安装同样优先考虑系统包。Ubuntu 上 apt install -y docker.io 就能装,虽然版本可能不是最新,但对大多数场景够用。安装完成后:
bash复制systemctl start docker
systemctl enable docker
然后跑一个简单的验证:
bash复制docker run hello-world
如果拉取镜像时经常超时,可以检查一下 /etc/docker/daemon.json,配置镜像加速地址。镜像源属于网络基础设施的一部分,实际使用中根据你所在网络环境调整即可。改完配置记得重启 Docker:
bash复制systemctl restart docker
这里要注意一点:docker 命令默认需要 root 权限,普通用户执行会报权限不足。解决办法是把自己加入 docker 组:
bash复制usermod -aG docker $USER
然后重新登录终端生效。但是要提前说明,加入 docker 组的用户相当于拥有了 root 能力,因为 docker 本身可以挂载宿主机目录。所以这个操作只建议在你自己可控的开发机上做,生产环境要谨慎。
5. 实战串讲:拿一台新服务器部署 Java 应用的完整指令链
5.1 环境检查:动手之前先看清楚机器情况
很多朋友第一次拿到服务器,第一件事就是急着装东西。我的习惯是先把机器情况摸清楚,几条基础命令就能完成:
bash复制uname -a
cat /etc/os-release
free -h
df -h
uname -a 看内核版本,/etc/os-release 看发行版版本和名称,free -h 看内存,df -h 看磁盘空间。这几条命令的结果决定了你后面该怎么装软件、能装多大体量的东西。我曾经在一台磁盘只剩 2GB 的机器上尝试部署服务,结果镜像都拉不下来,最后才发现是磁盘满了。
还要看一下系统架构,uname -m。同样是 Linux,x86_64 和 aarch64 的安装包完全不同。如果你要下载第三方二进制包,比如 JDK 的 tar.gz,选错架构安装后运行会直接报错。
5.2 装 JDK、传 jar 包、用 nohup 启动
假设你要部署一个 Java 应用。Ubuntu 上装 OpenJDK 17:
bash复制apt update
apt install -y openjdk-17-jdk
确认版本:
bash复制java -version
然后把本地打好的 jar 包传到服务器:
bash复制scp app.jar root@192.168.1.10:/opt/app/
到了服务器上,进入目录,用 nohup 启动:
bash复制cd /opt/app
nohup java -jar app.jar --spring.profiles.active=prod > app.log 2>&1 &
解释一下这条命令:nohup 让进程忽略挂断信号,即使你退出 SSH 终端,进程也不会被杀掉;> app.log 把标准输出重定向到日志文件;2>&1 把错误输出也合并到同一个地方;最后的 & 表示后台运行。
启动之后怎么确认它真的起来了?先看端口,再查日志:
bash复制ss -lntp | grep 8080
tail -f app.log
tail -f 会持续跟随日志文件的新增内容,是排查启动问题最直观的方式。看到 Started Application in x seconds 或者类似的日志,就说明启动成功了。
5.3 再启动一个实例前,先学会怎么停
如果要更新版本,需要先停掉旧进程。停进程的完整流程是:
bash复制ps -ef | grep app.jar
kill PID
先看一下进程号,然后用 kill 发 SIGTERM。几秒后再确认端口释放:
bash复制ss -lntp | grep 8080
如果端口还在,说明进程没有正常退出,可以再等几秒,或者看日志里是不是有卡住的任务。很不建议一上来直接 kill -9,因为这会让应用来不及做清理,比如数据库连接池没释放、临时文件没删掉,下次启动可能报端口被占用或者数据不一致。
这里有个小技巧:启动命令写进脚本的话,可以在脚本里先用 pgrep -f app.jar 把旧的进程号拿到,然后逐次发送 SIGTERM,等待几秒再检查是否退出。这样就把“查进程、杀进程、确认释放”的流程固化下来了。
5.4 日志太多怎么定位异常
应用跑起来之后,日志文件会越来越大。定位一个报错,我习惯的组合是:
bash复制grep -n "ERROR" app.log | tail -50
如果异常出现在某一次的请求里,可以用时间戳配合 sed 截取那一段:
bash复制sed -n '/2025-05-20 10:30:00/,/2025-05-20 10:35:00/p' app.log
sed 这个指令能按正则表达式定位行范围,在浩如烟海的日志里截取某个时间段的内容,比直接 cat 整个文件再肉眼查找高效得多。很多朋友对 sed 有畏惧感,其实不需要学太多,会这个区间打印的写法就够应付日常了。
如果某个异常反复出现,观察它的频率可以用:
bash复制grep -c "NullPointerException" app.log
这个数字能帮你看出来是偶发还是持续性问题,决定你是重启应用还是深入排查代码。
6. 这些指令不是背出来的,是用出来的
写到最后,说一点个人体会。很多人学 Linux 喜欢对着“命令大全”逐个背,但这种学习方法效率很低,因为命令之间是有关联的,孤立地背参数根本没有使用场景。我自己的经验是:每次部署一台机器、排查一次故障,把用到的指令随手记进自己的一份笔记里,久而久之你会发现,真正常用的 Linux 指令就那么几十个,高频场景也就那么几种。
你也可以在自己的笔记本上装个虚拟机或者用云主机,把这篇里提到的 useradd、chmod、find、grep、scp、systemctl 这些命令都手动敲一遍,尤其是故意制造一些错误,比如删不掉的目录、占用的端口、找不到的文件,看看报错信息长什么样。报错信息其实是 Linux 给你最好的提示,读懂它比记住任何一条指令都重要。
最后再分享一个小习惯:不要害怕用 man 或者 --help。遇到不确定的参数,先 man 命令名 看官方说明,或者直接 命令 --help 看简版用法。这不丢人,反而是最靠谱的学习路径。Linux 的生态实在太庞大了,没有人能记住所有指令的所有参数,但只要你掌握了排查思路和组合能力,遇到新指令也能很快上手。
