Day23:Call shell command - 100DayOfRust
Tags: rust
Published: 2019-12-11 03:49:10

I want to call some shell commands from Rust. In Python it is as easy as:

```python
import os
os.system("ls -alh")
```

In Rust, we can use the `std::process::Command` function.


### `ls` example

```rust
use std::process::Command;
use std::str;


fn main() {
    // ls
    let output = Command::new("ls")
                         .arg("-alh")
                         .output()
                         .unwrap();
    let output = str::from_utf8(&output.stdout).unwrap();
    println!("{}", output);
}
```

Run it we can get something like:
```
total 8
drwxr-xr-x  3 0xbf  staff    96B Dec 10 22:23 .
drwxr-xr-x  8 0xbf  staff   256B Dec 10 22:23 ..
-rw-r--r--  1 0xbf  staff   288B Dec 10 22:27 main.rs
```

### `cp` example

Suppose we have folder `source` and `target`, under `source` there is a `a.log` file and another folder b. The file structure like this:
```
|- source
    |- a.log
    |- b
|- target
```

Now we want to recursively copy all files under source to target:

```rust
use std::process::Command;


fn main() {
    // cp -r source target
    let status = Command::new("cp")
                         .arg("-r")
                         .arg("source")
                         .arg("target")
                         .status()
                         .unwrap();
    println!("cp status: {}", status);
}
```

If we run this, we could see it copied the source folder under target, like this:
```
|- source
    |- a.log
    |- b
|- target
    |- source
        |- a.log
        |- b
```

But this is not what I want, I want to only copy file `a.log` and folder `b` to the `target` folder, not the `source` folder itself. We know in shell we can simply do `cp -r source/* target`, so let's try to change the code to:

```rust
let status = Command::new("cp")
                         .arg("-r")
                         .arg("source/*")
                         .arg("target")
                         .status()
                         .unwrap();
```

Now if we run the code, we will actually get an error:

```
cp: source/*: No such file or directory
cp status: exit code: 1
```

The program treated `source/*` as a single file, which in this case, we don't have a file named as `source/*`. It doesn't work.

So what's the solution? Well, we can use `sh -c "cp -r source/* target"` !

```rust
let status = Command::new("sh")
                         .arg("-c")
                         .arg("cp -r source/* target")
                         .status()
                         .unwrap();
println!("cp status: {}", status);
```

Now if we run the code, we can see `cp status: exit code: 0` print out, and our file structure like this:
```
|- source
    |- a.log
    |- b
|- target
    |- a.log
    |- b
```

It works!

P.S, While Rust has `fs::copy` function to copy one single file, it doesn't have function to recursively copy a folder to another. Using the method we just introduced can achieve such goal. Another way is using the `fs_extra` [crate](https://docs.rs/fs_extra/1.1.0/fs_extra/fn.copy_items.html), but if your program only run in Linux/Unix system, I think directly call shell `cp` command is simpler.