5 个提升 Linux 运维效率的命令技巧

日常使用 Linux 时,我们常常沿用一套已经熟悉多年的操作流程:一次创建一个目录,或者用 ps 和 grep 反复查找进程号。这些做法当然能用,但在多台服务器上维护脚本时,额外步骤会不断累积,也更容易产生笔误。

本文介绍 5 个更简洁的命令行技巧:用 mkdir -p 和花括号展开创建目录树,用 heredoc 一次写入多行文件内容,用 tar -C 直接解压到目标目录,用 pgrep/pkill 按进程名处理任务,以及用 pidwait 等待进程结束。文中示例在 Ubuntu 26.04 LTS 和 Rocky Linux 10 上测试,通常也适用于其他主流 Linux 发行版,除非某一节另有说明。

Shell 特性与命令选项的区别

这些技巧可以分为两类:Shell 特性和命令选项。理解两者的差异,有助于判断它们在交互式终端和脚本中的行为。

第一类是 Bash 在启动命令之前处理的特性,例如花括号展开和 heredoc。以花括号展开为例,Bash 会先把花括号转换成一组路径,再交给 mkdir;mkdir 本身并不会看到花括号。由于这些特性依赖具体的 Shell,切换到其他 Shell 或使用不同解释器运行脚本时,行为可能不同。

第二类是工具自身实现的选项,例如 tar -C 以及 pgrep、pkill 的进程匹配参数。它们不依赖 Shell,因此无论由哪个 Shell 启动,行为通常保持一致。

1. 用 mkdir -p 和花括号展开创建嵌套目录

部署新应用时,我们经常要一次性建立多个目录。假设要为用户 tecmint 部署一个名为 myapp 的应用,分别保存可执行文件、配置、日志、数据和脚本。

传统做法是逐条执行多个 mkdir。更快的做法是把 mkdir -p 和 Bash 花括号展开结合起来,用一条命令创建整棵目录树:

mkdir -pv ~/myapp/{bin,config/{nginx,systemd},logs,data/{uploads,cache,backups},scripts}

-p 会在需要时创建缺失的父目录,-v 会输出每个已创建目录。这样既完成创建,又能立即检查结果。

输出:

mkdir: created directory '/home/tecmint/myapp'
mkdir: created directory '/home/tecmint/myapp/bin'
mkdir: created directory '/home/tecmint/myapp/config'
mkdir: created directory '/home/tecmint/myapp/config/nginx'
mkdir: created directory '/home/tecmint/myapp/config/systemd'
mkdir: created directory '/home/tecmint/myapp/logs'
mkdir: created directory '/home/tecmint/myapp/data'
mkdir: created directory '/home/tecmint/myapp/data/uploads'
mkdir: created directory '/home/tecmint/myapp/data/cache'
mkdir: created directory '/home/tecmint/myapp/data/backups'
mkdir: created directory '/home/tecmint/myapp/scripts'

不同实现的提示文字和顺序可能不同,但最终目录结构相同。

花括号的展开规则如下:

  • a,b,c 会按逗号拆成多个路径。例如 logs,scripts 会展开为 logs 和 scripts。
  • 嵌套写法 config/{nginx,systemd} 会在同一个父目录下生成多个路径。
  • 逗号后不要加空格。写成 bin, logs 会让 Bash 无法按预期识别表达式,花括号可能原样传给命令。

查看目录树

可用 tree 检查结构。精简安装的 Linux 系统可能没有这个工具,请先安装。

Ubuntu 和 Debian:

sudo apt install -y tree

Rocky Linux、AlmaLinux 和 RHEL:

sudo dnf install -y tree

安装后运行:

tree ~/myapp

输出:

/home/tecmint/myapp
├── bin
├── config
│   ├── nginx
│   └── systemd
├── data
│   ├── backups
│   ├── cache
│   └── uploads
├── logs
└── scripts

10 directories, 0 files

创建前预览展开结果

较长的花括号表达式很容易写错,一个位置不对的逗号就可能生成意外路径。由于 Bash 在调用命令前就会完成展开,可以用 echo 预览,不会改变文件系统:

echo ~/myapp/{bin,config/{nginx,systemd},logs}

输出:

/home/tecmint/myapp/bin /home/tecmint/myapp/config/nginx /home/tecmint/myapp/config/systemd /home/tecmint/myapp/logs

/bin/sh 脚本不支持花括号展开

花括号展开是 Bash 特性,不是 POSIX Shell 标准。Ubuntu 和 Debian 上的 /bin/sh 通常指向 dash,不支持 Bash 风格的花括号展开。

因此,以 #!/bin/sh 开头的脚本可能把 bin,config 原样传给 mkdir,而不是拆成多个目录名。如果脚本依赖这类展开,应使用 Bash shebang:

#!/bin/bash

如果必须兼容 POSIX Shell,就应改用多个路径参数或循环。

2. 用 cat heredoc 一次创建带内容的文件

目录准备好后,下一步通常是创建配置文件。手动创建、打开编辑器、输入并保存适合偶尔操作;配置多台服务器或自动化部署时,这种方式就变得繁琐。

heredoc(here document)可以把多行文本直接传给命令,并用指定标记结束输入。配合 cat 和输出重定向,就能一次创建文件并写入内容:

cat << 'EOF' > ~/myapp/config/app.env
APP_NAME=myapp
APP_PORT=8000
LOG_DIR=/home/tecmint/myapp/logs
EOF

检查内容:

cat ~/myapp/config/app.env

输出:

APP_NAME=myapp
APP_PORT=8000
LOG_DIR=/home/tecmint/myapp/logs

命令各部分的作用:

  • << 'EOF' 开始 heredoc,EOF 是自定义结束标记,最后一行必须完全匹配。
  • 给 EOF 加引号会阻止 Bash 展开正文中的变量和命令替换。例如 PATH=$HOME/bin 会按字面写入。创建配置文件时,这通常是更安全的默认做法。
  • > ~/myapp/config/app.env 会把内容写入文件并替换原内容;改用 >> 则追加。
  • 结束标记 EOF 必须独占一行,前后不能有多余空格,除非使用明确允许缩进的 heredoc 写法。

在 heredoc 中展开变量

有时需要让 Bash 写入动态值,例如当前用户、主机名或部署日期。此时不要给结束标记加引号,Bash 会在把文本交给 cat 前完成变量和命令替换:

cat << EOF > ~/myapp/DEPLOYED.txt
Deployed by: $USER
Host: $(hostname)
Date: $(date +%F)
EOF

读取文件:

cat ~/myapp/DEPLOYED.txt

示例输出:

Deployed by: tecmint
Host: server1
Date: 2026-10-27

实际结果取决于执行命令时的用户、主机名和日期。

用 sudo tee 写入 root 所有的文件

heredoc 也适合创建 /etc 下的系统配置文件。常见错误是以为给命令加上 sudo,输出重定向也会获得同样的权限。

普通用户执行下面的命令时,因为没有目标目录写权限,会失败:

sudo cat << 'EOF' > /etc/sysctl.d/90-tecmint.conf
vm.swappiness = 10
EOF

可能出现类似错误:

bash: /etc/sysctl.d/90-tecmint.conf: Permission denied

原因是 Shell 会在执行 sudo cat 之前处理 > 重定向,并尝试用普通用户权限打开目标文件。应在提权状态下由 tee 打开和写入文件:

sudo tee /etc/sysctl.d/90-tecmint.conf > /dev/null << 'EOF'
vm.swappiness = 10
EOF

> /dev/null 会隐藏 tee 的正常输出,避免把配置内容打印到终端。若要追加内容,使用 sudo tee -a。

3. 用 tar -C 将压缩包解压到另一个目录

应用目录和配置文件准备好后,下一步通常是安装以 tar 包分发的软件。常见做法是先移动压缩包、切换目录,再执行解压;也可能先在 ~/Downloads 解压,再把目录移走。两种流程都有多余步骤。

tar 的 -C 选项会在解压前切换到指定目录。这样压缩包可以留在下载位置,内容直接进入目标位置。

Go 官方安装方式就是一个实用示例,可以把 Linux 压缩包直接解压到 /usr/local:

wget https://go.dev/dl/go1.25.3.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gz

注意:请把 go1.25.3 替换为官方网站当前列出的合适版本。

rm -rf /usr/local/go 会删除已有的 Go 安装。Go 项目建议这样做,因为直接在旧版本上覆盖解压可能留下过时文件。

解压后,Go 可执行文件位于 /usr/local/go/bin,整个过程不需要切换当前工作目录:

ls /usr/local/go/bin

输出:

go  gofmt

让 tar 自动识别压缩类型

传统用法中,不同压缩格式需要不同选项,例如 gzip 用 -z、bzip2 用 -j、xz 用 -J。解压时,GNU tar 通常能自动识别常见格式,不必每次记住对应参数。

下面的命令会把 xz 压缩包直接解压到 /opt:

sudo tar -xf archive.tar.xz -C /opt

只要系统具备相应的解压支持,同一方式也适用于 gzip、bzip2、xz 和 Zstandard 压缩的 tar 包。若 .tar.zst 无法处理,请安装 zstd。

Ubuntu 和 Debian:

sudo apt install zstd

Rocky Linux、AlmaLinux 和 RHEL:

sudo dnf install zstd

tar -C 的常见错误

目标目录必须事先存在,因为 tar -C 只会切换到该目录,不会创建它。目录缺失时会报错:

tar: /opt/node: Cannot open: No such file or directory
tar: Error is not recoverable: exiting now

把 mkdir -p 与 tar -C 结合即可避免这个问题。另一个常见情况是压缩包内带有版本化的顶层目录,例如 node-v24.11.0-linux-x64/。如果希望该目录中的文件直接落到目标目录,可用 --strip-components=1 去掉第一层路径:

sudo mkdir -p /opt/node
sudo tar -xf node-v24.11.0-linux-x64.tar.xz -C /opt/node --strip-components=1

注意:压缩包文件名只是示例。请从 Node.js 官方下载页选择合适版本和架构。

解压陌生压缩包前,先查看内部结构:

tar -tf archive.tar.xz | head

这会列出压缩包中的前几个路径,帮助你判断是否需要 --strip-components=1。去掉过多层级可能错误地压平目录结构,或丢失有用的路径层级。

4. 用 pgrep 和 pkill 按名称查找、终止进程

测试服务器、卡住的脚本或失去响应的应用,最终都可能需要终止。传统方式是运行 ps -A | grep -i apache2,找到 PID 后再手动交给 kill。这样虽然可行,但步骤更多,而且 grep 匹配到自身命令行时还可能造成干扰。

更简单的方式是用 pgrep 查找进程,再用 pkill 按名称或完整命令行发送信号。

可以先启动一个安全的测试进程。Python 自带的 HTTP 服务在 Ubuntu 和 Rocky Linux 上都可用,末尾的 & 让它转到后台:

python3 -m http.server 8000 &

用 pgrep 查找进程。-a 会同时显示 PID 和完整命令行,更容易确认目标:

pgrep -a python3

示例输出:

4821 python3 -m http.server 8000

你的 PID 会不同。确认目标后,可直接用 pkill 发送信号,无需复制 PID:

pkill -e -f http.server

示例输出:

python3 killed (pid 4821)

-f 允许匹配完整命令行,因此即使进程名只是 python3,也能找到这个 HTTP 服务。-e 会显示被发送信号进程的名称和 PID。

常用的 pgrep、pkill 选项:

  • -f 匹配完整命令行,而不只是进程名。适合多个程序共用同一可执行文件的场景,但模式必须足够精确。
  • -x 要求进程名完全匹配。例如 pkill -x ssh 只匹配名为 ssh 的进程,不会匹配 sshd。
  • -u tecmint 只搜索 tecmint 用户拥有的进程,可降低共享服务器上的误匹配风险。
  • -HUP 或 --signal HUP 发送 SIGHUP,而不是默认的 SIGTERM。SIGTERM 请求进程正常退出,SIGKILL(-9)则会强制终止,进程没有机会清理。

用 systemctl 停止系统服务,而不是 pkill

一些旧示例会用 pkill apache2 停止 Web 服务器。但对于 systemd 管理的服务,推荐使用 systemctl stop。

直接终止服务进程可能引发问题。如果服务单元配置了重启策略,systemd 可能再次启动它;否则服务可能进入失败状态,或让 systemd 记录的状态与实际意图不一致。

Ubuntu 和 Debian 上 Apache 服务名是 apache2:

sudo systemctl stop apache2

Rocky Linux、AlmaLinux 和 RHEL 上服务名是 httpd:

sudo systemctl stop httpd

pkill 更适合你明确要直接管理的进程,例如自己的测试服务器、卡住的脚本,或另一个终端中不应继续运行的程序。systemd 服务应交给 systemctl 正常停止。

5. 用 pidwait 等待进程结束

最后一个技巧建立在 pgrep 和 pkill 之上,但解决的是另一个问题:等待进程结束后再执行下一条命令。例如,你可能希望长时间运行的备份结束后才开始软件包升级。

常见替代方案是用 while 循环反复调用 pgrep,并用 sleep 暂停。procps-ng 提供的 pidwait 能等待匹配进程结束,写法更简单。

等待 rsync 结束后输出完成消息:

pidwait -x rsync && echo Backup finished at $(date +%T)

-x 要求精确匹配进程名,&& 确保只有 pidwait 成功退出时才打印消息。

需要注意,pidwait 只会等待命令启动时已经找到的进程。如果当时没有匹配进程,它会立即返回,而不会等待未来出现的 rsync。另外,如果存在多个同名进程,仅按进程名匹配也可能产生歧义。

pidwait 是否可用取决于已安装的 procps-ng 版本和发行版打包情况。检查命令:

command -v pidwait

如果没有该命令,可以使用轮询循环:

while pgrep -x rsync > /dev/null; do
    sleep 5
done
echo Backup finished at $(date +%T)

循环每 5 秒检查一次,直到没有名为 rsync 的进程。与 pidwait 一样,如果启动时没有匹配进程,循环也不会等待未来任务。

它只检查进程名,不检查具体备份是否成功。因此,在结果很关键时,应使用更精确的进程匹配或明确的任务状态机制。

在脚本中安全使用这些命令

把上述技巧放进多台服务器上的脚本时,以下习惯能降低风险。

pkill -f 最大的风险是匹配模式过宽。像 pkill -f python 这样的模式会终止当前用户启动的所有 Python 程序。应先用 pgrep -a 和相同模式预览,并加上 -u 限定到任务所属用户。

在脚本中使用 heredoc 时,除非确实需要变量展开,否则应给结束标记加引号,例如 << 'EOF'。未加引号的 heredoc 如果包含本应写入配置文件的 $,例如密码或 Nginx 的 $host,会被静默替换为空字符串,最终得到一个看似正常但无法工作的配置。

最后,mkdir -p 的 -m 权限选项只作用于最后创建的目录,父目录仍使用默认权限。如果需要一次创建整条路径并指定权限和所有者,下面的写法更可预测:

sudo install -d -m 750 -o tecmint -g tecmint /path/to/dir

总结

这 5 个技巧分别替代了一类重复操作:用 mkdir -p 和花括号展开建立目录树,用 heredoc 配合 cat 或 sudo tee 写入文件,用 tar -C 把压缩包解压到目标目录,用 pgrep/pkill 按名称管理进程,以及用 pidwait 在进程结束后继续执行。合理使用它们,可以让脚本更短,也能减少笔误。

Avatar photo

关于 木子

Email: muzi@vip.rockylinux.cn 微信:VebinLee QQ: 2306867585
Founder of the Rocky Linux Chinese community, MVP、VMware vExpert、TVP, advocate for cloud native technologies, with over ten years of experience in site reliability engineering (SRE) and the DevOps field. Passionate about Cloud Computing、Microservices、CI&CD、DevOps、Kubernetes, currently dedicated to promoting and implementing Rocky Linux in Chinese-speaking regions.
用一杯咖啡支持我们,我们的每一篇[文档]都经过实际操作和精心打磨,而不是简单地从网上复制粘贴。期间投入了大量心血,只为能够真正帮助到您。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇