Single-file scripts that download their dependencies
An ideal distributable script is fully contained in a single file. It runs on any compatible operating system with an appropriate language runtime. It is plain text, and you can copy and paste it. It does not require mucking about with a package manager, or several, to run. It does not conflict with other scripts’ packages or require managing a project environment to avoid such conflicts.
The classic way to get around all of these issues with scripts is to limit yourself to using the scripting language’s standard library. However, programmers writing scripts don’t want to; they want to use libraries that do not come with the language by default. Some scripting languages, runtimes, and environments resolve this conflict by offering a means to download and cache a script’s dependencies with just declarations in the script itself. This page lists such languages, runtimes, and environments.
Contents
Anything with a Nix package
The Nix package manager can act as a #!
interpreter and start another program with a list of dependencies available to it.
#! /usr/bin/env nix-shell
#! nix-shell -i python3 -p python310 python310Packages.ansicolors
from colors import bold
print(bold("Hello, world!".rjust(20, "-")))
C# (dotnet-script)
The unofficial .NET tool dotnet-script can download NuGet packages.
#! /usr/bin/env dotnet-script"nuget: Spectre.Console, 0.46.0"
#r
using Spectre.Console;
Markup("[bold]" + "Hello, world!".PadLeft(20, '-') + "[/]\n"); AnsiConsole.
Clojure (Babashka)
The Babashka interpreter runs scripts in (a large subset of) Clojure. It has dynamic dependency resolution.
#! /usr/bin/env bb
require '[babashka.deps :as deps])
(:deps {coldnew/left-pad {:mvn/version "1.0.0"}}})
(deps/add-deps '{
require '[coldnew.left-pad :refer [leftpad]])
(print (leftpad "Hello, world!" 20 "-")) (
Crystal (crun)
You can write scripts in Crystal with crun. It was inspired by gorun.
#! /usr/bin/env crun
# ---
# crinja:
# github: straight-shoota/crinja
# ...
require "crinja"
puts Crinja.render(
"{{ greeting }}!",
{"greeting" => "Hello, world".rjust(19, '-')}
)
D
D’s official package manager DUB supports single-file packages.
#! /usr/bin/env dub/+ dub.sdl:
dependency "colorize" version="~>1.0.5"
+/
import std.conv : to;
import std.range : padLeft;
import colorize : color, cwriteln, mode;
void main() {
auto greeting = "Hello, world!".padLeft('-', 20).to!string;
cwriteln(greeting.color(mode.bold)); }
Dhall
The configuration language Dhall has native support for remote imports. You can write shebang scripts in Dhall with env -S
.
#! /usr/bin/env -S dhall text --file
let Text/replicate = https://prelude.dhall-lang.org/Text/replicate
in Text/replicate 7 "-"
++ ''
Hello, world!
''
Elixir
In Elixir 1.12 and later you can call Mix.install/2
from scripts.
#! /usr/bin/env elixir
Mix.install([
:leftpad, "~> 1.0"}
{
])
Leftpad.pad("Hello, world!", 20, ?-)
|> IO.puts()
F#
The .NET SDK has integrated support for F# scripts with dependencies.
#! /usr/bin/env -S dotnet fsi#r "nuget: Spectre.Console, 0.45.0"
open Spectre.Console
.Markup("[bold]" + "Hello, world!".PadLeft(20, '-') + "[/]\n") AnsiConsole
Go (gorun)
gorun lets you embed go.mod
and go.sum
in the source file.
/// 2>/dev/null ; gorun "$0" "$@" ; exit $?
//
// go.mod >>>
// module foo
// go 1.18
// require github.com/keltia/leftpad v0.1.0
// <<< go.mod
//
// go.sum >>>
// github.com/keltia/leftpad v0.1.0 h1:b2jL8i1cRsuLITknjWZHRAwf1Jwh+twMUkhwDXD7fqc=
// github.com/keltia/leftpad v0.1.0/go.mod h1:f/6pIO5tikWLxSz68iAcl9OXDpV6k2+2mRm9jShMzoU=
// <<< go.sum
package main
import "fmt"
import "github.com/keltia/leftpad"
func main() {
"Hello, world!", 20, '-')
padded, _ := leftpad.PadChar("%v\n", padded)
fmt.Printf( }
Groovy
Groovy comes with an embedded JAR dependency manager.
#! /usr/bin/env groovy
@Grab(group='org.apache.commons', module='commons-lang3', version='3.12.0')
import org.apache.commons.lang3.StringUtils
println StringUtils.leftPad('Hello, world!', 20, '-')
Haskell (Stack)
The Haskell build tool Stack has integrated support for scripts.
#! /usr/bin/env stack
-- stack --resolver lts-6.35 script --package acme-left-pad
import Data.Text.LeftPad
main :: IO ()
= do
main putStrLn $ leftPad "Hello, world!" 20 "-"
Java (JBang)
JBang lets you write scripts in Java, Kotlin, and Groovy. It was inspired by kscript.
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.commons:commons-lang3:3.12.0
import static java.lang.System.*;
import org.apache.commons.lang3.StringUtils;
public class hello {
public static void main(String... args) {
println(StringUtils.leftPad("Hello, world!", 20, "-"));
out.
} }
JavaScript
Deno
Deno downloads dependencies like a browser. Deno 1.28 and later can also import from NPM packages. Current versions of Deno require you to pass a run
argument to deno
. One way to accomplish this from a script is with a form of “exec magic”. Here the magic is modified from a comment by Rafał Pocztarski.
#! /bin/sh
":" //#; exec /usr/bin/env deno run "$0" "$@"
import leftPad from "npm:left-pad";
console.log(leftPad("Hello, world!", 20, "-"));
On Linux systems with recent GNU env(1) and on FreeBSD you can replace the magic with env -S
.
#! /usr/bin/env -S deno run
import leftPad from "npm:left-pad";
console.log(leftPad("Hello, world!", 20, "-"));
Bun
Bun resolves NPM dependencies in scripts.
#! /usr/bin/env bun
import leftPad from "left-pad";
console.log(leftPad("Hello, world!", 20, "-"));
Kotlin (kscript)
kscript is an unofficial scripting tool for Kotlin that understands several comment-based directives, including one for dependencies. Since version 1.4 Kotlin also has experimental integrated scripting.
New versions of kscript do not allow a space after #!
.
#!/usr/bin/env kscript
@file:DependsOn("org.apache.commons:commons-lang3:3.12.0")
import org.apache.commons.lang3.StringUtils
"Hello, world!", 20, "-")) println(StringUtils.leftPad(
Racket (Scripty)
Scripty interactively prompts you to install the missing dependencies for a script in any Racket language.
#! /usr/bin/env racket
#lang scripty
#:dependencies '("base" "typed-racket-lib" "left-pad")
------------------------------------------
#lang typed/racket/base
(require left-pad/typed)
(displayln (left-pad "Hello, world!" 20 "-"))
Ruby (Bundler)
The dependency manager Bundler has an API for single-file scripts.
#! /usr/bin/env ruby
'bundler/inline'
require
do
gemfile 'https://rubygems.org'
source 'left-pad', '~> 1.1'
gem end
'Hello, world!'.leftpad(20, '-') puts
Rust (rust-script)
rust-script can evaluate expressions and run scripts in Rust.
! /usr/bin/env rust-script
#//! ```cargo
//! [dependencies]
//! leftpad-rs = "1.2.0"
//! ```
use leftpad_rs::pad_char;
fn main() {
println!("{}", pad_char("Hello, world!", 20, '-').unwrap());
}
Scala
Ammonite
The scripting environment in Ammonite lets you import Ivy dependencies.
#! /usr/bin/env amm
import $ivy.`org.apache.commons:commons-lang3:3.12.0`,
apache.commons.lang3.StringUtils
org.
println(StringUtils.leftPad("Hello, world!", 20, "-"))
Scala CLI
Scala CLI is a newer tool that understands a subset of Ammonite’s imports.
#! /usr/bin/env scala-cli
import $ivy.`org.apache.commons:commons-lang3:3.12.0`,
apache.commons.lang3.StringUtils
org.
println(StringUtils.leftPad("Hello, world!", 20, "-"))
See also
- Hacker News discussion. Thanks to everyone who made suggestions.