如何在Unix系统上直接执行Rust代码?(使用shebang)

20

从阅读这个线程,看起来可以使用shebang运行Rust*。

#!/usr/bin/env rustc

fn main() {
    println!("Hello World!");
}

将这个文件变成可执行文件并运行,可以编译但却不能运行代码。

chmod +x hello_world.rs
./hello_world.rs
然而,这只是将代码编译成 hello_world

*.rs 文件是否可以直接执行,类似于 shell 脚本?


* 这引用了 rustx。我研究过这个,但它是一个 bash 脚本,每次编译脚本时都不会缓存并且从临时目录中永远不会删除该文件,虽然这可以改进。此外,它有一个重大限制,即无法使用 crates。


1
请点击以下链接查看代码库:https://github.com/killerswan/rustx - Josh Lee
5个回答

20

cargo-script,它还允许您使用依赖项。

通过 cargo install cargo-script 安装 cargo-script 后,您可以像这样创建您的脚本文件 (hello.rs):

#!/usr/bin/env run-cargo-script

fn main() {
    println!("Hello World!");
}

执行它需要:

$ chmod +x hello.rs
$ ./hello.rs
   Compiling hello v0.1.0 (file://~/.cargo/.cargo/script-cache/file-hello-d746fc676c0590b)
    Finished release [optimized] target(s) in 0.80 secs
Hello World!
请参见上述链接的 README 中的教程,以使用来自 crates.io 的 crate。

4
这可能是最好的答案,但不幸的是,cargo-script项目似乎已经被放弃。新读者也许想查看其他答案以确定是否更适合他们。我选择了scriptisto来进行个人使用。 - GrandOpener

9
这似乎是可行的:
#!/bin/sh
//usr/bin/env rustc $0 -o a.out && ./a.out && rm ./a.out ; exit

fn main() {
    println!("Hello World!");
}

2
如果运行 a.out 返回一个非零的退出码,&& 将无法删除 a.out - ideasman42
2
“//usr/bin/env rustc $0 -o a.out && ./a.out; rm -f a.out; exit” 这样写是否更好? - rustyhu

4
我已经为此编写了一个工具:Scriptisto。它是一个完全不受语言限制的工具,适用于其他编译语言或具有昂贵验证步骤(例如使用 mypy 的 Python)的语言。
对于 Rust,它还可以在后台获取依赖项或在没有 Rust 编译器的情况下完全构建Docker scriptisto 将这些模板嵌入二进制文件中,使您可以轻松启动。
$ scriptisto new rust > ./script.rs
$ chmod +x ./script.rs
$ ./script.rs

你可以使用new docker-rust而不是new rust,这样构建过程就不需要在主机系统上安装Rust编译器。


0
虽然其他答案非常好,但我想要一个简单的方法来编译、缓存和运行独立脚本。我的理由是,如果我要分发依赖于Rust安装的脚本,我可能也不能依赖于某个第三方库被安装以便它被编译。
如果我要费心传递多个文件,我倒不如直接传递一个预编译的二进制文件。如果我的脚本/程序用例足够复杂,那么我可能会通过标准的过程在git仓库中进行。
因此,对于只依赖于Rust和标准库的单个文件,请使用像以下这样的hello.rs文件:
#!/bin/sh
//bin/bash -ec '[ "$0" -nt "${0%.*}" ] && rustc "$0" -o "${0%.*}"; "${0%.*}" "$@"' "$0" "$@"; exit $?

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() > 1 {
        println!("Hello {}!", &args[1]);
    } else {
        println!("Hello world!");
    }
}


为了更好地理解shebang的作用,请尝试使用以下代码。它实现了相同的功能,但更易于维护:
#!/bin/sh
//bin/bash -exc 'source_file="$0"; exe_file="${source_file%.*}"; [ "$source_file" -nt "$exe_file" ] && rustc "$source_file" -o "$exe_file"; "$exe_file" "$@"' "$0" "$@"; exit $?

这个解决方案基于wimh的解决方案,但具有以下附加功能:

  1. 在与脚本相同的目录中缓存编译后的脚本,也适用于将脚本目录添加到路径中的情况。
  2. 将脚本的命令行参数传递给程序。
  3. 脚本的退出代码与程序或编译器的退出代码相同。

注意:shebang脚本依赖于脚本文件具有某种文件后缀,例如.rs.sh。否则,编译器会抱怨要用生成的可执行文件覆盖脚本文件。

我的测试显示,与直接运行编译后的程序相比,该脚本增加了约10毫秒的开销。

编辑:正在进行RFC以添加类似Scriptisto的解决方案到Rust核心,以便以标准方式解决OP的问题。


0
#!/bin/sh
#![allow()] /*
            exec cargo-play --cached --release $0 -- "$@"
                        */

需要cargo-play。你可以在这里看到一个不需要任何东西的解决方案here

#!/bin/sh
#![allow()] /*

# rust self-compiler by Mahmoud Al-Qudsi, Copyright NeoSmart Technologies 2020
# See <https://neosmart.net/blog/self-compiling-rust-code/> for info & updates.
#
# This code is freely released to the public domain. In case a public domain
# license is insufficient for your legal department, this code is also licensed
# under the MIT license.

# Get an output path that is derived from the complete path to this self script.
# - `realpath` makes sure if you have two separate `script.rs` files in two
#   different directories, they get mapped to different binaries.
# - `which` makes that work even if you store this script in $PATH and execute
#   it by its filename alone.
# - `cut` is used to print only the hash and not the filename, which `md5sum`
#   always includes in its output.
OUT=/tmp/$(printf "%s" $(realpath $(which "$0")) | md5sum | cut -d' '  -f1)

# Calculate hash of the current contents of the script, so we can avoid
# recompiling if it hasn't changed.
MD5=$(md5sum "$0" | cut -d' '  -f1)

# Check if we have a previously compiled output for this exact source code.
if !(test -f "${OUT}.md5" && test "${MD5}" = "$(cat ${OUT}.md5)"); then
    # The script has been modified or is otherwise not cached.
    # Check if the script already contains an `fn main()` entry point.
    if grep -Eq '^\s*(\[.*?\])*\s*fn\s*main\b*' "$0"; then
        # Compile the input script as-is to the previously determined location.
        rustc "$0" -o ${OUT}
        # Save rustc's exit code so we can compare against it later.
        RUSTC_STATUS=$?
    else
        # The script does not contain an `fn main()` entry point, so add one.
        # We don't use `printf 'fn main() { %s }' because the shebang must
        # come at the beginning of the line, and we don't use `tail` to skip
        # it because that would result in incorrect line numbers in any errors
        # reported by rustc, instead we just comment out the shebang but leave
        # it on the same line as `fn main() {`.
        printf "fn main() {//%s\n}" "$(cat $0)" | rustc - -o ${OUT}
        # Save rustc's exit code so we can compare against it later.
        RUSTC_STATUS=$?
    fi

    # Check if we compiled the script OK, or exit bubbling up the return code.
    if test "${RUSTC_STATUS}" -ne 0; then
        exit ${RUSTC_STATUS}
    fi

    # Save the MD5 of the current version of the script so we can compare
    # against it next time.
    printf "%s" ${MD5} > ${OUT}.md5
fi

# Execute the compiled output. This also ends execution of the shell script,
# as it actually replaces its process with ours; see exec(3) for more on this.
exec ${OUT} $@

# At this point, it's OK to write raw rust code as the shell interpreter
# never gets this far. But we're actually still in the rust comment we opened
# on line 2, so close that: */

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接