{"id":34973146,"url":"https://github.com/harryplusplus/sheller","last_synced_at":"2026-05-21T08:32:39.498Z","repository":{"id":219718915,"uuid":"749729248","full_name":"harryplusplus/sheller","owner":"harryplusplus","description":"🐚 Sheller is a shell command builder and standard command extension library written in Rust.","archived":false,"fork":false,"pushed_at":"2024-02-12T23:49:34.000Z","size":96,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-25T06:36:06.393Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/harryplusplus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-01-29T09:27:43.000Z","updated_at":"2025-08-29T20:27:45.000Z","dependencies_parsed_at":"2025-04-27T21:45:31.867Z","dependency_job_id":null,"html_url":"https://github.com/harryplusplus/sheller","commit_stats":null,"previous_names":["oneofthezombies/shell-process","oneofthezombies/sheller","harryplusplus/sheller"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/harryplusplus/sheller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fsheller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fsheller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fsheller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fsheller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/harryplusplus","download_url":"https://codeload.github.com/harryplusplus/sheller/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harryplusplus%2Fsheller/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33294028,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T02:57:32.698Z","status":"ssl_error","status_checked_at":"2026-05-21T02:57:31.990Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-26T23:53:29.997Z","updated_at":"2026-05-21T08:32:39.492Z","avatar_url":"https://github.com/harryplusplus.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sheller\n\n🐚 Sheller is a shell command builder and standard command extension library written in Rust.  \n\nI would really APPRECIATE it if you could star(⭐) this repository! [Click to jump to the repository.](https://github.com/oneofthezombies/sheller)  \n\n[![Build Status][actions-badge]][actions-url]\n[![Crates.io][crates-badge]][crates-url]\n\n[actions-badge]: https://github.com/oneofthezombies/sheller/workflows/CI/badge.svg\n[actions-url]: https://github.com/oneofthezombies/sheller/actions?query=workflow?CI+branch=main\n[crates-badge]: https://img.shields.io/crates/v/sheller.svg\n[crates-url]: https://crates.io/crates/sheller\n\n## Why I Created This\n\n### TL;DR  \n\n---\n\nI created it because I want to call `npm install` from Rust on multiplatforms.  \n(`npm` is installed with the file name `npm.cmd` on Windows platforms. and Rust `std::process::Command` does not support PATHEXT-based executable search like `cmd.exe`, `pwsh.exe` or `go`)\n\n--- \n\nI use Rust to write utility functions for managing Rust library and application projects.  \nFor example, calling `cargo clippy -- -D clippy::all -D clippy::pedantic`.  \nCurrently, this project is also a Rust project. Therefore, I wrote utility functions in Rust.  \n\nLet's take an example of the Git pre-push hook that verifies before pushing to Github.  \nThe `.cargo-husky/hooks/pre-push` file of this project is a file that is copied to `.git/hooks/pre-push` when the Cargo project is set up, and when `git push` is called, this script is called before a push is actually made.  \nThe implementation of this script is, to put it exaggeratedly, one line: `cargo run --package tool-dev -- pre-push`.  \nThe script calls the Rust command line in one line. And the actual implementation is written in Rust code.  \nWithin the code, call the command line again, such as `cargo check ...`, `cargo clippy ...`, `cargo fmt ...`, and `cargo test ...`.  \n\nThere are three reasons why it was written in Rust.  \n\nFirst, the syntax of Unix Shell or Windows Batch scripts is very difficult.  \nThere is no major problem when writing one or two lines.  \nHowever, management requirements are gradually increading.  \nConditional statements, loop statements, and function syntax are also not intuitive by my standards.  \nSince I can't memorize the syntax, I have to look up Stack Overflow or ask Chat GPT every time.  \n\nSecond, to use easy JavaScript or Python, I need to add a dependency to the project development environment.  \nIn fact, installing Node.js or Python runtime in my development environment doesn't require much effort.  \nHowever, Rust also has a convenient tool called `cargo run`.  \nDepending on the size of the project, the build may take quite a while.  \nHowever, once dependent libraries are built, no additional builds occur even if they are called multiple times.  \nSo, the developer experience is not bad after the first build.  \n\nThird, I am a Rust newbie.  \nThe Rust project was first started in 2024, when this article was written.  \nTo become proficient, whenever I get a chance to program something, I try to write it all in Rust.  \n\nThe introduction was long.  \nTo get straight to the point, it's because of `npm install`.  \nTo explain cause and effect, it is as follows.  \nI started a project to write a TypeScript compiler in Rust.  \nI was writing code in Rust to install the TypeScript sample project in the development environment.  \nTo call `npm install` in Rust, I must call the shell command and pass `npm install` as an argument.  \nThis is because the `npm` command is a script, not an executable file.  \nBecause it is a script, it must be passed as an argument to the shell command.  \nAnd this shell command is largely different for each Windows and Unix platform.  \nThe goal is not to create platform-independent scripts.  \nThe goal is to be able to call command lines in a shell environment.  \n\nFor this reason, I created the Sheller library.  \nThis is to write utility functions in the Rust project I use, or to use them in a Rust application if needed in the future.  \n\n## How does it Work\n\nIf you want to call `echo hello` then,\n\n### Windows  \n\nWhen `target_family` is `windows`.  \nSet the `COMSPEC` environment variable to `program`, and if the environment variable is not set, used `cmd.exe` as the fallback program.  \nAlso set the `args` to `[\"/D\", \"/S\", \"/C\", \"echo hello\"]`.  \n\n### Unix\n\nWhen `target_family` is `unix`.  \nSet the `SHELL` environment variable to program, and if the environment variable is not set, used `/bin/sh` as the fallback program.  \nAlso set the `args` to `[\"-c\", \"echo hello\"]`.\n\n## How to Use\n\nAdd `sheller` to your dependencies.\n\n```toml\n# Cargo.toml\n[dependencies]\nsheller = \"0.5\"\n```\n\nBelow are examples using `sheller`.  \n\nIf you simply want to run a shell script, use it as follows.  \n\n```rust\n// crates/examples/readme/src/run.rs\nuse sheller::run;\n\nfn main() {\n    run!(\"echo hello\");\n    // It will be printed as below, or panicked.\n    // hello\n}\n```\n\nIf you don't want `panic`, you can use the `try_run` methods to receive and process `sheller::Result\u003c()\u003e`.  \n\n```rust\n// crates/examples/readme/src/try_run.rs\nuse sheller::try_run;\n\nfn main() -\u003e sheller::Result\u003c()\u003e {\n    try_run!(\"echo hello\")\n}\n```\n\n📢 If you want output of which command line is executed, add the [tracing](https://github.com/tokio-rs/tracing) to your dependencies.  \nSheller internally uses `tracing`, a pupular centralized structured logging system.  \n\nBelow is an example using `tracing`.\n\n```toml\n# Cargo.toml\n[dependencies]\ntracing = \"0.1\"\ntracing-subscriber = \"0.3\"\n```\n\n```rust\n// crates/examples/readme/src/run_with_log.rs\nuse sheller::run;\n\nfn main() {\n    init_log();\n\n    run!(\"echo hello\");\n    // 2024-02-09T19:11:29.897389Z  INFO sheller: Running command. command=\"/bin/bash\" \"-c\" \"echo hello\"\n    // hello\n    // 2024-02-09T19:11:29.898254Z  INFO sheller: Succeeded to run command with zero exit code. command=\"/bin/bash\" \"-c\" \"echo hello\"\n}\n\nfn init_log() {\n    tracing::subscriber::set_global_default(\n        tracing_subscriber::FmtSubscriber::builder()\n            .with_max_level(tracing::Level::TRACE)\n            .finish(),\n    )\n    .expect(\"setting default subscriber failed\");\n}\n```\n\n👀 For more information on how to use tracing, please check the [tracing documentation](https://docs.rs/tracing/latest/tracing/index.html).\n\n`Sheller` uses `std::process::Command`.  \nIf you want to change the current working path, stdout/stderr or environment variables, use the `Sheller::build` method.  \nThis method returns `std::process::Command`.  \n\nBelow is an example of changing the current working path.  \n\n⚠️ If you don't see the `run` method, check `use sheller::CommandExt`.  \n\n```rust\n// crates/examples/readme/src/builder.rs\nuse sheller::{new, CommandExt};\n\nfn main() {\n    let mut command = new!(\"echo hello\").build();\n    command.current_dir(\"/my/dir\").run();\n}\n```\n\nLikewise, `run` and `try_run` can all be used.  \n\nIf you want to pipe stdout, please see the example below.  \n\n```rust\n// crates/examples/readme/src/pipe.rs\nuse sheller::new;\n\nstatic EOL: \u0026str = if cfg!(windows) { \"\\r\\n\" } else { \"\\n\" };\n\nfn main() {\n    let output = new!(\"echo hello\")\n        .build()\n        .stdout(std::process::Stdio::piped())\n        .output()\n        .unwrap();\n    assert_eq!(output.stdout, format!(\"hello{EOL}\").as_bytes());\n}\n```\n\nIn addition to the methods above, you can of course also use the Rust official `std::process::Command` methods.  \nFor more information about `std::process::Command`, please check [the Rust official page](https://doc.rust-lang.org/std/process/struct.Command.html).  \n\nThe `run` and `try_run` methods are implemented as `CommandExt`.  \nThe purpose of these methods is utility.  \nSo you don't necessarily have to use `Sheller`.  \n\nBelow is an example that uses only `CommandExt` without using `Sheller`.  \n\n```rust\n// crates/examples/readme/src/command_ext.rs\nuse sheller::CommandExt;\n\nfn main() {\n    let mut command = std::process::Command::new(\"echo\");\n    command.arg(\"hello\").run();\n}\n```\n\n--- \n\n\u003e __INFO__  \nIf your `shell` process does not die, use [__kill_tree__](https://github.com/oneofthezombies/kill-tree)!  \nFor example, `ctrl + c` (or `command + c`) event occurs, `kill_tree::Config.include_target` Set to `false` to kill all child processes except the current process.  \n\n```rust\nuse kill_tree::{blocking::kill_tree_with_config, Config};\nuse std::sync::mpsc::channel;\n\nfn cleanup_children() {\n    let current_process_id = std::process::id();\n    let config = Config {\n        include_target: false,\n        ..Default::default()\n    };\n    let result = kill_tree_with_config(current_process_id, \u0026config);\n    println!(\"kill_tree_with_config: {result:?}\");\n}\n\nfn main() {\n    let (tx, rx) = channel();\n\n    ctrlc::set_handler(move || {\n        cleanup_children();\n        tx.send(()).expect(\"Could not send signal on channel.\");\n    })\n    .expect(\"Error setting handler.\");\n\n    println!(\"Current process id: {}\", std::process::id());\n    println!(\"Waiting for signal...\");\n    rx.recv().expect(\"Could not receive from channel.\");\n    println!(\"Got it! Exiting...\");\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharryplusplus%2Fsheller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharryplusplus%2Fsheller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharryplusplus%2Fsheller/lists"}