APT包管理

1️⃣包管理核心配置文件

/etc/apt/
├── sources.list           # 主配置文件
├── sources.list.d/        # 额外源目录
│   └── cuda.list         # CUDA专用源
├── trusted.gpg            # 旧版GPG存储(不推荐)
├── trusted.gpg.d/         # 旧版GPG密钥目录(不推荐)
│   └── nvidia.gpg        # NVIDIA密钥
└── preferences.d/         # 包优先级配置

配置源文件语法解析

# 完整语法示例
sudo tee /etc/apt/sources.list.d/cuda.list << 'EOF'
# 基本格式:deb [选项] 仓库URL 发行版 组件
deb [signed-by=/usr/share/keyrings/nvidia-cuda-keyring.gpg arch=amd64] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ /
EOF

# 选项解释:
# signed-by=    # 指定GPG密钥路径(必须)
# arch=         # 指定架构(amd64/arm64等)
# types=        # 包类型(deb/deb-src)
# trusted=yes   # 信任此源(不推荐生产环境使用)

/usr/share/keyrings//etc/apt/trusted.gpg 的关系

  • 旧机制(已废弃)

    # Ubuntu 20.04及之前(已废弃)
    /etc/apt/trusted.gpg           # 主密钥环(全局信任)
    /etc/apt/trusted.gpg.d/        # 附加密钥目录
    
    # 使用方式:
    sudo apt-key add keyfile.pub    # 添加到全局信任
    # 问题:任何源添加的密钥都获得全局信任,安全风险!
    
  • 新机制(推荐)

    # Ubuntu 22.04+(现代方法)
    /usr/share/keyrings/           # 密钥存储目录
    /etc/apt/sources.list.d/       # 源配置文件
    
    # 使用方式:
    deb [signed-by=/path/to/key.gpg] http://repo.example.com ...
    # 好处:每个源指定自己的密钥,最小权限原则
    

2️⃣源配置步骤

以CUDA12.1安装为例,配置如下:

方法1:使用官方deb包[配置源,非APP]配置(推荐,最简单):

# 下载deb包 → 2. 安装deb包 → 3. 自动创建配置 → 4. APT识别新源 → 5. 可以安装CUDA
#  xx-keyring_*.deb 是一个配置包,它只负责告诉APT系统:"xx仓库在这里,密钥是这个",安装后就可以用apt install安装真正的xx软件了。这是Linux推荐的现代APT仓库配置方法。
# CUDA 12.1的官方deb包会自动处理源和密钥
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update
仓库类型 典型包名格式 示例
NVIDIA *-keyring*.deb cuda-keyring_1.1-1_all.deb nvidia-keyring_1.1-1_all.deb
Docker *-archive-keyring*.deb docker-archive-keyring_*.deb
Google *-archive-keyring*.deb google-archive-keyring_*.deb
Microsoft *-archive-keyring*.deb*-repo*.deb microsoft-archive-keyring_*.deb packages-microsoft-prod.deb
GitHub CLI *-archive-keyring*.deb github-archive-keyring_*.deb
MongoDB *-server-*.deb (包含源配置) mongodb-org-server_*.deb

最常见的三种模式,如下:

1. *-keyring*.deb         # 侧重密钥管理
2. *-archive-keyring*.deb # 包含完整仓库配置
3. *-repo*.deb            # 明确表示仓库配置

方法2:手动配置(理解原理):

# 步骤1:创建密钥存储目录
sudo mkdir -p /usr/share/keyrings

# 步骤2:下载并转换密钥为.gpg格式
# --dearmor选项将ASCII格式转换为二进制.gpg格式
curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/3bf863cc.pub | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-cuda-keyring.gpg

# 步骤3:创建源文件,使用signed-by指定密钥
echo "deb [signed-by=/usr/share/keyrings/nvidia-cuda-keyring.gpg arch=amd64] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ /" | sudo tee /etc/apt/sources.list.d/cuda.list

# 步骤4:更新包列表
sudo apt update

3️⃣GPG问题诊断与修复

常见GPG错误及解决方法

# 错误1:签名验证失败
# 症状:sudo apt update 时报 "The following signatures couldn't be verified"
# 解决:
# 1. 验证密钥是否正确安装
sudo gpg --show-keys /usr/share/keyrings/nvidia-cuda-keyring.gpg

# 2. 重新下载密钥
sudo rm /usr/share/keyrings/nvidia-cuda-keyring.gpg
curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/3bf863cc.pub | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-cuda-keyring.gpg

# 3. 检查源文件中signed-by路径是否正确
cat /etc/apt/sources.list.d/cuda.list

# 错误2:密钥格式问题
# 解决:确保使用--dearmor转换
# ❌ 错误做法(会失败):
curl -fsSL https://.../3bf863cc.pub > /usr/share/keyrings/nvidia-key.gpg

# ✅ 正确做法:
curl -fsSL https://.../3bf863cc.pub | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-key.gpg

# 错误3:密钥权限问题
# 解决:设置正确权限
sudo chmod 644 /usr/share/keyrings/nvidia-cuda-keyring.gpg

GPG调试工具

# 检查GPG密钥信息
sudo gpg --list-keys --keyring /usr/share/keyrings/nvidia-cuda-keyring.gpg

# 验证密钥指纹
sudo gpg --show-keys --with-fingerprint /usr/share/keyrings/nvidia-cuda-keyring.gpg

# 测试源连接(不验证签名)
sudo apt update -o APT::Get::AllowUnauthenticated=true

# 查看详细的APT调试信息
sudo apt update -o Debug::pkgAcquire=true 2>&1 | grep -A5 -B5 "signature"

Debian/Ubuntu的官方keyring包

系统内置的keyring管理

# Debian官方keyring包
apt list '*archive-keyring*' | grep installed

# 常见系统keyring包:
debian-archive-keyring      # Debian官方仓库密钥
ubuntu-archive-keyring      # Ubuntu官方仓库密钥
ubuntu-keyring              # Ubuntu发行版密钥
debian-keyring              # Debian开发者密钥

# 查看已安装的keyring包
dpkg -l | grep -E '(keyring|archive-keyring)'

Keyring包的工作原理

# 一个典型的keyring包结构:
$ dpkg -c docker-archive-keyring_2022.02.22_all.deb
# 输出:
# ./usr/share/keyrings/docker-archive-keyring.gpg
# ./etc/apt/sources.list.d/docker.list

# 内部包含:
# 1. GPG密钥文件
# 2. APT源配置文件
# 3. 有时包含优先级配置文件

4️⃣APT命令使用大全(现代化版本)

4.1 包查询与信息获取

命令 用途 示例 说明
apt list 列出包 apt list cuda* 支持通配符
apt list --installed 已安装包 `apt list --installed grep nvidia`
apt list --upgradable 可升级包 apt list --upgradable 需要sudo
apt search 搜索包 apt search "^cuda-12" 正则搜索
apt show 包详情 apt show cuda-toolkit-12-1 详细信息
apt policy 版本策略 apt policy cuda 查看优先级
apt depends 依赖关系 apt depends cuda-12-1 依赖树
apt rdepends 反向依赖 apt rdepends libcudnn8 谁依赖它

实际应用示例:

# 1. 查看CUDA所有可用版本
apt list 'cuda*' | grep -v installed

# 2. 查看包的具体信息(版本、大小、依赖)
apt show cuda-toolkit-12-1

# 3. 查看包优先级(防止版本冲突)
apt policy cuda-toolkit-12-1

# 4. 搜索特定架构的包
apt search "nvidia.*driver.*:amd64"

# 5. 查看依赖关系的图形化展示
apt depends --recurse cuda-12-1 | grep -E "依赖|推荐|建议"

4.2 安装与升级命令

命令 用途 示例 说明
apt install 安装包 sudo apt install cuda-12-1 基本安装
apt install --dry-run 模拟安装 apt install -s cuda-12-1 不加sudo
apt install --no-install-recommends 不装推荐包 sudo apt install --no-install-recommends cuda-toolkit-12-1 最小安装
apt reinstall 重新安装 sudo apt reinstall cuda-12-1 修复安装
apt upgrade 安全升级 sudo apt upgrade 不删除包
apt full-upgrade 完全升级 sudo apt full-upgrade 可能删除包
apt install --only-upgrade 仅升级 sudo apt install --only-upgrade cuda-12-1 指定升级
apt install --fix-broken 修复依赖 sudo apt --fix-broken install 修复问题

使用示例:

# CUDA安装最佳实践
# 1. 安装前先模拟查看
apt install -s cuda-12-1

# 2. 最小化安装(节省空间)
sudo apt install --no-install-recommends cuda-toolkit-12-1

# 3. 安装特定版本
sudo apt install cuda-toolkit-12-1=12.1.0-1

# 4. 安装时跳过询问
sudo DEBIAN_FRONTEND=noninteractive apt install cuda-12-1 -y

# 5. 批量安装CUDA组件
sudo apt install cuda-toolkit-12-1 cuda-libraries-12-1 cuda-nvcc-12-1

4.3 移除与清理命令

命令 用途 示例 说明
apt remove 移除包 sudo apt remove cuda-12-1 保留配置
apt purge 完全移除 sudo apt purge cuda-12-1 删除配置
apt autoremove 自动移除 sudo apt autoremove 清理依赖
apt autoclean 清理旧包 sudo apt autoclean 清理.deb
apt clean 清理缓存 sudo apt clean 清空缓存
apt-mark auto 标记自动 sudo apt-mark auto cuda-12-1 可自动移除
apt-mark manual 标记手动 sudo apt-mark manual cuda-12-1 保护不被移除

使用示例:

# CUDA清理操作
# 1. 完全移除CUDA(推荐)
sudo apt purge 'cuda*' 'nvidia*' 'libcudnn*'

# 2. 移除孤儿包
sudo apt autoremove --purge

# 3. 仅移除CUDA但保留驱动
sudo apt remove cuda-toolkit-12-1 cuda-libraries-12-1

# 4. 查看将被移除的包
apt remove -s cuda-12-1

# 5. 清理特定源的包
sudo apt purge $(apt list --installed | grep cuda | cut -d'/' -f1)

4.4 缓存与配置管理

命令 用途 示例 说明
apt update 更新列表 sudo apt update 必须步骤
apt update --fix-missing 修复缺失 sudo apt update --fix-missing 修复问题
apt edit-sources 编辑源 sudo apt edit-sources 编辑主源
apt-cache stats 缓存统计 apt-cache stats 统计信息
apt-cache policy 策略查看 apt-cache policy 所有策略
apt-get source 下载源码 apt-get source cuda-12-1 下载源码包
apt download 下载deb apt download cuda-12-1 仅下载

使用示例:

# 缓存管理技巧
# 1. 仅更新CUDA源
sudo apt update -o Dir::Etc::sourcelist="sources.list.d/cuda.list"

# 2. 查看缓存大小
sudo du -sh /var/cache/apt/archives

# 3. 清理特定包的缓存
sudo apt clean cuda-12-1

# 4. 查看APT操作历史
grep -E "(install|remove|purge)" /var/log/apt/history.log

# 5. 生成包状态报告
dpkg --get-selections > package-list.txt

4.5 高级APT操作

# 1. 设置包保持(不升级)
sudo apt-mark hold cuda-12-1
# 查看被保持的包
apt-mark showhold

# 2. 允许降级(修复问题)
sudo apt install cuda-toolkit-12-1/oldstable

# 3. 离线安装(先下载再安装)
apt download cuda-12-1
sudo dpkg -i cuda-12-1_*.deb
sudo apt --fix-broken install

# 4. 安装指定发布的包
sudo apt install -t ubuntu-advantage-tools cuda-12-1

# 5. 创建本地APT仓库
# 下载所有依赖
apt-get download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances cuda-12-1 | grep "^\w" | sort -u)

# 6. 检查包完整性
debsums -s cuda-12-1

环境变量

  • 常见变量

    变量名 变量说明
    PATH 是系统路径。
    HOME 是系统根目录。
    HISTSIZE 是指保存历史命令记录的条数。
    LOGNAME 是指当前用户的登录名。
    HOSTNAME 是指主机的名称,若应用程序要用到主机名,通常是从这个环境变量中来取得的。
    SHELL 是指当前用户用的是哪种shell。
    LANG/LANGUGE 是和语言相关的环境变量,使用多种语言的用户可以修改此环境变量。
    MAIL 是指当前用户的邮件存放目录
  • 设置环境变量方法

    • 通过echo 显示字符串(指定环境变量)
    • 通过export 设置新的环境变量
    • 通过env 显示所有环境变量
    • 通过set 命令显示所有本地定义的shell 变量
    • 通过unset 命令来清除环境变量

函数定义function

function get_make_command()
{
  echo command make
}
  • function get_make_command():定义名为get_make_command的函数(function关键字可选)。
  • echo command make:函数返回字符串command make。这里的command是 Shell 内置命令,用于强制执行外部命令(而非别名或函数)。

复合命令{}和()

{ cmd1; cmd2; cmd3; } 的含义

  • 作用:将多个命令组合成一个逻辑分组代码块,将多个命令视为一个整体,便于重定向条件判断,在当前 Shell 进程中顺序执行。
  • 语法要求
    • {} 必须与命令用空格或换行隔开(否则 Shell 会解析错误)。
    • 命令之间必须用 ; 或换行分隔。
    • 最后一个命令后的 ; 可选(但建议保留以保持一致性)。
特性 { cmd1; cmd2; } (cmd1; cmd2)
执行环境 当前 Shell 进程(共享变量和状态) 子 Shell 进程(隔离环境,修改不影响父进程)
变量修改 会影响当前 Shell 不会影响当前 Shell
性能 更快(无进程创建开销) 稍慢(需创建子进程)
典型用途 代码块重定向、循环体 临时环境隔离、并行任务

示例

# { } 修改当前 Shell 的变量
{ var=123; }; echo $var  # 输出 123

# ( ) 在子 Shell 中修改变量,不影响当前 Shell
( var=456 ); echo $var   # 输出空(或之前的值)

命令分隔符; && ||

;&&|| 的区别

符号 作用 示例 说明
; 顺序执行,无论前一个命令是否成功。 cmd1; cmd2 即使 cmd1 失败也执行 cmd2
&& 逻辑与,仅当前一个命令成功(退出状态码 0)时才执行下一个命令。 cmd1 && cmd2 cmd1 失败则跳过 cmd2
` ` 逻辑或,仅当前一个命令失败(非 0)时才执行下一个命令。

关键区别

  • ;无脑执行,不关心命令的成功与否。
  • &&||条件执行,依赖前一个命令的退出状态。

示例对比

false; echo "This will run"      # 输出 "This will run"(; 无视失败)
false && echo "This won't run"  # 无输出(&& 因 false 失败而跳过)
false || echo "This will run"   # 输出 "This will run"(|| 在失败时执行)

命令替换与执行$( )

相当于`cmd …`操作,但$(cmd ...)可以嵌套。

$(get_make_command) "$@"
current_time=$(date +%s)  # 执行 `date +%s` 命令,将结果(时间戳)赋值给变量
  • $(get_make_command):命令替换,执行函数并将结果(command make)替换到此处。
  • "$@":传递脚本接收到的所有参数(原样保留引号和空格)。

算术运算 $(( ))

相当于`…`操作,但$( )可以嵌套。

a=5
b=3
sum=$(($a + $b))  # 计算 5+3,结果为 8
echo "和: $sum"   # 输出:和: 8
echo $(( 2*3-4 ))
  • 仅支持整数运算,不支持浮点数。
  • 运算符包括:+-*/%(取余)、**(幂运算)等。
  • 括号内的空格可选,但推荐使用以提高可读性

变量扩展${...}

${...}变量扩展的语法,用于引用变量、对变量进行操作(如替换、截取、默认值等)。

name="Alice"
echo "Hello, ${name}!"  # 输出:Hello, Alice!

# 变量替换
text="Hello World"
echo "${text/World/Bob}"  # 输出:Hello Bob

常见用法

  • ${variable}:引用变量。
  • ${variable-default}:若变量未定义,则使用默认值。
  • ${variable/pattern/replacement}:替换变量中的文本,一次替换。
  • ${variable//pattern/replacement}:替换变量中的文本,全量替换。
  • ${variable:offset:length}:截取子字符串。

$开头的特殊变量

在 Shell 脚本中,$开头的特殊变量用于引用预定义的参数、状态码或进程信息,如:

  • $? 用于检查命令是否成功执行。
  • $$ 常用于生成唯一的临时文件名(如/tmp/tempfile.$$)。
  • $@ 是处理脚本参数的首选方式,能正确处理包含空格的参数。

以下是常见的特殊变量及其含义:

① 位置参数(传递给脚本 / 函数的参数)

变量 含义
$0 脚本或函数的名称(完整路径或调用时的名称)。 示例:若脚本名为/home/user/test.sh,则$0test.sh./test.sh
$1, $2, …, $9 第 1 到第 9 个参数。超过 9 个参数需用${10}${11}等。 示例./script.sh a b$1a$2b
$# 参数的数量(不包括$0)。 示例./script.sh a b c$#3
$@ 所有参数,作为独立的字符串示例"$@" 等价于 "$1" "$2" ...
$* 所有参数,作为单个字符串(由 IFS 变量分隔,默认为空格)。 示例"$*" 等价于 "$1 $2 ..."

$@$\* 的关键区别, 当被双引号包围时:

  • "$@" 扩展为 "$1" "$2" "$3"(保留参数的独立性,适合传递带空格的参数)。
  • "$*" 扩展为 "$1 $2 $3"(合并为一个字符串,可能导致参数被错误分割)。

② 状态码与进程信息

变量 含义
$? 上一个命令的退出状态码(0 表示成功,非 0 表示失败)。 示例ls /nonexistent; echo $?2(文件不存在)。
$$ 当前 Shell 进程的 PID(进程 ID)。 示例echo $$12345(当前脚本的 PID)。
$! 最后一个后台进程的 PID。 示例sleep 10 & echo $!12346(sleep 进程的 PID)。

③ 其他特殊变量

变量 含义
$- 当前 Shell 的选项标志(如-x表示调试模式)。 示例echo $-himBHs(当前启用的选项)。
$_ 上一个命令的最后一个参数。 示例ls /tmp; echo $_/tmp

示例演示

假设脚本test.sh内容如下:

#!/bin/bash
echo "脚本名称: $0"
echo "参数数量: $#"
echo "所有参数(独立字符串): $@"
echo "所有参数(单字符串): $*"
echo "当前PID: $$"

# 执行命令并检查状态码
ls /nonexistent >/dev/null 2>&1
echo "上一个命令的状态码: $?

执行结果:

$ ./test.sh a "b c" d
脚本名称: ./test.sh
参数数量: 3
所有参数(独立字符串): a b c d
所有参数(单字符串): a b c d
当前PID: 12345
上一个命令的状态码: 2

Shell内置命令

Shell 的几个核心内建命令:commandeval,以及相关的 builtintypeexec。理解它们是成为高级 Shell 用户的关键。这些内建命令赋予了 Shell 脚本强大的元编程能力:

  • command / builtin:让你能穿透别名和函数的“魔法”,触及底层。
  • eval:让你能创造魔法,将字符串变为可执行代码。
  • type:让你能洞察魔法的本质,看清命令的真实身份。
  • exec:让你能进行夺舍,彻底替换当前进程。

command:命令的“本尊”调用器

command 的核心作用是 绕过 Shell 的别名和函数查找机制,直接调用“原始”命令

  • 基本语法command [-pVv] 命令 [参数...]

  • 常用选项

    • -p:使用 标准的系统 PATH/bin:/usr/bin 等)来搜索命令,忽略用户自定义的 PATH。这对于编写可移植的安全脚本非常重要。
    • -v查询模式。显示 Shell 将如何解释给定的命令名(是别名、函数、内建命令还是外部程序)。
    • -V详细查询模式。比 -v 提供更详细的描述。
  • 主要用途与示例:

    a. 屏蔽别名

    # 假设 ls 被别名化为 `ls --color=auto`
    alias ls='ls --color=auto'
    ls                # 会调用带颜色的 ls (别名)
    command ls        # 会调用不带颜色的、原始的 /bin/ls
    \ls               # 另一种屏蔽别名的方法:使用反斜杠转义
    

    b. 在函数内部安全调用同名命令(防止递归):

    ls() {
        echo "函数开始";
        command ls -l "$@"; # 这里调用的是系统 ls,而不是这个函数本身
        echo "函数结束";
    }
    

    c. 确定命令的真实身份

      command -v ls     # 可能输出: `alias ls='ls --color=auto'`
      command -v cd     # 输出: `cd` (内建命令)
      command -v grep   # 输出: `/usr/bin/grep` (外部命令)
      command -v my_func # 输出: `my_func` (如果是函数)
    

eval:字符串的“二次编译”执行器

eval 的核心作用是将其参数拼接成一个字符串,然后将这个字符串交给 Shell 再次解析和执行(可以理解为in-place plugin,Shell不变!)。它工作在命令解析的“最终阶段”。

  • 基本语法eval [参数字符串]

主要用途与示例:

  • 动态构造并执行复杂命令(这是其最经典、也最危险的用途):

    # 从配置文件或用户输入动态构造命令
    condition="GREATER_THAN"
    value=10
    # 根据条件动态选择运算符
    case $condition in
        GREATER_THAN) op="-gt" ;;
        LESS_THAN)    op="-lt" ;;
    esac
    # 构造一个 test 命令的字符串
    cmd_string="[ \$1 $op $value ]"
    # 此时 cmd_string 的内容是: [ $1 -gt 10 ]
    # 使用 eval 来执行这个“命令字符串”
    eval "$cmd_string" 50 && echo "成立" || echo "不成立" # 输出: 成立
    eval "$cmd_string" 5  && echo "成立" || echo "不成立" # 输出: 不成立
    
  • 间接变量引用(模拟指针)

    var_name="MY_PATH"
    var_value="/usr/local/bin"
    # 将变量名赋值给另一个变量
    ref="var_name"
    echo ${!ref}      # 方法1: 使用 ${!var} 间接引用(bash特性),输出: MY_PATH
    eval "echo \$$ref" # 方法2: 使用 eval,同样输出: MY_PATH
    # 通过 ref 设置变量值
    eval "$ref=\"new_value\""
    echo $var_name    # 输出: new_value
    
  • 解析包含引号或空格的结构化数据(需格外小心):

    # 假设我们从某处得到了一串需要被正确解析为多个参数的字符串
    args='arg1 "arg 2 with space" arg3'
    # 直接 echo 会把整个字符串当作一个参数
    echo $args        # 输出: arg1 "arg 2 with space" arg3
    # 使用 eval 可以让 Shell 正确解析其中的引号
    eval "my_command $args"
    # 等效于执行: my_command arg1 "arg 2 with space" arg3
    
  • eval 和命令替换 $(...) 的功能区别

特性 eval 命令替换 $(...)...
核心作用 将字符串作为 Shell 命令在当前进程中执行 执行一个命令,并将其标准输出作为字符串值返回/替换
工作阶段 在 Shell 解析并执行命令的阶段工作。 在 Shell 解析命令行、进行参数扩展的阶段工作。
返回值 返回其内部执行的最后一条命令的退出状态码 返回被捕获的命令的标准输出内容(字符串)
类比 像是 “编译器”“解释器”,它接收一段“代码”(字符串),然后编译并运行它。 像是 “函数调用”,它执行一个过程,然后把执行结果的“返回值”(文本)给你。
常见用途 动态构造并执行复杂的命令;从变量或文件中读取并执行 Shell 代码。 将命令的输出结果赋值给变量,或嵌套在另一个命令的参数中。

builtin:强制调用内建命令

builtin 的作用是 强制调用指定名称的 Shell 内建命令,即使该名称被函数或别名覆盖了

  • 基本语法builtin 内建命令名 [参数...]

主要用途与示例:

  • 在自定义函数中调用被覆盖的内建命令

    cd() {
        echo "准备切换到: $1";
        builtin cd "$1" || { echo "切换失败"; return 1; } # 这里调用的是真正的 cd 内建命令
        echo "现在位于: $(pwd)";
        ls;
    }
    
  • builtincommand 的区别

    • command 主要用来调用外部命令查询命令信息
    • builtin 专门且只能用于调用内建命令
    • 对于既是内建命令又有外部实现的命令(如 echotest/[),command 默认优先调用外部命令,而 builtin 强制调用内建版本。

type:命令的“身份鉴定师”

type 用来确定一个命令到底是什么(别名、关键字、函数、内建命令、磁盘文件,或未找到)。

  • 基本语法type [-aftpP] 名称...
  • 常用选项
    • -t:只输出一个词表示类型(alias, keyword, function, builtin, file, '')。
    • -a:显示所有包含该名称的定义位置(例如,echo 可能同时是内建命令和 /bin/echo)。
    • -p:仅当名称为外部命令时,才显示其路径(否则无输出)。

主要用途与示例:

type ls           # 输出: ls 是 `ls --color=auto' 的别名
type -t ls        # 输出: alias
type -a echo      # 输出: echo 是 shell 内建命令 和 echo 是 /usr/bin/echo
type -p grep      # 输出: /usr/bin/grep
type nonexistent  # 输出: bash: type: nonexistent: 未找到

exec:进程的“夺舍”替换

exec 的核心作用是 用指定的命令替换当前 Shell 进程。执行后,当前进程的 PID 不变,但程序完全被新命令替换,原 Shell 进程结束。

  • 基本语法exec [-cl] [-a name] [命令 [参数...]] [重定向...]
  • 常用选项
    • -l:在新进程启动前,在 argv[0] 前加一个 -(类似登录 Shell)。
    • -a:显式设置新进程的 argv[0](命令名)。

主要用途与示例:

  • 永久替换当前 Shell(常用于登录脚本或容器入口点):

    # 如果用户 shell 是 /bin/false,将其替换为 /bin/bash
    if [ "$SHELL" = "/bin/false" ]; then
        exec /bin/bash
    fi
    
  • 仅用于重定向(不替换命令时的经典用法):

    # 将当前 Shell 的所有后续输出重定向到文件
    exec > logfile.txt 2>&1
    echo "这行会写入 logfile.txt"
    # 打开一个文件作为文件描述符 3 用于读取
    exec 3< input.txt
    read -u 3 line
    
  • 在脚本末尾启动守护进程

    # 完成所有初始化后,启动主程序并替换当前脚本进程
    daemon --config=/etc/daemon.conf
    exec my_main_program
    

⑥ 核心风险与最佳实践

命令 主要风险 最佳实践
eval 极高:如果参数来自不可信来源(如用户输入、网络),会导致任意命令执行漏洞。 1. 永远优先考虑不使用 eval 的替代方案(如数组 "${array[@]}")。 2. 如果必须使用,确保参数内容完全可控、可信任。 3. 对输入进行严格的验证和清理。
exec :命令执行后,当前进程(包括脚本)立即结束,没有回头路。 1. 只在脚本逻辑终点或明确需要永久替换进程时使用。 2. 用于重定向时,注意其对后续所有命令的全局影响。
command / builtin :主要风险在于脚本的可移植性(并非所有 Shell 都支持所有选项)。 在需要屏蔽自定义行为或确保调用特定版本时使用。对于可移植脚本,使用 command 更安全。

Linux预置user/group

nobody dialout都很这些都是 Linux 系统安全和权限模型的核心概念。这些特殊的用户账户不是为了给人登录用的,而是为了给系统进程资源分配最小必要权限,这极大地增强了系统的安全性。下面将详细解释 dialoutnobody 以及其他常见的特殊用户。

① dialout 用户/用户组 (Group)

  • 它是什么? dialout 主要是一个用户组,而不是一个用户。

  • 用途是什么? 在早期,这个组用于控制对拨号调制解调器(Dial-out Modem)的访问权限。调制解调器在 Linux 中被表示为串行设备(如 /dev/ttyS0, /dev/ttyUSB0)。

  • 现代用途: 虽然拨号上网已不常见,但这个组的用途保留了下来。现在,它主要用于控制对串行端口的访问。

    • 例如: 当你插入一个 USB 转串口适配器(用于连接 Arduino、树莓派、路由器 Console 口等),设备文件通常是 /dev/ttyUSB0/dev/ttyACM0。默认情况下,只有 root 用户和 dialout 组的成员有读写权限。
  • 如何让你的用户使用串口? 就是将你的普通用户加入 dialout 组:

    sudo usermod -a -G dialout $USER
    

    然后注销再重新登录,让你的组权限生效。之后你就可以不用 sudo 直接运行 screen /dev/ttyUSB0 115200 这样的命令了。


② nobody用户

  • 它是什么? nobody 是一个预设的系统用户,它的 UID(用户ID)和 GID(组ID)通常是 65534 或 99,具体取决于发行版。它的唯一目的是代表最低可能的权限
  • 用途是什么? “最小权限原则”的完美体现
    • 许多网络服务(如 web 服务器 nginx/apache)在启动时以 root 权限运行(因为需要绑定 80/443 等特权端口),但一旦启动完成,它们会立即将自身进程的权限降级nobody 用户运行。
    • 为什么要这样做? 如果黑客利用了这个服务的漏洞进行攻击,他们获得的权限也仅仅是 nobody 用户的权限,而不是强大的 root 权限。这极大地限制了破坏范围,黑客很难篡改系统关键文件或安装恶意软件。
    • 它也用于运行一些不需要任何特殊权限的脚本或任务。

③ 需要这些特殊用户的核心思想

核心是最小权限原则权限分离

  1. 安全隔离: 每个系统服务都应该用自己的专用用户运行。如果两个服务(如 web 服务器和数据库)都用同一个用户运行,那么一个服务的漏洞会危及另一个服务的数据。为每个服务创建单独的用户可以有效地将它们“关在自己的笼子里”。
  2. 降低风险: 就像 nobody 的例子,以尽可能低的权限运行进程,即使被攻破,损失也最小。
  3. 资源所有权: 文件、设备、端口等资源都有所有者和权限。通过为用户组(如 dialout, audio, video)分配权限,可以精细地控制哪些用户或进程能访问哪些资源。

⑤ 预置用户的注册机制

A. “中央注册机构”:事实标准与规范

虽然没有一个像 ICANN 管理域名那样的全球强制性机构,但 Linux 世界通过以下方式实现了事实上的统筹:

  • Linux Standard Base (LSB): 这是一个旨在增加不同 Linux 发行版之间兼容性的规范。LSB 对核心系统用户和组UID/GID 编号名称做出了规定。例如,它规定了 root 必须是 0,nobody 通常是 65534 或 99,daemon 是 1 或 2 等。
  • 发行版维护者: 各大主流发行版(Debian, Red Hat, Ubuntu, SUSE等)的维护者们会协同工作,遵循 LSB 和其他历史惯例。他们在打包软件时,会确保同一个服务在不同发行版上尽可能使用相同的用户名和 UID。
  • 软件作者的建议: 当一个软件(如 Apache, MySQL)被打包时,其官方文档通常会建议创建一个特定的用户(如 www-data, mysql)来运行它。发行版的软件包维护者会遵循这个建议,在安装软件包时自动创建相应的系统用户。

所以,这个“统筹组织”是一个由规范、主流发行版维护者和软件生态共同构成的松散但有效的联盟。

B. UID 的分配规则:约定优于配置
  • UID 范围约定:
    • 0: 保留给 root,神圣不可侵犯。
    • 1-999 (系统用户): 这个范围约定俗成地分配给由系统包管理器(apt, yum, dnf)安装的软件所创建的用户。这些用户通常用于运行守护进程和服务。普通用户绝对不应该手动创建这个范围内的用户。
    • 1000-65533 (登录用户): 这个范围分配给通过 useradd 等命令创建的普通人类用户。第一个创建的普通用户通常是 UID 1000。
    • 65534: 通常保留给 nobody/nogroup
  • “任何人都可以分配吗?”
    • 技术上:是的。 只有 root 有权限创建用户,而 root 可以强行指定任何 UID(比如 useradd -u 100 someuser)。
    • 实践上:绝对不要这样做! 这是一个灾难性的做法。原因如下:
      1. 冲突风险: 如果你手动创建了一个 UID 为 100 的用户,而下一个通过包管理器安装的软件也期望创建一个 UID 为 100 的系统用户,安装就会失败,或者导致权限混乱。
      2. 可移植性与协作灾难: 你的系统将变得“异类”。脚本、工具、乃至其他管理员都会默认系统用户遵循标准约定。打破约定会使系统难以维护和理解。
      3. 文件权限错乱: 如果之前有一个系统文件属于 UID 100,你创建的新用户恰好也用了 UID 100,那么这个新用户就会意外地拥有那些系统文件的权限,造成严重的安全风险。
C. 这些特殊用户创建和使用

这分为两种情况:

  1. 预创建的: 绝大多数常用的系统用户和组(如 root, daemon, bin, sys, nobody, www-data, sudo, dialout, audio 等)在系统安装之初就已经由你的 Linux 发行版创建好了。它们存在于 /etc/passwd/etc/group 文件中,可以直接使用(例如,直接将你的用户加入 dialout 组)。
  2. 按需创建的: 当你使用包管理器安装一个软件时,安装脚本(post-install script)会自动检查并创建该软件所需的特定用户和组。
    • 例如,当你 apt install mysql-server 时,APT 包管理器的安装流程中包含了创建 mysql 用户和用户组的步骤。
    • 这些步骤是软件包定义的一部分。对于系统管理员来说,这个过程是自动的、“无需创建”的。

⑥. 其他常见的特殊系统用户

Linux 系统中有大量这样的用户,通常它们的 UID 小于 1000(在大多数发行版中)。你可以查看 /etc/passwd 文件来找到它们。

用户名 主要用途
root 超级管理员,UID 0,拥有系统完全控制权。不要用于日常登录!
daemon 用于运行系统守护进程。
bin 历史上用于管理二进制程序文件,现在较少使用。
sys 类似 daemon,用于系统进程和文件。
www-data Web 服务器(如 Apache, Nginx)常用的用户,用于运行服务器进程和访问 web 文件。
mysql / postgres 专门用于运行 MySQLPostgreSQL 数据库服务。
mail 用于处理邮件相关的进程(如 SMTP, POP3 服务)。
uuidd 用于生成 UUIDs 的小守护进程的用户。
ssh 用于管理 SSH 相关的进程。
ntp 用于运行网络时间协议 (NTP) 守护进程,同步系统时间。
cups 用于管理 打印服务 (Common UNIX Printing System)。
redis / memcached 用于运行 Redis / Memcached 缓存数据库服务。
systemd-\* 各种与 systemd init 系统相关的用户,用于隔离不同的系统服务。
dbus 用于运行 D-Bus 消息总线系统,允许进程间通信。
rpc 用于运行 RPC(远程过程调用)服务。

⑦ 如何查看它们?

  • 查看所有用户: cat /etc/passwd
  • 查看所有用户组: cat /etc/group
  • 查看当前登录的用户: whow
  • 查看某个文件的所有者: ls -l filename。第三列是用户,第四列是用户组。

总结

  • dialout 是一个用户组,用于授予对串行端口(如 USB 转串口设备)的访问权限。
  • nobody 是一个系统用户,代表最低权限,用于以安全的方式运行服务。
  • Linux 创建大量特殊系统用户是为了遵循最小权限原则,实现服务间的安全隔离,从而构建一个更稳定、更安全的系统环境。这些用户是 Linux 系统管理不可或缺的一部分。
  • LSB 和发行版联盟就像是 IANA,负责分配知名的端口号(如 80 给 HTTP,22 给 SSH)。你不会擅自把自家的网页服务端口改成 22。
  • UID 0-999 就像是 系统端口号(0-1023),只有特权进程才能使用,并且每个端口号都有约定俗成的服务。
  • 手动分配一个 UID < 1000 就像是 强行把你的私人应用绑在 80 端口上,这会让真正的 Web 服务器无法工作,造成整个系统的混乱和不稳定。
  • 绝对尊重 0-999 的 UID/GID 范围,将其视为由发行版包管理器管理的保留地。
  • 创建普通用户时,永远不要使用 -u 选项指定一个小于 1000 的 UID,交给系统自动分配。
  • 使用系统用户时,直接使用发行版预创建好的那些(如 nobody, www-data),或者通过包管理器安装软件来自动创建所需的用户(如 mysql, redis)。
  • 如果需要为某个自研服务创建专门的系统用户,请使用 useradd -r -s /bin/false service_name 命令。-r 选项会自动在 1-999 的范围内选择一个可用的系统用户UID,这是唯一安全的手动创建系统用户的方式。

Bash 的大括号扩展{}

大括号扩展是 Bash 中非常高效的功能,用于生成多个字符串或路径组合。以下是常见用法:

① 枚举列表

echo file_{1,2,3}.txt
# 输出:file_1.txt file_2.txt file_3.txt

② 连续数字或字母序列

echo {1..5}       # 输出:1 2 3 4 5
echo {a..d}       # 输出:a b c d
echo {01..10}     # 输出:01 02 03 ... 10(自动补零)

③ 路径组合

cp /path/to/{old,new}/file.txt
# 展开为:cp /path/to/old/file.txt /path/to/new/file.txt

④ 嵌套组合

echo {A,B}_{1,2}
# 输出:A_1 A_2 B_1 B_2

⑤ 与其他通配符(如 \*)结合

ls /dev/{null,zero,tty*}  
# 先展开大括号,再匹配通配符

通用工具

以下是工具用法:

① find

# 对找到的文件,逐个执行exec命名. "\;"控制逐个; +则是将find的输出作为整体
# {} 是一个占位符,表示当前匹配到的文件名
# ';'前"\"目的是转义,否则被认为是shell命令结束符。
# "*.txt" 则是防止shell将*.txt展开,否则相当于‘find . -name 1.txt -exec ls -l {} \;‘ 
# 如果目录有1.txt, 没有.txt文件就是‘’空!
find . -name "*.txt" -exec ls -l {} \;
find . -name "*.txt" -exec ls -l {} ';' #也是一样

#找到所有 .c 文件,先打印文件名,再检查是否包含 main 函数
find . -name "*.c" -exec echo "File:" {} \; -exec grep -l "main" {} \;

#-exec 之间是**“与”关系**(类似 -a),即所有条件都满足才会执行。
#如果需要“或”逻辑,可以用 -o 连接
find . \( -name "*.txt" -exec cmd1 {} \; \) -o \( -name "*.log" -exec cmd2 {} \; \)

# 对找到的文件,整体作为exec的参数. “+“是将find的输出作为整体
find . -name "*.txt" -exec ls -l {} +

② xargs

#管道传递:前一个命令(如 find、grep)的输出通过管道|传给 xargs
#批量处理,效率高(减少命令调用次数)
find . -name "*.txt" | xargs ls -l #相当find . -name "*.txt" -exec ls -l {} +

#空格/特殊字符:文件名含空格时需用 -print0 和 xargs -0:
find . -name "*.txt" -print0 | xargs -0 ls -l

#命令参数位置:若参数需放在中间,用 -I{}
find . -name "*.txt" | xargs -I{} mv {} /backup

③ ldconfig

配置运行时库的搜索路径。一般 Linux 系统把 /lib/usr/lib 两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到 库的搜索路径之中。设置库文件的搜索路径有下列两种方式,可任选其一使用:

  • 在环境变量 LD_LIBRARY_PATH 中指明库的搜索路径
  • 在 /etc/ld.so.conf 文件中添加库的搜索路径

第二种搜索路径的设置方式对于程序连接时的库(包括共享库和静态库)的定位已经足够了,但是对于使用了共享库的程序的执行还是不够的。这是因为为了加快程序执行时对共享库的定位速度,避免使用搜索路径查找共享库的低效率,动态连接程序ld直接读取库列表文件/etc/ld.so.cache ,从中进行搜索的。/etc/ld.so.cache 是一个非文本的数据文件,不能直接编辑,需要运行ldconfig, 它是根据 /etc/ld.so.conf更新/etc/ld.so.cache
ldconfig它的作用就是将/etc/ld.so.conf列出的路径下的库文件缓存到/etc/ld.so.cache 以供使用。因此当安装完一些库文件,(例如刚安装好glib),或者修改/etc/ld.so.conf增加新的库路径后,需要运行一下/sbin/ldconfig使所有的库文件都被缓存到ld.so.cache中。如果没做,即使库文件明明就在/usr/lib下的,也是不会被使用的,结果编译过程中抱错,缺少xxx库,去查看发现明明就在那放着!

④ diff

Linux/Unix 系统中用于比较文件或目录差异的核心工具,它能逐行对比文本内容,并输出修改、添加或删除的行.

  • 基本语法
diff [选项] 文件1 文件2       # 比较两个文件
diff [选项] 目录1 目录2     # 比较两个目录
  • 常用选项
选项 说明
-u 输出统一格式(Unified Mode),更易读(常用于补丁文件)
-c 输出上下文格式(Context Mode)
-i 忽略大小写差异
-w 忽略所有空格(包括制表符和空格符)
-b 忽略空格数量的变化(如多个空格视为一个)
-r 递归比较目录中的文件
-q 仅显示文件是否不同(不显示具体差异)
-N 将不存在的文件视为空文件(用于目录比较)
-y 并排显示差异(需终端宽度足够)
--color 彩色输出差异(需终端支持)
  • 输出格式详解

    1>. 默认格式

    • a(add):文件1的某行需添加到文件2才能匹配。
    • d(delete):文件1的某行需删除才能匹配文件2。
    • c(change):文件1的某行需修改才能匹配文件2。

    示例

    3d2                      # 文件1的第3行需删除(d)以匹配文件2的第2行
    < 原内容                 # 文件1中被删除的行
    5a5                      # 文件1的第5行后需添加(a)以匹配文件2的第5行
    > 新内容                 # 文件2中新增的行
    

    2>. 统一格式(-u

    更易读的格式,广泛用于生成补丁文件(.patch):

    --- 文件1 时间戳           # 原始文件
    +++ 文件2 时间戳           # 修改后的文件
    @@ -行号1,行数1 +行号2,行数2 @@
    - 删除的行
    + 添加的行
    共有的行(上下文)
    

    示例

    @@ -1,5 +1,6 @@
     1: Line 1
    -2: Line 2
    +2: Modified Line 2
     3: Line 3
    +4: Added Line
     4: Line 4
     5: Line 5
    

    逐段解释

    1. @@ -1,5 +1,6 @@
      • -1,5:旧文件从第1行开始,共显示5行(包括修改行和上下文)。
      • +1,6:新文件从第1行开始,共显示6行(因为新增了1行,总行数增加)。
    2. 差异内容
      • -2: Line 2:旧文件的第2行被删除(用 - 标记)。
      • +2: Modified Line 2:新文件的第2行被添加(用 + 标记)。
      • +4: Added Line:新文件新增了第4行(原第4行 Line 4 被挤到第5行)。

    格式说明:@@ -行号1,行数1 +行号2,行数2 @@

    • -行号1,行数1:表示**原始文件(旧文件)**的变更位置。
      • 行号1:差异开始的起始行号。
      • 行数1:显示的上下文行数(包括修改行和周围未改动的行)。
    • +行号2,行数2:表示**修改后文件(新文件)**的变更位置。
      • 行号2:差异开始的起始行号。
      • 行数2:显示的上下文行数。

⑤ cut

cut 的选项主要分为两类:按字段提取按字符提取,以下是最常用的选项:

按字段提取(适用于有分隔符的文本)

字段指的是文本中由「分隔符」分隔的部分(如 CSV 文件中的逗号、/etc/passwd 中的冒号)。

选项 作用
-d <分隔符> 指定字段的分隔符(必须是单个字符,默认分隔符是制表符 \t)。
-f <字段范围> 指定要提取的字段(字段编号从 1 开始),支持多种格式(见示例)。
-s 只显示包含分隔符的行(忽略没有分隔符的行,避免无意义的整行输出)。
--output-delimiter <字符> 指定输出时的字段分隔符(默认与输入分隔符一致)。

-f 字段范围的格式

  • 单个字段:-f1(提取第 1 个字段)。
  • 连续字段:-f1-3(提取第 1 到第 3 个字段,包含首尾)。
  • 不连续字段:-f1,3(提取第 1 和第 3 个字段)。
  • 从某字段到结尾:-f2-(提取第 2 个字段及之后所有字段)。

示例
/etc/passwd 文件为例(该文件用冒号 : 分隔用户信息,格式为 用户名:密码占位符:UID:GID:注释:家目录:shell)。

  • 提取所有用户名(第 1 个字段):

    cut -d: -f1 /etc/passwd
    # 输出:root、bin、daemon...(系统所有用户名)
    
  • 提取用户名和 UID(第 1 和第 3 个字段):

    cut -d: -f1,3 /etc/passwd
    # 输出:root:0、bin:1、daemon:2...
    
  • 提取第 2 到最后一个字段,并用逗号作为输出分隔符:

    cut -d: -f2- --output-delimiter=, /etc/passwd
    # 输出:x,0,root,/root,/bin/bash(原冒号被替换为逗号)
    
  • 只显示包含冒号的行(忽略无意义的行):

    # 假设文件中有一行无冒号:"this is a test"
    cut -d: -f1 -s file.txt  # 这行不会被输出
    

按字符提取(适用于无分隔符的文本)

如果文本没有明确分隔符,但需要按「字符位置」提取(如固定长度的日志),可用以下选项:

选项 作用
-c <字符范围> 按字符位置提取(字符编号从 1 开始),支持与 -f 类似的范围格式。
--complement 反向提取:不提取指定的字段 / 字符,而是保留其他部分(配合 -f-c 使用)。

-c 字符范围的格式

  • 单个字符:-c5(提取第 5 个字符)。
  • 连续字符:-c1-5(提取第 1 到第 5 个字符)。
  • 从某字符到结尾:-c3-(提取第 3 个字符及之后所有字符)。

示例
处理一行文本 hello world 123(无分隔符,按字符位置提取)。

  • 提取前 5 个字符:

    echo "hello world 123" | cut -c1-5  # 输出:hello
    
  • 提取第 7 到第 11 个字符:

    echo "hello world 123" | cut -c7-11  # 输出:world
    
  • 反向提取(排除前 5 个字符,保留剩余部分):

    echo "hello world 123" | cut -c1-5 --complement  # 输出: world 123
    

注意事项

  1. 分隔符限制-d 只能指定单个字符作为分隔符,无法处理多个字符(如 --)或正则表达式(如空格 + 制表符)。如果需要复杂分隔符,建议用 awk
  2. 连续分隔符处理cut 会将连续的分隔符视为多个分隔符(例如连续两个逗号 ,, 会被当作两个分隔符,中间视为空字段)。如需将连续分隔符视为一个,可先用 tr -s 压缩(如 tr -s ',' < file.csv | cut -d, -f1)。
  3. 兼容性cut 是 POSIX 标准工具,所有 Linux/Unix 系统都自带,无需额外安装,适合在脚本中使用。

⑥ od和hexdump

$hexdump config -C
00000000  86 80 51 9b 06 00 90 20  00 00 00 06 00 00 00 00  |..Q.... ........|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 62 14 97 12  |............b...|
00000030  00 00 00 00 e0 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040

⑦ Screen

GNU Screen 是一个终端多路复用器。简单来说,它就像一个“容器”,可以在一个单一的物理终端窗口(或SSH连接)中,创建和管理多个虚拟终端

它的核心功能是保持会话。即使你的网络连接断开(SSH 会话结束),运行在 screen 里面的进程也会继续在后台执行。之后你重新连接,可以随时“附着”回这个会话,无缝继续之前的工作,仿佛从未断开过。

A. Screen(使用场景

  1. 会话持久化 (最重要的功能)
    • 场景:你通过 SSH 连接到一台远程服务器,正在执行一个需要运行很久的任务(比如编译大型软件、数据备份、传输大文件)。突然网络波动,SSH 断开,你的任务也被中断了,一切前功尽弃。
    • Screen 的解决方案:在 SSH 连接后,立即创建一个 screen 会话,然后在这个会话里运行你的长任务。即使 SSH 断开,任务也会在 screen 中继续运行。你下次重新 SSH 连接后,恢复这个 screen 会话,就能看到任务已经完成或仍在运行的输出。
  2. 多窗口工作
    • 场景:你需要在服务器上同时做多项工作,比如一个窗口看日志 (tail -f),一个窗口编辑配置文件 (vim),一个窗口执行命令。
    • Screen 的解决方案:在一个终端窗口内,创建多个“虚拟窗口”并在它们之间快速切换,无需打开多个 SSH 连接或终端标签页。
  3. 协作共享
    • 场景:你需要和同事一起调试服务器上的一个问题,让他看到你的实时操作。
    • Screen 的解决方案:你可以创建一个会话,然后让你的同事从他的终端“附着”到你的同一个会话中。你们双方可以看到完全一样的屏幕内容,并且都可以输入命令。这非常适合远程结对调试。
  4. 作为简单的串口终端
    • 场景:如之前所述,连接 Arduino、路由器等串行设备。
    • Screen 的解决方案:直接使用 screen /dev/ttyUSB0 115200 命令即可。

B. 核心概念:快捷键

screen 的所有强大功能都通过一个命令前缀快捷键来触发。默认的前缀是 Ctrl + a(写作 C-a)。

所有操作流程都是:

  1. 按下 Ctrl + a(屏幕左下角会短暂显示)
  2. 松开所有键
  3. 再按一个命令键(如 c 创建新窗口)

C. Screen常用命令

1. 创建与管理会话
  • 创建新会话screen -S session_name
    • 例如 screen -S my_work。给会话起一个有意义的名字 (my_work) 非常重要,便于后续恢复。
  • 列出所有会话screen -ls
    • 输出示例:There is a screen on: 28827.my_work (Attached)
  • 恢复/附着到最新会话screen -r
  • 恢复指定会话screen -r session_namescreen -r 28827(使用进程ID)
  • 强制恢复(踢掉已附着者):如果会话已经是“Attached”状态,可能是异常断开,使用 screen -D -r session_name-D 先强制断开另一端,再 -r 恢复。
  • 彻底结束一个会话:在会话内部,输入 exit 或按 C-a \
2. 窗口管理 (在一个会话内操作)
  • 创建新窗口C-a c (Create)
  • 切换到下一个窗口C-a n (Next)
  • 切换到上一个窗口C-a p (Previous)
  • 切换到第 0-9 号窗口C-a 0C-a 9
  • 列出所有窗口C-a " (双引号)。会显示一个列表,可以用方向键选择。
  • 关闭当前窗口:在窗口的 shell 提示符下输入 exit,或者按 C-a k (Kill)。它会询问你是否真的要 kill。
  • 重命名当前窗口C-a A (大写A,即 Shift+a),然后输入新名字。
3. 区域分割 (Split Screen)
  • 水平分割当前区域C-a S (大写S)
  • 垂直分割当前区域C-a | (竖线,需要 Shift + \)
  • 在分割区域间切换焦点C-a Tab
  • 关闭当前焦点所在的区域C-a X (大写X)
  • 关闭所有区域分割(只保留当前焦点区域)C-a Q (大写Q)
4. 滚动和复制模式
  • 进入复制/滚动模式C-a [C-a Esc
    • 进入后,可以用方向键、PageUp/PageDown 滚动查看历史输出。
    • Space 键开始选择文本起点,移动光标选择终点,再按 SpaceEnter 复制所选文本。
  • 粘贴内容C-a ]
5. 会话共享 (多用户)
  1. 用户A(分享者)在服务器上创建多用户会话:screen -S shared_session -x
    • (-x 参数允许多人同时附着)
  2. 用户A告知会话名 shared_session 给用户B。
  3. 用户B(参与者)在自己的终端输入:screen -x username/shared_session
    • 现在双方可以看到并控制同一个会话。

D. 工作流示例:编译一个大型软件

  1. 通过 SSH 连接到服务器

    ssh user@remote-server
    
  2. 创建一个命名的 screen 会话

    screen -S compile_nginx
    
  3. 在 screen 会话中开始工作

    tar -zxvf nginx-1.xx.x.tar.gz
    cd nginx-1.xx.x
    ./configure --with-http_ssl_module
    make -j4 # 开始长时间编译
    
  4. 网络突然断开!

    • 编译进程仍在 screen 会话中安全地运行。
  5. 重新连接 SSH

    ssh user@remote-server
    
  6. 列出并恢复之前的会话

    screen -ls
    # There is a screen on:
    #     12345.compile_nginx (Detached)
    screen -r compile_nginx
    
  7. 你回来了!

    • 你会看到 make 命令可能已经完成,或者还在继续输出。就像你从未离开过一样。

E. 进阶技巧与配置

  • 配置文件 (~/.screenrc): 可以高度自定义 screen 的行为,比如修改前缀键、设置状态栏、设置启动窗口等。

    • 示例:设置一个信息丰富的状态栏(将以下内容添加到 ~/.screenrc):

      caption always "%{= kw}%-w%{= BW}%n %t%{-}%+w %=%d %M %Y %0c %{G}| %{B}%l%{G}"
      
  • 修改前缀键:如果你觉得 Ctrl-a 和 Vim 中跳到行首冲突,可以在配置文件中修改,比如改为 Ctrl-b

    escape ^Bb
    

总结

场景 核心命令
开始新工作 screen -S <name>
暂时离开 C-a d (Detach)
重新连接 screen -r <name>
查看有哪些工作 screen -ls
一个窗口干多件事 C-a c (创建新窗口), C-a n/p (切换)
分屏 C-a S (水平分), `C-a ` (垂直分)
查看历史输出 C-a [
结束工作 exitC-a \

screen 是系统管理员和开发者的必备神器,它极大地提高了在远程服务器上工作的可靠性和效率。虽然现在有更现代化的替代品(如 tmux),但 screen 几乎预装在所有 Linux 发行版中,简单可靠,值得花时间掌握。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐