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 作为配置管理工具,明确各自职责。

延伸阅读

↑ 回到顶部