#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h> // 包含 clone 标志和 unshare
#include <sys/syscall.h> // 包含 syscall 和系统调用号
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h> // 包含 waitpid
#include <fcntl.h> // 包含 open, O_RDONLY 等
#include <errno.h>
#include <string.h>
#include <sys/mount.h> // 包含 mount
#include <linux/nsfs.h> // 包含 ioctl_ns 的常量 (NS_GET_nstype 等)
#include <sys/ioctl.h> // 包含 ioctl// 定义子进程栈大小
#define STACK_SIZE (1024 * 1024) // 1MB// 子进程1:由 clone 创建,拥有自己的 Mount 和 UTS 命名空间// 然后它会调用 unshare 来获得独立的 Network 命名空间
int container_init(void *arg) {
printf("\n--- Inside Container Init Process (PID: %d) ---\n", getpid());
// 1. 更改容器内的主机名 (在 UTS 命名空间内)// 这不会影响宿主机的主机名
if (sethostname("my-container", strlen("my-container")) == -1) {
perror("sethostname (in container)");
} else {
printf("Container: Set hostname to 'my-container'.\n");
}
// 2. 创建一个挂载点并挂载 tmpfs (在 Mount 命名空间内)// 这个挂载在宿主机上不可见
const char* mount_point = "/tmp/container_root";
if (mkdir(mount_point, 0755) == -1 && errno != EEXIST) {
perror("mkdir (in container)");
return 1;
}
if (mount("tmpfs", mount_point, "tmpfs", 0, NULL) == -1) {
perror("mount tmpfs (in container)");
return 1;
}
printf("Container: Mounted tmpfs on %s\n", mount_point);
// 3. 在挂载点内创建一个文件
char file_path[256];
snprintf(file_path, sizeof(file_path), "%s/container_file.txt", mount_point);
FILE *f = fopen(file_path, "w");
if (f) {
fprintf(f, "This file exists only inside the container's mount namespace.\n");
fclose(f);
printf("Container: Created file %s\n", file_path);
} else {
perror("fopen (in container)");
}
// 4. 使用 unshare 脱离当前的 Network 命名空间,进入一个新的空的 Network 命名空间// 这使得容器拥有完全隔离的网络视图
printf("Container: Calling unshare(CLONE_NEWNET) to get isolated network...\n");
if (unshare(CLONE_NEWNET) == -1) {
perror("unshare CLONE_NEWNET");
umount(mount_point); // 清理
return 1;
}
printf("Container: Successfully unshared network namespace.\n");
printf("Container: Network is now isolated. 'ip link' should show only loopback.\n");
// 5. 容器主循环:等待信号或执行任务// 这里我们简单地睡眠,以便我们可以从外部观察
printf("Container: Sleeping for 60 seconds. Explore from host and container.\n");
printf("Container: From host terminal, run:\n");
printf(" - 'ls /tmp/container_root' (should NOT see the file)\n");
printf(" - 'sudo nsenter -t %d -n ip link' (should see only loopback)\n", getpid());
printf("Container: From another terminal (as root), run this program's second part:\n");
printf(" - './namespace_demo join %d'\n", getpid());
sleep(60); // 睡眠 60 秒// 6. 清理 (退出时内核通常会自动清理命名空间和挂载)
printf("Container: Cleaning up and exiting.\n");
umount(mount_point);
rmdir(mount_point);
return 0;
}
// 辅助函数:打开并返回指定进程的指定类型命名空间的文件描述符
int open_namespace_fd(pid_t pid, const char* ns_type) {
char path[256];
snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, ns_type);
int fd = open(path, O_RDONLY);
if (fd == -1) {
perror("open_namespace_fd");
fprintf(stderr, "Failed to open %s namespace for PID %d\n", ns_type, pid);
}
return fd;
}
// 辅助函数:使用 ioctl_ns 获取命名空间类型
void query_namespace_type(int ns_fd) {
// NS_GET_NSTYPE 是一个 ioctl 命令,用于获取命名空间类型
int ns_type = ioctl(ns_fd, NS_GET_NSTYPE);
if (ns_type == -1) {
perror("ioctl NS_GET_NSTYPE");
return;
}
const char* type_str;
switch (ns_type) {
case CLONE_NEWNS: type_str = "Mount (CLONE_NEWNS)"; break;
case CLONE_NEWCGROUP: type_str = "Cgroup (CLONE_NEWCGROUP)"; break;
case CLONE_NEWUTS: type_str = "UTS (CLONE_NEWUTS)"; break;
case CLONE_NEWIPC: type_str = "IPC (CLONE_NEWIPC)"; break;
case CLONE_NEWUSER: type_str = "User (CLONE_NEWUSER)"; break;
case CLONE_NEWPID: type_str = "PID (CLONE_NEWPID)"; break;
case CLONE_NEWNET: type_str = "Network (CLONE_NEWNET)"; break;
default: type_str = "Unknown";
}
printf(" Namespace fd %d type is: %s\n", ns_fd, type_str);
}
// 主函数:演示创建和加入命名空间
int main(int argc, char *argv[]) {
// --- 场景 1: 加入已存在的命名空间 ---
if (argc == 3 && strcmp(argv[1], "join") == 0) {
pid_t target_pid = atoi(argv[2]);
if (target_pid <= 0) {
fprintf(stderr, "Invalid PID provided.\n");
exit(EXIT_FAILURE);
}
printf("--- Joining Existing Namespace (as separate process) ---\n");
printf("This process (PID: %d) will join the mount namespace of PID: %d\n", getpid(), target_pid);
// 1. 打开目标进程的 Mount 命名空间文件描述符
int target_mnt_ns_fd = open_namespace_fd(target_pid, "mnt");
if (target_mnt_ns_fd == -1) exit(EXIT_FAILURE);
// 2. 查询并打印命名空间类型 (使用 ioctl)
printf("Querying namespace type using ioctl...\n");
query_namespace_type(target_mnt_ns_fd);
// 3. 使用 setns 加入目标的 Mount 命名空间
printf("Calling setns() to join the mount namespace...\n");
if (syscall(SYS_setns, target_mnt_ns_fd, CLONE_NEWNS) == -1) {
perror("setns");
close(target_mnt_ns_fd);
exit(EXIT_FAILURE);
}
printf("Successfully joined the mount namespace of PID %d.\n", target_pid);
close(target_mnt_ns_fd);
// 4. 现在,当前进程的文件系统视图与目标进程相同
printf("Checking for file that exists in the target namespace...\n");
if (access("/tmp/container_root/container_file.txt", F_OK) == 0) {
printf("SUCCESS: Found '/tmp/container_root/container_file.txt'. We are inside the container's mount namespace!\n");
} else {
printf("FAIL: Could not find the file. setns might have failed or file doesn't exist.\n");
}
printf("Exiting join process.\n");
exit(EXIT_SUCCESS);
}
// --- 场景 2: 创建新的隔离进程 (容器) ---
printf("--- Creating Isolated Process (Container) ---\n");
printf("Main process PID: %d\n", getpid());
// 1. 分配子进程栈
char *child_stack = malloc(STACK_SIZE);
if (!child_stack) {
perror("malloc");
exit(EXIT_FAILURE);
}
// 2. 使用 clone 创建子进程,并让它在新的 Mount 和 UTS 命名空间中启动// SIGCHLD: 子进程退出时发送 SIGCHLD 信号给父进程
pid_t container_pid = syscall(SYS_clone,
CLONE_NEWNS | CLONE_NEWUTS | SIGCHLD,
child_stack + STACK_SIZE, // 栈是向下增长的
NULL, NULL);
if (container_pid == -1) {
perror("clone");
free(child_stack);
exit(EXIT_FAILURE);
}
if (container_pid == 0) {
// --- 在子进程中 ---
free(child_stack); // 子进程不需要父进程的栈指针
container_init(NULL); // 运行容器初始化函数
exit(EXIT_SUCCESS);
}
// --- 回到父进程 ---
printf("\n--- Back in Parent Process ---\n");
printf("Parent: Created container process with PID: %d\n", container_pid);
// 等待几秒,让子进程完成初始化 (挂载等)
sleep(3);
// 3. 父进程演示:获取子进程的命名空间文件描述符并查询信息
printf("\n--- Parent: Inspecting Container's Namespaces ---\n");
int child_mnt_ns_fd = open_namespace_fd(container_pid, "mnt");
int child_net_ns_fd = open_namespace_fd(container_pid, "net");
if (child_mnt_ns_fd != -1) {
printf("Parent: Opened child's Mount namespace fd: %d\n", child_mnt_ns_fd);
query_namespace_type(child_mnt_ns_fd);
// 注意:此时父进程还未加入,所以访问 /tmp/container_root 应该失败
if (access("/tmp/container_root/container_file.txt", F_OK) != 0) {
printf("Parent: As expected, '/tmp/container_root/container_file.txt' is NOT accessible from parent namespace.\n");
}
// close(child_mnt_ns_fd); // 暂时不关闭,后面 setns 还要用
}
if (child_net_ns_fd != -1) {
printf("Parent: Opened child's Network namespace fd: %d\n", child_net_ns_fd);
query_namespace_type(child_net_ns_fd);
close(child_net_ns_fd);
}
// 4. 父进程演示:使用 setns 加入子进程的 Mount 命名空间// (注意:实际应用中,你可能不会在父进程中这样做,这里仅为演示)/*
printf("\n--- Parent: Attempting to join child's Mount Namespace ---\n");
if (child_mnt_ns_fd != -1) {
if (syscall(SYS_setns, child_mnt_ns_fd, CLONE_NEWNS) == 0) {
printf("Parent: Successfully joined child's mount namespace.\n");
// 现在父进程可以访问容器内的文件了
if (access("/tmp/container_root/container_file.txt", F_OK) == 0) {
printf("Parent: Now I can access '/tmp/container_root/container_file.txt'.\n");
}
} else {
perror("Parent: setns failed");
}
close(child_mnt_ns_fd);
}
*/// 5. 等待子进程结束
printf("\n--- Parent: Waiting for container process (PID: %d) to finish ---\n", container_pid);
printf("Parent: The container will exit automatically after its sleep.\n");
printf("Parent: Or, you can manually stop it with 'kill %d'\n", container_pid);
int status;
waitpid(container_pid, &status, 0);
if (WIFEXITED(status)) {
printf("Parent: Container process exited with status %d.\n", WEXITSTATUS(status));
} else {
printf("Parent: Container process did not exit normally.\n");
}
free(child_stack);
printf("Parent: Main process finished.\n");
return 0;
}
如何编译和运行
# 1. 保存代码为 namespace_demo.c# 2. 编译 (需要 root 权限来运行,但编译不需要)
gcc -o namespace_demo namespace_demo.c
# 3. 打开两个终端 (Terminal 1 和 Terminal 2)# --- Terminal 1: 创建容器 ---# 运行程序的第一部分,创建一个隔离的容器进程
sudo ./namespace_demo
# 你会看到类似输出:# --- Creating Isolated Process (Container) ---# Main process PID: 12345# Parent: Created container process with PID: 12346## --- Inside Container Init Process (PID: 12346) ---# Container: Set hostname to 'my-container'.# Container: Mounted tmpfs on /tmp/container_root# Container: Created file /tmp/container_root/container_file.txt# Container: Calling unshare(CLONE_NEWNET) to get isolated network...# Container: Successfully unshared network namespace.# Container: Network is now isolated. 'ip link' should show only loopback.# Container: Sleeping for 60 seconds. Explore from host and container.# ...# --- Terminal 2: 加入容器 ---# 在程序输出中找到容器的 PID (例如 12346),然后运行程序的第二部分# 加入容器的 Mount 命名空间
sudo ./namespace_demo join 12346
# 你会看到类似输出:# --- Joining Existing Namespace (as separate process) ---# This process (PID: 12347) will join the mount namespace of PID: 12346# Querying namespace type using ioctl...# Namespace fd 3 type is: Mount (CLONE_NEWNS)# Calling setns() to join the mount namespace...# Successfully joined the mount namespace of PID 12346.# Checking for file that exists in the target namespace...# SUCCESS: Found '/tmp/container_root/container_file.txt'. We are inside the container's mount namespace!# Exiting join process.
详细说明
场景 1: 使用 clone 创建隔离进程
main 函数:作为父进程启动。
malloc(STACK_SIZE):为子进程分配独立的栈空间。
syscall(SYS_clone, ...):调用 clone 系统调用。
CLONE_NEWNS:告诉内核为新进程创建一个新的 Mount 命名空间。
CLONE_NEWUTS:告诉内核为新进程创建一个新的 UTS 命名空间(用于隔离主机名)。
child_stack + STACK_SIZE:传递子进程栈的顶部指针(栈向下增长)。
if (container_pid == 0):在 clone 返回后,执行流分叉。在子进程中,clone 返回 0。
container_init:子进程执行此函数,它现在运行在一个隔离的 Mount 和 UTS 命名空间中。它设置了主机名、挂载了文件系统并创建了文件。然后它调用 unshare。
Linux 命名空间 (Namespaces) 是 Linux 内核的一个强大特性,它提供了隔离机制。通过命名空间,可以将一组进程及其资源(如网络接口、挂载点、进程 ID 等)与系统上的其他进程隔离开来,仿佛它们运行在独立的系统中一样。这是实现 容器 (Containers) 技术(如 Docker, LXC)的核心基础之一。
Linux 支持多种类型的命名空间,每种隔离不同类型的系统资源:
Mount (mnt): 隔离文件系统挂载点。
PID (pid): 隔离进程 ID 空间。
Network (net): 隔离网络设备、IP 地址、端口等。
Interprocess Communication (ipc): 隔离 System V IPC 和 POSIX 消息队列。
--- Demonstrating setns with Mount Namespace ---
Main process PID: 12345
Opened parent's mount namespace fd: 3
Created child process with new mount namespace. PID: 12346
Child process (PID: 12346) started.
Child process: Mounted tmpfs on /tmp/my_test_mount
Child process: Created file /tmp/my_test_mount/test_file.txt
Child process: Sleeping for 30 seconds. Check /tmp/my_test_mount from parent and child.
Opened child's mount namespace fd: 4
Parent process: /tmp/my_test_mount does NOT exist in parent namespace (as expected).
--- Calling setns to join child's mount namespace ---
Parent process successfully joined child's mount namespace.
Parent process (after setns): /tmp/my_test_mount NOW EXISTS in current namespace.
Parent process (after setns): You can now see the file created by the child.
Parent process (after setns): Read from file: Hello from child process in its own mount namespace!
--- Cleaning up ---
Child process exited with status 0.
Main process finished.
总结: setns 是一个强大的系统调用,是 Linux 容器技术的基石之一。它允许进程动态地加入到已存在的命名空间中,从而获得该命名空间的视图和资源访问权限。对于 Linux 编程新手来说,理解命名空间的概念是第一步,setns 则是实现命名空间操作的关键工具。直接使用它进行编程比较复杂,通常在容器运行时(如 runc)或高级系统管理脚本中会用到。日常开发中,使用 nsenter 或容器管理工具(如 docker exec)是更常见的与命名空间交互的方式。
# 假设原始主机名是 'old-hostname'
$ sudo ./sethostname_example NewTempName
--- Demonstrating sethostname ---
[Initial] Current hostname is: 'old-hostname'
[uname] System name: Linux
[uname] Node name (hostname): old-hostname
[uname] Release: 5.4.0-XX-generic
[uname] Version: #XX-Ubuntu SMP ...
[uname] Machine: x86_64
Attempting to change hostname to: 'NewTempName'
sethostname('NewTempName') succeeded.
[After sethostname] Current hostname is: 'NewTempName'
[uname after change] Node name (hostname): NewTempName
--- Important Notes ---
1. The hostname change is TEMPORARY and only lasts until the system is rebooted.
2. To make the change persistent, you need to update configuration files like /etc/hostname.
3. You need ROOT privileges to call sethostname.
# 验证命令
$ hostname
NewTempName
$ uname -n
NewTempName
--- Demonstrating setfsuid and setfsgid ---
Initial IDs:
Real UID: 1000
Effective UID: 1000
Saved UID: 1000
Real GID: 1000
Effective GID: 1000
Saved GID: 1000
(By default, FS-UID and FS-GID equal Effective UID/GID)
--- Testing setfsuid ---
Current FS-UID (queried): 1000
Attempting to set FS-UID to: 999
setfsuid(999) returned: 1000 (should be the old FS-UID: 1000)
Verifying: setfsuid(999) again returned: 999
-> FS-UID was successfully set to 999.
--- Testing setfsgid ---
Current FS-GID (queried): 1000
Attempting to set FS-GID to: 999
setfsgid(999) returned: 1000 (should be the old FS-GID: 1000)
Verifying: setfsgid(999) again returned: 999
-> FS-GID was successfully set to 999.
--- Attempting file operation to observe behavior ---
Created test file 'test_fsuid_file.txt'.
Successfully opened 'test_fsuid_file.txt' for reading (as expected).
Deleted test file 'test_fsuid_file.txt'.
--- Important Notes ---
1. setfsuid/setfsgid primarily affect filesystem permission checks.
2. They do not change effective UID/GID for other operations.
3. Their main use is in system daemons like NFS servers.
4. The return value is the OLD fsid, success is verified by calling again.
--- Demonstrating setfsuid and setfsgid ---
Initial IDs:
Real UID: 1000
Effective UID: 1000
Saved UID: 1000
Real GID: 1000
Effective GID: 1000
Saved GID: 1000
(By default, FS-UID and FS-GID equal Effective UID/GID)
--- Testing setfsuid ---
Current FS-UID (queried): 1000
Attempting to set FS-UID to: 999
setfsuid(999) returned: 1000 (should be the old FS-UID: 1000)
Verifying: setfsuid(999) again returned: 999
-> FS-UID was successfully set to 999.
--- Testing setfsgid ---
Current FS-GID (queried): 1000
Attempting to set FS-GID to: 999
setfsgid(999) returned: 1000 (should be the old FS-GID: 1000)
Verifying: setfsgid(999) again returned: 999
-> FS-GID was successfully set to 999.
--- Attempting file operation to observe behavior ---
Created test file 'test_fsuid_file.txt'.
Successfully opened 'test_fsuid_file.txt' for reading (as expected).
Deleted test file 'test_fsuid_file.txt'.
--- Important Notes ---
1. setfsuid/setfsgid primarily affect filesystem permission checks.
2. They do not change effective UID/GID for other operations.
3. Their main use is in system daemons like NFS servers.
4. The return value is the OLD fsid, success is verified by calling again.
总结: 对于 Linux 编程新手,setfsuid 和 setfsgid 是比较特殊的系统调用。它们不是日常编程中会频繁使用的。理解它们有助于理解 Linux 权限模型的细节,特别是文件系统权限检查是如何与进程的其他权限分离的。在编写需要代表不同用户执行文件操作的系统服务(如文件服务器)时,它们会很有用。在常规应用程序开发中,使用标准的 setuid/seteuid 或现代的 capabilities 通常更合适。
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <sys/prctl.h>
#include <unistd.h>
int prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5);
int seccomp(unsigned int operation, unsigned int flags, void *args);
#include <linux/kcmp.h>
#include <sys/syscall.h>
#include <unistd.h>
int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2);
// kexec_file_load (推荐的现代接口)
long kexec_file_load(int kernel_fd, int initrd_fd,
unsigned long cmdline_len,
const char *cmdline_ptr,
unsigned long flags);
# 格式1:直接指定脚本
sed [选项] '脚本' 文件名...
# 格式2:从文件读取脚本
sed [选项] -f 脚本文件 文件名...
# 格式3:管道使用
命令 | sed [选项] '脚本'
# 格式4:标准输入
sed [选项] '脚本'
2. 常用选项详解(基于 sed –help)
选项
长选项
说明
-n
--quiet, --silent
抑制默认输出(只输出明确指定的内容)
-e
--expression=脚本
添加脚本到命令中(可多次使用)
-f
--file=脚本文件
从文件读取脚本
-i
--in-place[=后缀]
原地编辑文件(提供后缀则备份原文件)
-r
--regexp-extended
使用扩展正则表达式(同-E)
-E
--regexp-extended
使用扩展正则表达式(推荐使用)
-s
--separate
将多个文件分别处理,而非作为连续流
-u
--unbuffered
减少I/O缓冲,及时刷新输出
-z
--null-data
使用NUL字符作为行分隔符
-b
--binary
二进制模式(Windows兼容)
--posix
禁用所有GNU扩展,使用POSIX兼容模式
--sandbox
沙盒模式(禁用e/r/w命令)
--follow-symlinks
原地编辑时跟随符号链接
-c
--copy
使用复制而非重命名方式处理文件(-i模式)
-l
--line-length=N
指定’l’命令的行包装长度
三、基本命令详解
1. 替换命令 (s)
# 基本替换语法:s/原字符串/新字符串/标志
sed 's/old/new/' file.txt # 只替换每行第一次出现
sed 's/old/new/g' file.txt # 全局替换(所有出现)
sed 's/old/new/i' file.txt # 忽略大小写替换
sed 's/old/new/p' file.txt # 替换并打印匹配行
sed 's/old/new/gp' file.txt # 全局替换并打印匹配行
sed 's/old/new/2' file.txt # 替换每行第2次出现
sed 's/old/new/gw output.txt' file.txt # 替换结果写入指定文件# 使用不同分隔符(避免转义斜杠)
sed 's|/old/path|/new/path|' file.txt
sed 's_old_new_' file.txt
sed 's#http://#https://#' file.txt
2. 删除命令 (d)
# 删除指定行
sed '3d' file.txt # 删除第3行
sed '3,5d' file.txt # 删除第3到第5行
sed '3,$d' file.txt # 删除第3行到文件末尾
sed '1~2d' file.txt # 删除奇数行
sed '2~2d' file.txt # 删除偶数行# 删除匹配模式的行
sed '/pattern/d' file.txt # 删除包含pattern的行
sed '/^$/d' file.txt # 删除空行
sed '/^[[:space:]]*$/d' file.txt # 删除空白行
sed '/^#/d' file.txt # 删除注释行
3. 打印命令 §
# 需要配合-n选项使用
sed -n '3p' file.txt # 打印第3行
sed -n '3,5p' file.txt # 打印第3到第5行
sed -n '/pattern/p' file.txt # 打印包含pattern的行
sed -n '1~2p' file.txt # 打印奇数行
sed -n '2~2p' file.txt # 打印偶数行
4. 插入命令 (i, a, c)
# 插入命令
sed '3i\新行内容' file.txt # 在第3行前插入
sed '/pattern/i\新行内容' file.txt # 在匹配行前插入# 追加命令
sed '3a\新行内容' file.txt # 在第3行后追加
sed '/pattern/a\新行内容' file.txt # 在匹配行后追加# 替换命令
sed '3c\新行内容' file.txt # 替换第3行
sed '/pattern/c\新行内容' file.txt # 替换匹配行
5. 其他常用命令
# 行号显示 (=)
sed '=' file.txt # 显示所有行号
sed '/pattern/=' file.txt # 显示匹配行的行号# 列出行内容 (l)
sed -n 'l' file.txt # 显示所有行(转义特殊字符)
sed -n '/pattern/l' file.txt # 显示匹配行(转义特殊字符)# 读取文件 (r)
sed '/pattern/r insert.txt' file.txt # 在匹配行后读入文件内容# 写入文件 (w)
sed -n '/pattern/w output.txt' file.txt # 将匹配行写入文件# 退出命令 (q)
sed '10q' file.txt # 处理10行后退出
sed '/pattern/q' file.txt # 匹配到pattern后退出
四、地址范围
1. 行号地址
# 单行地址
sed '5命令' file.txt # 处理第5行# 行号范围
sed '5,10命令' file.txt # 处理第5到第10行
sed '5,$命令' file.txt # 处理第5行到文件末尾
sed '1,3!命令' file.txt # 处理除第1到第3行外的所有行# 步进地址
sed '1~2命令' file.txt # 处理奇数行(1,3,5...)
sed '2~2命令' file.txt # 处理偶数行(2,4,6...)
2. 模式地址
# 单模式地址
sed '/pattern/命令' file.txt # 处理包含pattern的行# 模式范围
sed '/start/,/end/命令' file.txt # 处理从包含start到包含end的行
sed '/pattern/,+3命令' file.txt # 处理包含pattern的行及其后3行
sed '/pattern/,$命令' file.txt # 处理包含pattern的行到文件末尾# 模式与行号组合
sed '/pattern/,10命令' file.txt # 处理包含pattern的行到第10行
3. 地址否定
# 否定地址(处理不匹配的行)
sed '3!命令' file.txt # 处理除第3行外的所有行
sed '/pattern/!命令' file.txt # 处理不包含pattern的行
五、正则表达式详解
1. 基本正则表达式(默认)
# 字符匹配
sed 's/a/b/' file.txt # 匹配字符a
sed 's/./b/' file.txt # 匹配任意字符
sed 's/[abc]/X/' file.txt # 匹配a、b或c
sed 's/[^abc]/X/' file.txt # 匹配除a、b、c外的字符# 重复匹配
sed 's/a*/X/' file.txt # 匹配0个或多个a
sed 's/a\+/X/' file.txt # 匹配1个或多个a(需要转义)
sed 's/a\{2,4\}/X/' file.txt # 匹配2到4个a(需要转义)# 位置匹配
sed 's/^/X/' file.txt # 匹配行首
sed 's/$/X/' file.txt # 匹配行尾
sed 's/\<word\>/X/' file.txt # 匹配单词边界
2. 扩展正则表达式(-E选项)
# 使用-E选项启用扩展正则表达式
sed -E 's/a+/X/' file.txt # 匹配1个或多个a
sed -E 's/a{2,4}/X/' file.txt # 匹配2到4个a
sed -E 's/(group)+/X/' file.txt # 分组匹配
sed -E 's/pattern1|pattern2/X/' file.txt # OR操作
sed -E 's/(..)(..)/\2\1/' file.txt # 捕获组交换
3. 捕获组和引用
# 基本正则中的捕获组
sed 's/\(pattern\)/[\1]/' file.txt # 使用\1引用第一个捕获组
sed 's/\(first\)\(second\)/\2\1/' file.txt # 交换两个捕获组# 扩展正则中的捕获组
sed -E 's/(pattern)/[\1]/' file.txt # 使用\1引用第一个捕获组
sed -E 's/(first)(second)/\2\1/' file.txt # 交换两个捕获组
六、实用示例
1. 文本替换
# 简单替换
sed 's/old/new/' file.txt
# 全局替换
sed 's/old/new/g' file.txt
# 忽略大小写替换
sed 's/old/new/gi' file.txt
# 使用捕获组
sed 's/\(pattern\)/[\1]/' file.txt
# 删除行首空白
sed 's/^[ \t]*//' file.txt
# 删除行尾空白
sed 's/[ \t]*$//' file.txt
# 替换多个空格为单个空格
sed 's/ */ /g' file.txt
# 删除所有空白字符
sed 's/[[:space:]]//g' file.txt
2. 文件处理
# 原地编辑文件
sed -i 's/old/new/g' file.txt
# 原地编辑并备份
sed -i.bak 's/old/new/g' file.txt
# 处理多个文件
sed 's/old/new/g' file1.txt file2.txt
# 从标准输入处理
cat file.txt | sed 's/old/new/g'
3. 复杂操作
# 多命令组合(使用分号)
sed 's/old/new/g; s/abc/xyz/g' file.txt
# 多命令组合(使用-e选项)
sed -e 's/old/new/g' -e 's/abc/xyz/g' file.txt
# 使用脚本文件
echo -e 's/old/new/g\ns/abc/xyz/g' > script.sed
sed -f script.sed file.txt
# 条件处理
sed '/pattern/{s/old/new/;p}' file.txt
# 多行处理
sed '/start/,/end/{s/old/new/g}' file.txt
4. 实际应用场景
# 配置文件处理
sed 's/#.*$//' file.conf | sed '/^$/d' # 删除注释和空行# 日志处理
sed -n '/ERROR/p' application.log # 提取错误日志
sed '/DEBUG/d' application.log # 过滤调试日志# CSV处理
sed 's/,/|/g' data.csv # 替换分隔符# 格式转换
sed 's/\t/ /g' file.txt # Tab转空格
七、高级功能
1. 保持空间操作
# h - 将模式空间复制到保持空间# H - 将模式空间追加到保持空间# g - 将保持空间复制到模式空间# G - 将保持空间追加到模式空间# x - 交换模式空间和保持空间# 示例:在文件末尾添加文件头
sed '1h; 1d; $G' file.txt
# 示例:反转文件行顺序
sed -n '1!G; h; $p' file.txt
2. 分支和测试命令
# b - 分支到标签(无标签则跳到脚本末尾)# t - 测试(如果上次替换成功则分支)# T - 测试(如果上次替换失败则分支)# 示例:替换成功后退出
sed '/pattern/{s/old/new/; t; d}' file.txt
3. 原地编辑高级用法
# 创建带时间戳的备份
sed -i.$(date +%Y%m%d) 's/old/new/g' file.txt
# 处理符号链接
sed --follow-symlinks -i 's/old/new/g' symlink.txt
# 使用复制模式
sed -c -i 's/old/new/g' file.txt
八、常见问题解决
1. 特殊字符处理
# 处理包含斜杠的路径
sed 's|/old/path|/new/path|' file.txt
sed 's#/old/path#/new/path#' file.txt
# 处理特殊字符
sed 's/\*/STAR/g' file.txt # 替换星号
sed 's/\$/DOLLAR/g' file.txt # 替换美元符号
sed 's/\\/BACKSLASH/g' file.txt # 替换反斜杠
2. 性能优化
# 对大文件只处理前N行
sed '1000q; s/old/new/g' largefile.txt
# 使用地址范围限制处理范围
sed '100,200s/old/new/g' file.txt
# 避免不必要的全局替换
sed 's/old/new/' file.txt # 只替换第一次出现
3. 跨平台兼容性
# 使用POSIX模式
sed --posix 's/old/new/g' file.txt
# Windows兼容模式
sed -b 's/old/new/g' file.txt
# 使用扩展正则表达式的可移植性
sed -E 's/pattern1|pattern2/replacement/' file.txt # GNU/Linux
sed -r 's/pattern1|pattern2/replacement/' file.txt # 旧版本GNU sed
九、快速参考表
基本命令速查
s/old/new/ 替换(第一次出现)
s/old/new/g 全局替换
d 删除行
p 打印行(需-n选项)
i\text 在前插入
a\text 在后追加
c\text 替换行
= 显示行号
l 列出行内容
q 退出
# 删除空行和注释行
sed '/^$/d; /^#/d' file.txt
# 提取文件头和尾各5行
sed -n '1,5p; $-4,$p' file.txt
# 给文件添加行号
sed '=' file.txt | sed 'N; s/\n/\t/'
# 合并连续的空行为单个空行
sed '/^$/N; /\n$/D' file.txt
# 在每个匹配行后添加标记
sed '/pattern/a\--- MATCH ---' file.txt
# functions.awk
function square(x) { return x * x }
function cube(x) { return x * x * x }
# main.awk
@include "functions.awk"
BEGIN {
print square(5) # 25
print cube(3) # 27
}
setregid系统调用详解及示例,掌握Linux进程权限管理,了解函数功能与使用方法,提升系统编程技能。setregid 是Linux系统调用,用于同时设置进程的真实组ID(real group ID)和有效组ID(effective group ID)。它是组ID管理的重要函数,允许进程在不同组权限之间切换,实现灵活的权限控制。
2. 函数原型
#include <unistd.h>
int setregid(gid_t rgid, gid_t egid);
#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
void print_current_uids(const char* context) {
uid_t ruid, euid, suid;
printf("[%s] Current UIDs - Real: %d, Effective: %d, Saved: %d\n",
context, getuid(), geteuid(), (getresuid(&ruid, &euid, &suid) == 0) ? suid : -1);
}
int main() {
uid_t original_ruid, original_euid, original_suid;
uid_t target_uid;
int result;
printf("--- Demonstrating setuid ---\n");
// 1. 获取并打印初始 UID
if (getresuid(&original_ruid, &original_euid, &original_suid) != 0) {
perror("getresuid");
exit(EXIT_FAILURE);
}
print_current_uids("Start");
// 2. 检查是否以 root 权限运行
if (geteuid() == 0) {
printf("\nRunning as ROOT (Privileged User)\n");
// 作为 root,可以设置为任意有效的 UID// 这里我们尝试设置为 'nobody' 用户 (通常 UID 65534, 但请检查你的系统)
target_uid = 65534;
printf("Attempting to set UID to %d (usually 'nobody' user)...\n", target_uid);
print_current_uids("Before setuid");
result = setuid(target_uid);
if (result == 0) {
printf("setuid(%d) succeeded.\n", target_uid);
print_current_uids("After setuid");
printf("Note: All UIDs (Real, Effective, Saved) are now %d.\n", target_uid);
printf("The process is now running with 'nobody' privileges.\n");
} else {
perror("setuid");
printf("Failed to set UID to %d.\n", target_uid);
}
} else {
printf("\nRunning as a REGULAR USER (UID: %d)\n", getuid());
// 作为普通用户,只能设置为自己的 ruid 或 suid
target_uid = original_ruid; // 选择设置为自己的真实 UID (这不会改变任何东西)
printf("Attempting to set UID to my Real UID (%d)...\n", target_uid);
print_current_uids("Before setuid");
result = setuid(target_uid);
if (result == 0) {
printf("setuid(%d) succeeded (as expected).\n", target_uid);
print_current_uids("After setuid");
} else {
perror("setuid");
}
// 尝试设置为一个无效的 UID (比如一个不存在的或不属于我的 UID)// 这通常会失败
target_uid = 9999; // 假设这是一个无效的或不属于当前用户的 UID
printf("\nAttempting to set UID to an invalid/different UID (%d)...\n", target_uid);
result = setuid(target_uid);
if (result == -1) {
if (errno == EPERM) {
printf("setuid(%d) failed with EPERM (Operation not permitted) - as expected for a regular user.\n", target_uid);
printf("This is because %d is not my Real UID (%d) or Saved Set-UID (%d).\n",
target_uid, original_ruid, original_suid);
} else {
perror("setuid");
}
print_current_uids("After failed setuid");
} else {
printf("setuid(%d) unexpectedly succeeded.\n", target_uid);
print_current_uids("After unexpected setuid");
}
}
printf("\n--- Summary ---\n");
printf("The setuid() function changes the Effective UID of the process.\n");
printf("For root: It can change to any UID, and also changes Real and Saved UID.\n");
printf("For regular users: It can only change Effective UID to Real or Saved UID.\n");
printf("This is crucial for security, especially in programs like setuid binaries.\n");
return 0;
}
--- Demonstrating setuid ---
[Start] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000
Running as a REGULAR USER (UID: 1000)
Attempting to set UID to my Real UID (1000)...
[Before setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000
setuid(1000) succeeded (as expected).
[After setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000
Attempting to set UID to an invalid/different UID (9999)...
setuid(9999) failed with EPERM (Operation not permitted) - as expected for a regular user.
This is because 9999 is not my Real UID (1000) or Saved Set-UID (1000).
[After failed setuid] Current UIDs - Real: 1000, Effective: 1000, Saved: 1000
--- Summary ---
The setuid() function changes the Effective UID of the process.
For root: It can change to any UID, and also changes Real and Saved UID.
For regular users: It can only change Effective UID to Real or Saved UID.
This is crucial for security, especially in programs like setuid binaries.
12. 预期输出 (使用 sudo 以 root 权限运行)
--- Demonstrating setuid ---
[Start] Current UIDs - Real: 0, Effective: 0, Saved: 0
Running as ROOT (Privileged User)
Attempting to set UID to 65534 (usually 'nobody' user)...
[Before setuid] Current UIDs - Real: 0, Effective: 0, Saved: 0
setuid(65534) succeeded.
[After setuid] Current UIDs - Real: 65534, Effective: 65534, Saved: 65534
Note: All UIDs (Real, Effective, Saved) are now 65534.
The process is now running with 'nobody' privileges.
struct signalfd_siginfo si;
ssize_t s = read(sfd, &si, sizeof(si));
if (s != sizeof(si)) {
// Handle error
}
// Now you can access signal information in 'si'
printf("Got signal %d from PID %d\n", si.ssi_signo, si.ssi_pid);
--- Demonstrating signalfd ---
PID: 12345
Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT
Created signalfd: 3
Try sending signals:
kill -USR1 12345
kill -USR2 12345
Or press Ctrl+C (SIGINT) or Ctrl+\ (SIGQUIT)
Type 'exit' and press Enter to quit.
This program uses poll() to wait for signals or input.
Read from stdin: hello
Read from stdin: world
Received signal: 10 (SIGUSR1)
Sender PID: 12346
Sender UID: 1000
Received signal: 12 (SIGUSR2)
Sender PID: 12346
Sender UID: 1000
Received signal: 2 (SIGINT - Ctrl+C)
Sender PID: 0
Sender UID: 0
Exiting due to signal.
Closing signalfd...
Program finished.
struct signalfd_siginfo si;
ssize_t s = read(sfd, &si, sizeof(si));
if (s != sizeof(si)) {
// Handle error
}
// Now you can access signal information in 'si'
printf("Got signal %d from PID %d\n", si.ssi_signo, si.ssi_pid);
--- Demonstrating signalfd ---
PID: 12345
Blocked signals: SIGUSR1, SIGUSR2, SIGINT, SIGQUIT
Created signalfd: 3
Try sending signals:
kill -USR1 12345
kill -USR2 12345
Or press Ctrl+C (SIGINT) or Ctrl+\ (SIGQUIT)
Type 'exit' and press Enter to quit.
This program uses poll() to wait for signals or input.
Read from stdin: hello
Read from stdin: world
Received signal: 10 (SIGUSR1)
Sender PID: 12346
Sender UID: 1000
Received signal: 12 (SIGUSR2)
Sender PID: 12346
Sender UID: 1000
Received signal: 2 (SIGINT - Ctrl+C)
Sender PID: 0
Sender UID: 0
Exiting due to signal.
Closing signalfd...
Program finished.
--- Demonstrating statfs ---
Filesystem information for '/':
Optimal I/O Block Size: 4096 bytes
Total Data Blocks: 123456789
Free Blocks: 56789012
Available Blocks: 50000000 (for non-root users)
Total Inodes: 30000000
Free Inodes: 25000000
Filesystem ID: 12345:67890
Maximum Filename Length: 255
Fragment Size: 4096
Mount Flags: 0x400
Total Size: 471.86 GiB
Available Size: 190.73 GiB
Filesystem Type: ext2/ext3/ext4
Filesystem information for '/tmp':
Optimal I/O Block Size: 4096 bytes
Total Data Blocks: 2048000
Free Blocks: 2048000
Available Blocks: 2048000 (for non-root users)
Total Inodes: 512000
Free Inodes: 512000
Filesystem ID: 0:1234567
Maximum Filename Length: 255
Fragment Size: 4096
Mount Flags: 0x41c
Total Size: 7.81 GiB
Available Size: 7.81 GiB
Filesystem Type: tmpfs
Filesystem information for '/proc':
...
Filesystem Type: proc
...
Filesystem information for '/sys':
...
Filesystem Type: sysfs
...
--- Checking Disk Space ---
Checking if '/tmp' has at least 100.00 MiB available...
OK: 8000.00 MiB available, 100.00 MiB required.
--- Demonstrating symlink and symlinkat ---
Created original file: original_file.txt
Created hard link: hard_link_to_original.txt -> original_file.txt
Created symbolic link (absolute target): symlink_absolute_to_original.txt -> /full/path/to/somewhere
Created symbolic link (relative target): symlink_relative_to_original.txt -> original_file.txt
Created dangling symbolic link: symlink_to_nonexistent.txt -> this_file_does_not_exist.txt
Created directory: test_symlink_dir
Created symbolic link using symlinkat: test_symlink_dir/symlink_in_dir.txt -> ../original_file.txt
--- File Information ---
--- Original File: original_file.txt ---
Inode: 123456
Links: 2
Size: 42 bytes
Type: Regular File
--- Hard Link: hard_link_to_original.txt ---
Inode: 123456
Links: 2
Size: 42 bytes
Type: Regular File
--- Symbolic Link (Absolute): symlink_absolute_to_original.txt ---
Inode: 123457
Links: 1
Size: 23 bytes
Type: Symbolic Link
-> Points to: /full/path/to/somewhere
--- Symbolic Link (Relative): symlink_relative_to_original.txt ---
Inode: 123458
Links: 1
Size: 18 bytes
Type: Symbolic Link
-> Points to: original_file.txt
--- Dangling Symbolic Link: symlink_to_nonexistent.txt ---
Inode: 123459
Links: 1
Size: 27 bytes
Type: Symbolic Link
-> Points to: this_file_does_not_exist.txt
--- Symbolic Link (Created with symlinkat): test_symlink_dir/symlink_in_dir.txt ---
Inode: 123460
Links: 1
Size: 21 bytes
Type: Symbolic Link
-> Points to: ../original_file.txt
--- Accessing Files ---
Can access original file 'original_file.txt'.
Can access hard link 'hard_link_to_original.txt'.
Can access symbolic link 'symlink_relative_to_original.txt' (follows to existing target).
Cannot access dangling symbolic link 'symlink_to_nonexistent.txt': No such file or directory (expected)
--- Cleaning Up ---
All files and directory cleaned up.
--- Summary ---
1. Hard links (link) point to the same inode as the original file.
2. Symbolic links (symlink) are separate files containing a path string.
3. Symbolic links can point to non-existent targets (dangling).
4. symlinkat allows creating symlinks relative to a directory file descriptor.
--- Demonstrating sync_file_range ---
Created and opened file: sync_test_file.dat
Writing 10485760 bytes (10.00 MiB) in 1048576 byte chunks...
Written up to 2097152 bytes. Syncing range [0, 2097152)...
-> sync_file_range for 2097152 bytes took 5.23 ms
Written up to 4194304 bytes. Syncing range [2097152, 4194304)...
-> sync_file_range for 2097152 bytes took 3.12 ms
Written up to 6291456 bytes. Syncing range [4194304, 6291456)...
-> sync_file_range for 2097152 bytes took 2.87 ms
Written up to 8388608 bytes. Syncing range [6291456, 8388608)...
-> sync_file_range for 2097152 bytes took 3.01 ms
Finished writing file.
--- Syncing final part of the file ---
Syncing final range [8388608, EOF) using sync_file_range...
-> sync_file_range for final 2097152 bytes took 2.95 ms
--- Comparing with fsync (syncs entire file) ---
Calling fsync() to sync the entire file...
-> fsync() took 12.34 ms
--- Verification ---
File 'sync_test_file.dat' created successfully.
Final file size: 10485760 bytes (10.00 MiB)
--- Summary ---
1. sync_file_range allows synchronizing a specific byte range of a file.
2. It can be more efficient than fsync for large files where only parts need syncing.
3. Flags control whether to start writeback (WRITE) and/or wait for completion (WAIT_*).
4. It provides fine-grained control for performance-critical applications like databases.
--- Demonstrating sync and syncfs ---
PID: 12345
1. Creating and writing test files...
Opened /tmp/sync_test_file1.txt
Opened /home/user/sync_test_file2.txt
Data written to files. It's likely in the page cache now.
2. --- Demonstrating syncfs ---
Calling syncfs() on the filesystem containing /tmp/sync_test_file1.txt...
syncfs(fd1) completed in 15.23 ms.
This synced only the filesystem containing /tmp/sync_test_file1.txt.
Calling syncfs() on the filesystem containing /home/user/sync_test_file2.txt...
syncfs(fd2) completed in 8.45 ms.
This synced only the filesystem containing /home/user/sync_test_file2.txt.
3. --- Demonstrating sync ---
Calling sync() to flush ALL filesystems...
sync() completed in 45.67 ms.
This synced data for ALL mounted filesystems on the system.
4. --- Cleaning up ---
Deleted /tmp/sync_test_file1.txt
Deleted /home/user/sync_test_file2.txt
--- Summary ---
1. sync(): Flushes dirty data for ALL mounted filesystems. No return value.
2. syncfs(fd): Flushes dirty数据 for ONLY the filesystem containing the file referred to by 'fd'. Returns 0 on success.
3. syncfs is more targeted and usually faster than sync.
4. Use sync() before system shutdown. Use syncfs() or fsync() for specific data safety in applications.
因此,子进程中打印但未刷新的 “Child is about to terminate. ” 不会出现在输出中。
在父进程中:
打印消息。
调用 exit(EXIT_SUCCESS)。
exit 会刷新父进程的 stdio 缓冲区。
因此,父进程中打印但未刷新的 “Parent process (PID: …) printing without newline. Buffer content: ” 会因为 exit 的刷新操作而被打印出来。
然后打印 “Parent continues …” 和 “Parent process … finished.”。
运行结果:
Parent process (PID: 12345) printing without newline. Buffer content: This is child process (PID: 12346).
Child is about to terminate. Child calling _exit(EXIT_SUCCESS).
Parent continues after fork.
Parent process (PID: 12345) finished.