Rust进程控制库proc-exit的使用:安全终止程序与自定义退出码管理

Rust进程控制库proc-exit的使用:安全终止程序与自定义退出码管理

特性

  • i32 新类型用于退出码
    • 可以表示任何有效的退出码
    • 类型安全,操作仅限于退出码的有效范围
  • 包含标准退出码和信号退出码
  • 可与 mainstd::processstd::io::Error 集成
  • 支持静默退出(通过其他方式报告错误消息)

安装

添加到你的 Cargo.toml:

proc-exit = "2.0.2"

使用示例

基本用法

use proc_exit::prelude::*;

fn main() {
    if some_condition() {
        // 使用标准退出码
        proc_exit::exit(proc_exit::Code::USAGE);
    }
    
    // 使用自定义退出码
    proc_exit::exit(42);
}

与main集成

use proc_exit::prelude::*;

fn main() -> proc_exit::ExitResult {
    // 你的程序逻辑
    if some_error_condition() {
        // 返回错误退出码
        return proc_exit::Code::DATA_ERR.to_result();
    }
    
    // 成功返回
    proc_exit::Code::SUCCESS.to_result()
}

与std::process和std::io::Error集成

use proc_exit::prelude::*;
use std::fs::File;
use std::io;

fn open_file() -> proc_exit::ExitResult {
    File::open("not_exist.txt")
        .map_err(|e| proc_exit::Code::NO_INPUT.with_message(e))?;
    Ok(())
}

fn main() -> proc_exit::ExitResult {
    open_file()?;
    Ok(())
}

完整示例

use proc_exit::prelude::*;

fn main() -> proc_exit::ExitResult {
    let args: Vec<String> = std::env::args().collect();
    
    if args.len() < 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        return proc_exit::Code::USAGE.to_result();
    }
    
    let filename = &args[1];
    let content = std::fs::read_to_string(filename)
        .map_err(|e| proc_exit::Code::NO_INPUT.with_message(e))?;
        
    if content.is_empty() {
        eprintln!("Error: File is empty");
        return proc_exit::Code::DATA_ERR.to_result();
    }
    
    println!("File content: {}", content);
    
    // 成功退出
    proc_exit::Code::SUCCESS.to_result()
}

完整示例demo

下面是一个更完整的文件处理程序示例,展示了proc-exit库的各种用法:

use proc_exit::prelude::*;
use std::fs;
use std::path::Path;

// 检查文件是否存在且可读
fn check_file(filename: &str) -> proc_exit::ExitResult {
    if !Path::new(filename).exists() {
        eprintln!("Error: File '{}' does not exist", filename);
        return proc_exit::Code::NO_INPUT.to_result();
    }

    // 检查文件权限
    match fs::metadata(filename) {
        Ok(metadata) => {
            if metadata.permissions().readonly() {
                eprintln!("Error: File '{}' is read-only", filename);
                return proc_exit::Code::NO_PERM.to_result();
            }
        }
        Err(e) => {
            return proc_exit::Code::OS_ERR
                .with_message(format!("Failed to access file metadata: {}", e))
                .to_result();
        }
    }

    Ok(())
}

// 处理文件内容
fn process_file(filename: &str) -> proc_exit::ExitResult {
    let content = fs::read_to_string(filename)
        .map_err(|e| proc_exit::Code::IO_ERR.with_message(e))?;

    if content.trim().is_empty() {
        eprintln!("Warning: File '{}' is empty", filename);
        return proc_exit::Code::DATA_ERR.to_result();
    }

    println!("Processing file content...");
    println!("First 100 chars: {}", &content[..content.len().min(100)]);

    // 模拟处理过程
    if content.contains("error") {
        eprintln!("Error: File contains 'error' keyword");
        return proc_exit::Code::DATA_ERR.to_result();
    }

    // 模拟成功处理
    println!("File processed successfully");
    Ok(())
}

fn main() -> proc_exit::ExitResult {
    let args: Vec<String> = std::env::args().collect();

    // 参数检查
    if args.len() != 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        return proc_exit::Code::USAGE.to_result();
    }

    let filename = &args[1];

    // 检查文件
    check_file(filename)?;

    // 处理文件
    process_file(filename)?;

    // 成功退出
    proc_exit::Code::SUCCESS.to_result()
}

相关库

其他可能对测试命令行程序有用的库:

  • duct 用于协调多个进程
  • commandspec 用于更容易地编写命令
  • rexpect 用于控制交互式程序
  • assert_cmd 可以重用以简化控制CLI

许可证

根据以下任一许可证授权:

  • Apache License, Version 2.0
  • MIT license

由你选择。


1 回复

Rust进程控制库proc-exit的使用:安全终止程序与自定义退出码管理

proc-exit是一个轻量级的Rust库,专门用于安全地终止程序并管理退出码。它提供了比直接使用std::process::exit更安全和灵活的方式来控制程序退出行为。

主要特性

  1. 提供类型安全的退出码管理
  2. 支持自定义退出码枚举
  3. 允许在退出前执行清理操作
  4. 提供与anyhoweyre等错误处理库的集成

基本使用方法

首先在Cargo.toml中添加依赖:

[dependencies]
proc-exit = "1.0.0"

简单示例

use proc_exit::prelude::*;

fn main() {
    // 成功退出
    proc_exit::exit(0);
    
    // 或使用预定义的常量
    proc_exit::exit!(proc_exit::Code::SUCCESS);
}

自定义退出码

use proc_exit::prelude::*;

#[derive(Debug)]
enum ExitCode {
    Success = 0,
    InvalidInput = 1,
    IoError = 2,
    CalculationError = 3,
}

fn main() {
    let result = do_something();
    
    match result {
        Ok(_) => proc_exit::exit!(ExitCode::Success),
        Err(e) => {
            eprintln!("Error: {}", e);
            proc_exit::exit!(ExitCode::CalculationError);
        }
    }
}

fn do_something() -> Result<(), String> {
    // 模拟可能失败的操作
    if rand::random() {
        Ok(())
    } else {
        Err("Something went wrong".to_string())
    }
}

与错误处理库集成

与anyhow集成

use proc_exit::prelude::*;
use anyhow::{Context, Result};

fn main() -> proc_exit::ExitResult {
    run().with_code(1)
}

fn run() -> Result<()> {
    let config = std::fs::read_to_string("config.toml")
        .context("Failed to read config file")?;
    
    // 处理配置...
    
    Ok(())
}

与eyre集成

use proc_exit::prelude::*;
use eyre::{WrapErr, Result};

fn main() -> proc_exit::ExitResult {
    run().with_code(1)
}

fn run() -> Result<()> {
    let data = std::fs::read("data.bin")
        .wrap_err("Could not read data file")?;
    
    // 处理数据...
    
    Ok(())
}

高级用法

退出前执行清理

use proc_exit::prelude::*;

fn main() {
    let cleanup = || {
        println!("Performing cleanup before exit...");
        // 执行清理操作
    };
    
    // 注册退出时的清理函数
    proc_exit::set_hook(cleanup);
    
    // 现在任何通过proc_exit::exit的退出都会先执行清理
    if some_condition() {
        proc_exit::exit!(1);
    }
}

fn some_condition() -> bool {
    true
}

自定义退出处理

use proc_exit::prelude::*;

fn main() {
    // 替换默认的退出处理
    proc_exit::set_exit(move |code| {
        println!("Custom exit handler with code: {}", code);
        std::process::exit(code);
    });
    
    proc_exit::exit!(0);
}

完整示例代码

下面是一个综合了上述特性的完整示例:

use proc_exit::prelude::*;
use anyhow::{Context, Result};

// 自定义退出码枚举
#[derive(Debug)]
enum AppExitCode {
    Success = 0,
    ConfigError = 1,
    DataError = 2,
    RuntimeError = 3,
}

fn main() -> proc_exit::ExitResult {
    // 设置退出前的清理函数
    proc_exit::set_hook(|| {
        println!("执行清理操作:关闭文件、释放资源等...");
    });

    // 主程序逻辑
    run().map_err(|e| {
        eprintln!("应用程序错误: {}", e);
        // 根据错误类型返回不同的退出码
        if e.to_string().contains("配置文件") {
            proc_exit::Code::from(AppExitCode::ConfigError)
        } else if e.to_string().contains("数据") {
            proc_exit::Code::from(AppExitCode::DataError)
        } else {
            proc_exit::Code::from(AppExitCode::RuntimeError)
        }
    })
}

fn run() -> Result<()> {
    // 模拟读取配置文件
    let config = std::fs::read_to_string("config.toml")
        .context("无法读取配置文件")?;
    
    // 模拟处理数据
    let data = std::fs::read("data.bin")
        .context("无法读取数据文件")?;
    
    // 模拟业务逻辑
    if rand::random() {
        Ok(())
    } else {
        Err(anyhow::anyhow!("随机运行时错误"))
    }
}

这个完整示例展示了:

  1. 自定义退出码枚举
  2. 退出前的清理操作
  3. 与anyhow错误处理集成
  4. 根据错误类型返回不同的退出码
  5. 类型安全的退出码管理

为什么使用proc-exit而不是std::process::exit

  1. 类型安全proc-exit提供了类型安全的退出码管理
  2. 灵活性:支持自定义退出码枚举
  3. 可扩展性:可以注册退出前的钩子函数
  4. 集成性:与流行的错误处理库无缝集成
  5. 测试友好:更容易在测试中模拟退出行为

proc-exit是构建需要精细控制退出行为的命令行工具的绝佳选择,特别是当你的程序需要以不同的状态码退出,或者在退出前需要执行清理操作时。

回到顶部