FAQ-9:杂项与实用技巧
预计阅读时间:10 分钟
📖 目录
FAQ-9:杂项与实用技巧(Q104-Q128)
问题速查表
| 问题 | 章节 |
|---|---|
| Q104: 如何查看发行版和版本? | Q104 |
| Q105: 如何查看硬件信息? | Q105 |
| Q106: 如何生成 SSH 密钥? | Q106 |
| Q107: 如何批量复制 SSH 公钥? | Q107 |
| Q108: 如何让别名永久生效? | Q108 |
| Q109: 终端中显示 Git 分支? | Q109 |
| Q110: 如何查看命令完整路径? | Q110 |
| Q111: 如何修改默认 Shell? | Q111 |
| Q112: 如何快速跳转目录? | Q112 |
| Q113: 如何生成强密码? | Q113 |
| Q114: 如何修改主机名? | Q114 |
| Q115: 如何后台静默运行命令? | Q115 |
| Q116: 如何查看系统启动时刻? | Q116 |
| Q117: 如何批量替换多文件中的字符串? | Q117 |
| Q118: 如何查看文件被哪些进程使用? | Q118 |
| Q119: 命令行中做数学计算? | Q119 |
| Q120: 如何查看命令历史执行时间? | Q120 |
| Q121: 如何找两个文件的共同/不同行? | Q121 |
| Q122: 如何排除某些命令不记入历史? | Q122 |
| Q123: tmux/screen 基础操作? | Q123 |
| Q124: 如何查看 tldr 简化帮助? | Q124 |
| Q125: 如何查找最近修改的文件? | Q125 |
| Q126: 脚本中正确使用 cd? | Q126 |
| Q127: 如何限制命令执行时间? | Q127 |
| Q128: 如何获取公网 IP? | Q128 |
Q104: 如何查看发行版和版本?
# /etc/os-release:最可靠的方式(systemd 系统都有)
cat /etc/os-release
# 输出:PRETTY_NAME="Ubuntu 24.04.1 LTS" VERSION_ID="24.04"
# lsb_release:需要安装 lsb-release 包
lsb_release -a
# hostnamectl:同时显示主机名和系统信息
hostnamectl
Q105: 如何查看硬件信息?
# lscpu:CPU 信息(型号、核心数、架构)
lscpu
# lsmem:内存信息
lsmem
# lspci:PCI 设备(显卡、网卡、磁盘控制器等)
lspci
# lsusb:USB 设备
lsusb
# dmidecode:完整硬件信息(需 root)
sudo dmidecode -t system # 系统信息
sudo dmidecode -t memory # 内存条详细信息
Q106: 如何生成 SSH 密钥?
# ed25519 是当前推荐的密钥算法(比 RSA 更安全、更快)
ssh-keygen -t ed25519 -C "your_email@example.com"
# -C:注释(通常用邮箱,便于识别密钥归属)
# 默认保存位置:~/.ssh/id_ed25519(私钥)和 ~/.ssh/id_ed25519.pub(公钥)
# 如果需要兼容旧系统(不支持 ed25519),使用 RSA 4096 位
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
Q107: 如何批量复制 SSH 公钥?
# ssh-copy-id:将公钥追加到远程服务器的 authorized_keys
ssh-copy-id user@host
# 会要求输入一次密码,之后即可免密登录
# 批量部署(用循环)
for host in host1 host2 host3; do
ssh-copy-id user@$host
done
# Ansible 方式(大规模部署)
ansible all -m authorized_key -a "user=admin key={{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
Q108: 如何让别名永久生效?
# 在 ~/.bashrc 中添加别名(交互式 Shell)
echo "alias ll='ls -lh'" >> ~/.bashrc
source ~/.bashrc # 立即生效
# 在 ~/.bash_profile 中添加(登录 Shell)
# 区别:
# ~/.bashrc:每次打开终端都会加载(交互式非登录 Shell)
# ~/.bash_profile:SSH 登录时加载(登录 Shell)
# 推荐在 ~/.bashrc 中定义别名
Q109: 终端中显示 Git 分支?
# 在 ~/.bashrc 中配置 PS1 变量
# 需要先安装 git 并确保 ~/.bashrc 中有 __git_ps1 函数
# 通常由 /etc/bash_completion.d/git-prompt 提供
# 在 ~/.bashrc 中添加:
# GIT_PS1_SHOWDIRTYSTATE=1 # 显示 * 表示有未提交修改
# GIT_PS1_SHOWSTASHSTATE=1 # 显示 $ 表示有 stash
# GIT_PS1_SHOWUNTRACKEDFILES=1 # 显示 % 表示有未跟踪文件
# PS1='\[\e[32m\]\u@\h \[\e[33m\]\w\[\e[0m\]$(__git_ps1 " (%s)") \$ '
Q110: 如何查看命令完整路径?
# which:在 PATH 中查找可执行文件
which python3
# 输出:/usr/bin/python3
# type:显示命令类型(别名、内建命令、外部命令)
type ls
# 输出:ls is aliased to 'ls --color=auto'
# command -v:POSIX 标准写法
command -v git
Q111: 如何修改默认 Shell?
# chsh:修改默认登录 Shell
chsh -s /bin/zsh # 需要重新登录才生效
# 查看可用 Shell
cat /etc/shells
# 输出:/bin/sh, /bin/bash, /bin/zsh, ...
# 查看当前 Shell
echo $SHELL
Q112: 如何快速跳转目录?
# cd -:回到上一个目录(OLDPWD)
cd -
# pushd/popd:目录栈
pushd /path/to/dir1 # 压入栈并切换
pushd /path/to/dir2
popd # 弹出栈顶并切换
dirs -v # 显示目录栈内容
Q113: 如何生成强密码?
# openssl 生成随机字符串
openssl rand -base64 32
# 输出:K7gNUxxsGCj8PjugneSoGV2dpZ6TgY3NVA6JwNZPPkMyyPgFGmRCQ7eaGrTx
# base64 编码,32 字节 = 256 位随机性
# tr 从 /dev/urandom 选取特定字符
tr -dc 'A-Za-z0-9!@#$%' < /dev/urandom | head -c 32
# -d:删除不在字符集中的字符
# -c:取反(保留指定字符)
Q114: 如何修改主机名?
# hostnamectl:systemd 系统推荐方式
sudo hostnamectl set-hostname new-server-name
# 同时更新 /etc/hosts(防止某些程序解析旧主机名失败)
sudo sed -i "s/$(hostname)/new-server-name/g" /etc/hosts
# 临时修改(重启失效)
sudo hostname new-server-name
Q115: 如何后台静默运行命令?
# nohup + 重定向输出
# nohup:忽略 SIGHUP(终端关闭信号)
# > /dev/null 2>&1:丢弃所有输出
nohup command > /dev/null 2>&1 &
# disown 方式
command &
disown
# 两者区别:
# nohup:从一开始就防止 SIGHUP
# disown:在命令启动后从作业表中移除
Q116: 如何查看系统启动时刻?
# uptime -s:显示系统开机时刻
uptime -s # 输出:2026-07-20 11:08:36
# /proc/stat 的 btime:启动时间的 epoch 秒数
grep btime /proc/stat # btime 1788764916
# 运行时长相关内容见 faq04 Q55(uptime / uptime -p / /proc/uptime),此处不重复
Q117: 如何批量替换多文件中的字符串?
# find + exec sed:按文件名模式匹配替换
find . -type f -name "*.txt" -exec sed -i 's/old/new/g' {} +
# grep + xargs:按内容匹配替换
grep -rl "old" . | xargs sed -i 's/old/new/g'
# -r:递归 -l:只输出文件名
# 注意:sed -i 会直接修改文件,建议先用 -n 预览
sed -n 's/old/new/gp' file.txt # 只打印匹配行,不修改
Q118: 如何查看文件被哪些进程使用?
# lsof:列出打开文件的进程
lsof /path/to/file
# fuser:更简洁的方式
fuser -v /path/to/file # -v:详细输出
# 终止占用进程
fuser -k /path/to/file # -k:发送 SIGKILL
Q119: 命令行中做数学计算?
# Bash 内置整数运算
echo $((3 + 5)) # 输出:8
echo $((10 / 3)) # 输出:3(整数除法)
# bc:任意精度浮点运算
echo "scale=2; 10/3" | bc # 输出:3.33
# awk:浮点运算
awk "BEGIN {print 10/3}" # 输出:3.33333
Q120: 如何查看命令历史执行时间?
# HISTTIMEFORMAT:自定义 history 时间格式
# %F:日期(2026-06-15) %T:时间(14:30:01)
HISTTIMEFORMAT="%F %T " history
# 永久生效:写入 ~/.bashrc
echo 'HISTTIMEFORMAT="%F %T "' >> ~/.bashrc
Q121: 如何找两个文件的共同/不同行?
# comm:比较两个已排序文件
# <(sort f1):进程替换,将排序结果作为文件
comm <(sort f1) <(sort f2)
# 第 1 列:仅在 f1 中的行
# 第 2 列:仅在 f2 中的行
# 第 3 列:共同行
# 只取共同行
comm -12 <(sort f1) <(sort f2)
# grep 方式:找共同行
grep -Fxf f1 f2 # -F:固定字符串 -x:整行匹配 -f:从文件读取模式
Q122: 如何排除某些命令不记入历史?
# HISTIGNORE:按模式排除命令
export HISTIGNORE="ls:ll:cd:pwd:clear:history:exit"
# HISTCONTROL:控制重复命令的记录方式
export HISTCONTROL=ignoreboth # 忽略重复命令和以空格开头的命令
# ignoreboth = ignorespace + ignoredups
# 以空格开头的命令不记入历史
export HISTCONTROL=ignorespace
Q123: tmux/screen 基础操作?
# tmux(推荐,功能更强大)
tmux new -s mysession # 创建会话
# Ctrl+B D # 分离会话(后台运行)
tmux attach -t mysession # 重新连接
tmux ls # 列出所有会话
# screen(传统工具)
screen -S mysession # 创建会话
# Ctrl+A D # 分离会话
screen -r mysession # 重新连接
screen -ls # 列出所有会话
# 区别:tmux 支持窗口分割、更好的脚本化、状态栏自定义
Q124: 如何查看 tldr 简化帮助?
# tldr:Too Long; Didn't Read,命令的简明示例
# 比 man 简洁,比 --help 详细
sudo apt install tldr
tldr tar
# 或使用在线版
curl cheat.sh/tar
Q125: 如何查找最近修改的文件?
# find + -printf:显示修改时间并按时间排序
find /path -type f -printf '%T+ %p\n' | sort -r | head -20
# %T+:修改时间(ISO 格式) %p:文件路径
# sort -r:逆序(最新在前)
# 更简单的方式
ls -lt | head -20
Q126: 脚本中正确使用 cd?
# 子 Shell 方式:cd 不会影响当前 Shell 的工作目录
(cd /path && ./script.sh)
# pushd/popd 方式
pushd /path > /dev/null
./script.sh
popd > /dev/null
Q127: 如何限制命令执行时间?
# timeout:在指定时间后自动终止命令
# 10s:10 秒后终止
timeout 10s command
# 超时退出码为 124
timeout 5s sleep 10; echo $? # 输出:124
Q128: 如何获取公网 IP?
# 通过外部服务查询
curl -s ifconfig.me
curl -s ipinfo.io/ip
curl -s api.ipify.org
# 通过 DNS 查询
dig +short myip.opendns.com @resolver1.opendns.com
真实案例
案例 A:HISTIGNORE 配置错误导致密码泄露到 history
现象:运维在命令行中输入了 mysql -u root -pMyS3cret,虽然 HISTIGNORE 配置了排除 mysql 命令,但密码仍然出现在 history 中。
根因:HISTIGNORE 的模式是 mysql,但实际命令是 mysql -u root -pMyS3cret,模式不匹配(需要 mysql*)。
修复:export HISTIGNORE="mysql*" 使用通配符匹配所有 mysql 开头的命令。或者用空格开头输入敏感命令(配合 HISTCONTROL=ignorespace)。
案例 B:sed -i 批量替换误伤二进制文件
现象:用 find . -name "*.conf" -exec sed -i 's/old/new/g' 替换配置,但误匹配了 .conf 二进制文件,导致文件损坏。
根因:find 的 -name 模式匹配了不该修改的文件,sed 对二进制内容做文本替换会导致数据损坏。
修复:替换前先用 file 命令确认文件类型,只对文本文件操作:find . -name "*.conf" -exec file {} \; | grep "ASCII text"。
案例 C:pushd/popd 忘记 popd 导致工作目录异常
现象:脚本中使用 pushd /tmp 切换到临时目录执行操作,但脚本异常退出时没有执行 popd,后续命令在错误的目录中执行。
# 脚本中
pushd /tmp
# ... 执行操作 ...
# 脚本在此处异常退出,popd 未执行
# 当前目录仍为 /tmp
根因:pushd 将目录压入栈,但如果脚本在 popd 前退出,目录栈不会自动恢复。
修复:使用子 Shell 执行 cd 操作(不影响当前 Shell),或用 trap 确保 popd 执行。
预防措施
- 敏感操作(密码、密钥)用空格开头输入,配合 HISTCONTROL=ignorespace 不记入历史
- sed -i 批量替换前先用
-n预览,确认无误再执行 - tmux/screen 长期运行任务,避免 SSH 断开导致任务丢失
- 修改 Shell 配置(.bashrc、.zshrc)前备份:
cp ~/.bashrc ~/.bashrc.bak - SSH 密钥使用 ed25519 算法,比 RSA 更安全且更快
案例 D:tmux 会话因窗口标题含特殊字符导致重连失败
现象:tmux 会话名称含中文或特殊字符,tmux attach 时报 can't find session,但 tmux ls 能看到该会话。
# 列出所有 tmux 会话
$ tmux ls
中文会话: 1 windows (created Mon Jul 31 10:00:00 2026)
# 尝试重连失败
$ tmux attach -t 中文会话
no server running on /tmp/tmux-1000/default
# 原因:终端不支持 UTF-8 会话名传递给 tmux
# 修复:用会话 ID 代替名称
$ tmux ls -F '#{session_id} #{session_name}'
$1 中文会话
# 用 session ID 重连
$ tmux attach -t $1 ← 成功
# 预防:tmux 会话使用英文和下划线命名
$ tmux new -s my_session_01
根因:tmux 会话名包含非 ASCII 字符时,终端编码与 tmux 服务器之间的字符传递可能出错。不同终端模拟器对 UTF-8 的支持程度不同,导致会话名解析失败。
修复:tmux 会话名称使用 ASCII 字符(英文、数字、下划线)。如果已有中文会话,用 tmux ls -F '#{session_id} #{session_name}' 获取 session ID,用 ID 重连。脚本中避免使用中文会话名。
案例 E:etckeeper 自动提交与 Ansible 配置管理冲突
现象:Ansible playbook 修改 /etc/nginx/nginx.conf 后,etckeeper 自动提交了变更,但 Ansible 再次执行时报 file has been modified 冲突。
# 检查 etckeeper 提交历史
$ cd /etc && git log --oneline -5
a1b2c3d Updated /etc/nginx/nginx.conf
e4f5g6h Updated /etc/hosts
i7j8k9l Initial commit
# etckeeper 每天自动提交 /etc 目录的变更
# Ansible 每次执行也会修改 /etc 文件
# 修复:在 Ansible playbook 中处理 etckeeper
$ cat playbook.yml
- name: Update nginx config
hosts: web
tasks:
- name: Stop etckeeper auto-commit
ansible.builtin.systemd:
name: etckeeper
state: stopped
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Reload nginx
- name: Commit etc changes
ansible.builtin.shell: cd /etc && git add -A && git commit -m "Ansible: nginx config update"
- name: Restart etckeeper
ansible.builtin.systemd:
name: etckeeper
state: started
根因:etckeeper 会自动监控 /etc 目录变更并提交到 Git 仓库,而 Ansible 也会修改同一目录下的配置文件。两者同时操作会产生 Git 冲突。etckeeper 的设计是记录手动变更,与自动化配置管理工具需要协调。
修复:Ansible playbook 中在修改 /etc 前先提交 etckeeper 的变更,修改后再提交。或在 Ansible 中禁用 etckeeper 的自动提交,改用 Ansible 自己的版本管理。长期方案:将 etckeeper 作为变更审计工具,Ansible 作为配置管理工具,明确各自职责。
延伸阅读
- 4.5:网络故障排查 网络故障排查——网络命令与 SSH 密钥认证基础
- 2.4:进程管理 进阶命令与技巧——别名、history、sed/awk 等高效 shell 招式
- 2.11:SSH 深入 SSH 深入——~/.ssh/config、ProxyJump 与证书认证
- 2.1:Shell 脚本入门 Shell 脚本入门——脚本化日常任务的起点