14 个版本 (5 个重大变更)
0.5.4 | 2024年2月12日 |
---|---|
0.5.3 | 2024年2月12日 |
0.4.0 | 2024年2月9日 |
0.3.0 | 2024年2月9日 |
0.0.2 | 2024年2月6日 |
#7 in #cmd
每月下载量166
25KB
279 行
Sheller
🐚 Sheller 是一个用 Rust 编写的壳命令构建器和标准命令扩展库。
如果您能 star(⭐) 这个仓库,我将非常感激! 点击跳转到仓库。
为什么创建这个
TL;DR
我创建它是因为我想从 Rust 在多平台上调用 npm install
。
(npm
在 Windows 平台上以文件名 npm.cmd
安装,而 Rust 的 std::process::Command
不支持基于 PATHEXT 的可执行文件搜索,如 cmd.exe
、pwsh.exe
或 go
)
我使用 Rust 编写用于管理 Rust 库和应用程序项目的实用函数。
例如,调用 cargo clippy -- -D clippy::all -D clippy::pedantic
。
目前,此项目也是一个 Rust 项目。因此,我使用 Rust 编写实用函数。
让我们以 Git 预推送钩子为例,它在推送至 Github 之前进行验证。
此项目的 .cargo-husky/hooks/pre-push
文件在设置 Cargo 项目时被复制到 .git/hooks/pre-push
,当调用 git push
时,此脚本将在实际推送之前被调用。
此脚本的实现,夸张地说,只有一行:cargo run --package tool-dev -- pre-push
。
脚本在一行中调用 Rust 命令行。实际的实现是用 Rust 代码编写的。
在代码中再次调用命令行,例如 cargo check ...
,cargo clippy ...
,cargo fmt ...
,以及 cargo test ...
。
它被写成Rust的原因有三个。
首先,Unix Shell或Windows批处理脚本的语法非常困难。
写一两条命令时没有太大问题。
然而,管理需求逐渐增加。
条件语句、循环语句和函数语法按照我的标准来说也不直观。
由于我记不住语法,每次都得查阅Stack Overflow或询问Chat GPT。
第二,为了使用简单的JavaScript或Python,我需要在项目开发环境中添加依赖。
实际上,在我的开发环境中安装Node.js或Python运行时并不需要太多努力。
然而,Rust也有一个方便的工具,叫做 cargo run
。
根据项目的大小,构建可能需要相当长的时间。
然而,一旦构建了依赖库,即使多次调用也不会再次构建。
所以,在第一次构建之后,开发体验并不差。
第三,我是一个Rust新手。
撰写本文时,Rust项目始于2024年。
为了熟练掌握,每次有机会编程时,我都尽量用Rust来编写。
介绍有点长。
直截了说,是因为 npm install
。
为了解释因果关系,如下所述。
我开始了一个用Rust编写TypeScript编译器的项目。
我正在用Rust编写代码,在开发环境中安装TypeScript示例项目。
要在Rust中调用 npm install
,我必须调用shell命令,并将 npm install
作为参数传递。
这是因为 npm
命令是一个脚本,而不是可执行文件。
由于它是一个脚本,必须作为参数传递给shell命令。
而且这个shell命令在Windows和Unix平台上很大程度上不同。
目标不是创建平台无关的脚本。
目标是能够在shell环境中调用命令行。
因此,我创建了Sheller库。
这是为了在使用的Rust项目中编写实用函数,或者在未来需要时在Rust应用程序中使用它们。
它是如何工作的
如果你想调用 echo hello
,那么
Windows
当 target_family
是 windows
时。
将 COMSPEC
环境变量设置为 program
,如果环境变量未设置,则使用 cmd.exe
作为后备程序。
还将 args
设置为 ["/D", "/S", "/C", "echo hello"]
.
Unix
当 target_family
是 unix
时。
将环境变量 SHELL
设置为程序,如果没有设置环境变量,则使用 /bin/sh
作为后备程序。
还将 args
设置为 ["-c", "echo hello"]
.
使用方法
将 sheller
添加到您的依赖项中。
# Cargo.toml
[dependencies]
sheller = "0.5"
以下是一些使用 sheller
的示例。
如果您只想运行一个 shell 脚本,请按以下方式使用。
// crates/examples/readme/src/run.rs
use sheller::run;
fn main() {
run!("echo hello");
// It will be printed as below, or panicked.
// hello
}
如果您不想出现 panic
,可以使用 try_run
方法接收和处理 sheller::Result<()>
.
// crates/examples/readme/src/try_run.rs
use sheller::try_run;
fn main() -> sheller::Result<()> {
try_run!("echo hello")
}
📢 如果您想看到执行的命令行输出,请将 tracing 添加到您的依赖项中。
Sheller 内部使用流行的集中式结构化日志系统 tracing
。
以下是使用 tracing
的示例。
# Cargo.toml
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
// crates/examples/readme/src/run_with_log.rs
use sheller::run;
fn main() {
init_log();
run!("echo hello");
// 2024-02-09T19:11:29.897389Z INFO sheller: Running command. command="/bin/bash" "-c" "echo hello"
// hello
// 2024-02-09T19:11:29.898254Z INFO sheller: Succeeded to run command with zero exit code. command="/bin/bash" "-c" "echo hello"
}
fn init_log() {
tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::TRACE)
.finish(),
)
.expect("setting default subscriber failed");
}
👀 关于如何使用跟踪的更多信息,请查看 tracing 文档。
Sheller
使用 std::process::Command
。
如果您想更改当前工作路径、stdout/stderr 或环境变量,请使用 Sheller::build
方法。
此方法返回 std::process::Command
。
以下是更改当前工作路径的示例。
⚠️ 如果您看不到 run
方法,请检查 use sheller::CommandExt
。
// crates/examples/readme/src/builder.rs
use sheller::{new, CommandExt};
fn main() {
let mut command = new!("echo hello").build();
command.current_dir("/my/dir").run();
}
同样,可以同时使用 run
和 try_run
。
如果您想将 stdout 管道化,请参阅下面的示例。
// crates/examples/readme/src/pipe.rs
use sheller::new;
static EOL: &str = if cfg!(windows) { "\r\n" } else { "\n" };
fn main() {
let output = new!("echo hello")
.build()
.stdout(std::process::Stdio::piped())
.output()
.unwrap();
assert_eq!(output.stdout, format!("hello{EOL}").as_bytes());
}
除了上述方法外,您当然也可以使用 Rust 官方的 std::process::Command
方法。
有关 std::process::Command
的更多信息,请查看 Rust 官方页面。
run
和 try_run
方法作为 CommandExt
实现。
这些方法的目的在于实用。
因此,您不必一定使用 Sheller
。
以下是仅使用 CommandExt
而不使用 Sheller
的示例。
// crates/examples/readme/src/command_ext.rs
use sheller::CommandExt;
fn main() {
let mut command = std::process::Command::new("echo");
command.arg("hello").run();
}
信息
如果你的shell
进程没有结束,请使用 kill_tree!
例如,当发生ctrl + c
(或command + c
)事件时,将kill_tree::Config.include_target
设置为false
可以为除了当前进程外的所有子进程结束。
use kill_tree::{blocking::kill_tree_with_config, Config};
use std::sync::mpsc::channel;
fn cleanup_children() {
let current_process_id = std::process::id();
let config = Config {
include_target: false,
..Default::default()
};
let result = kill_tree_with_config(current_process_id, &config);
println!("kill_tree_with_config: {result:?}");
}
fn main() {
let (tx, rx) = channel();
ctrlc::set_handler(move || {
cleanup_children();
tx.send(()).expect("Could not send signal on channel.");
})
.expect("Error setting handler.");
println!("Current process id: {}", std::process::id());
println!("Waiting for signal...");
rx.recv().expect("Could not receive from channel.");
println!("Got it! Exiting...");
}
依赖项
~420KB