22 releases

0.3.5 Dec 13, 2023
0.3.4 Jul 26, 2023
0.3.2 Jun 20, 2023
0.2.7 Nov 15, 2022
0.1.1 Jul 15, 2020

#49 in Command-line interface

Download history 2140/week @ 2024-02-27 2095/week @ 2024-03-05 2622/week @ 2024-03-12 4798/week @ 2024-03-19 4798/week @ 2024-03-26 3791/week @ 2024-04-02 4327/week @ 2024-04-09 3801/week @ 2024-04-16 4857/week @ 2024-04-23 3715/week @ 2024-04-30 4876/week @ 2024-05-07 5108/week @ 2024-05-14 3837/week @ 2024-05-21 4895/week @ 2024-05-28 5917/week @ 2024-06-04 4377/week @ 2024-06-11

20,154 downloads per month
Used in 13 crates

MIT license

1.5K SLoC


clio is a rust library for parsing CLI file names.

It implements the standard unix conventions of when the file name is "-" then sending the data to stdin/stdout as appropriate. With the clap-parse feature it also adds a bunch of useful filters to validate paths from command line parameters, e.g. it exists or is/isn't a directory.


Inputs and Outputs can be created directly from args in args_os. They will error if the file cannot be opened for any reason

// a cat replacement
fn main() -> clio::Result<()> {
    for arg in std::env::args_os().skip(1) {
        let mut input = clio::Input::new(&arg)?;
        std::io::copy(&mut input, &mut std::io::stdout())?;

If you want to defer opening the file you can use InputPaths and OutputPaths. This avoid leaving empty Output files around if you error out very early. These check that the path exists, is a file and could in theory be opened when created to get nicer error messages from clap. Since that leaves room for TOCTTOU bugs, they will still return a Err if something has changed when it comes time to actually open the file.

With the clap-parse feature they are also designed to be used with clap 3.2+.

See the older docs for examples of older clap/structopt

# #[cfg(feature="clap-parse")]{
use clap::Parser;
use clio::*;
use std::io::Write;

#[clap(name = "cat")]
struct Opt {
    /// Input file, use '-' for stdin
    #[clap(value_parser, default_value="-")]
    input: Input,

    /// Output file '-' for stdout
    #[clap(long, short, value_parser, default_value="-")]
    output: Output,

    /// Directory to store log files in
    #[clap(long, short, value_parser = clap::value_parser!(ClioPath).exists().is_dir(), default_value = ".")]
    log_dir: ClioPath,

fn main() {
    let mut opt = Opt::parse();

    let mut log = opt.log_dir.join("cat.log").create().unwrap_or(Output::std_err());

    match std::io::copy(&mut opt.input, &mut opt.output) {
        Ok(len) => writeln!(log, "Copied {} bytes", len),
        Err(e) => writeln!(log, "Error {:?}", e),
# }

Alternative crates


Nameless is an alternative to clap that provides full-service command-line parsing. This means you just write a main function with arguments with the types you want, add a conventional documentation comment, and it uses the magic of procedural macros to take care of the rest.

It's input and output streams have the many of the same features as clio (e.g. '-' for stdin) but also support transparently decompressing inputs, and more remote options such as scp://


If you are as horified as I am by the amount of code in this crate for what feels like it should have been a very simple task, then patharg is a much lighter crate that works with clap for treating '-' as stdin/stdout.

It does not open the file, or otherwise validate the path until you ask it avoiding TOCTTOU issues but in the process looses the nice clap error messages.

It also avoids a whole pile of complexity for dealing with seeking and guessing up front if the input supports seeking.

Also watch out patharg has no custom clap ValueParser so older versions of clap will convert via a String so path will need to be valid utf-8 which is not guarnatied by linux nor windows.


If all you really need is support mapping '-' to stdin() try this lovely function distilled from patharg.

It works because either has helpfully added impls for many common traits when both sides implement them.

    use either::Either;
    use std::io;
    use std::ffi::OsStr;
    use std::fs::File;

    pub fn open(path: &OsStr) -> io::Result<impl io::BufRead> {
        Ok(if path == "-" {
        } else {

The corresponding create function is left as an exercise for the reader.



Implements ValueParserFactory for all the types and adds a bad implementation of Clone to all types as well to keep clap happy.

HTTP Client

If a url is passed to Input::new then it will perform and HTTP GET. This has the advantage vs just piping in the output of curl as you know the input size, and can infer related urls, e.g. get the Cargo.lock to match the Cargo.toml.

If a url is passed to Output::new then it will perform and HTTP PUT. The main advantage over just piping to curl is you can use OutputPath::create_with_len to set the size before the upload starts e.g. needed if you are sending a file to S3.


bundles in ureq as a HTTP client.


bundles in curl as a HTTP client.


~192K SLoC