Scripting · CLI hosts
Run scripts & use the interactive shell.
The same native statement engine powers standalone files with nift script.f and the persistent nift interpreter.
Run a script
nift scripts/report.f who := read()
print("hello, $[who]")
return 0 A script starts in a fresh root scope. Fallthrough and bare return exit successfully without return output; return expression renders the returned value to stdout. Parse, runtime and I/O errors use stderr and a non-zero status.
Inline programs and interactive continuation
nift -e 'name := "Ada"; print(name)'
nift -c 'name := "Ada"; print(name)'
nift -i -e 'x := 40; x += 2' -e executes a complete Nift program supplied on the command line; -c is an exact alias. This is intentionally different from nift eval, whose contract is expression-oriented and can emit JSON. Add -i to continue into the interactive shell after a successful inline program or file, using the same live runtime: bindings and named functions created by the initial program remain available. If the initial program fails, Nift exits non-zero instead of entering the shell. Inline programs use <command-line> as cmd; arguments after the source become args, with -- available when an option-looking argument must be literal.
Programs from stdin
printf 'print("hello")\n' | nift -
nift - < scripts/report.f nift - consumes stdin through EOF as one Nift program. It is intentionally distinct from plain nift, which starts the interactive shell. Stdin programs use <stdin> as their stable cmd identity and preserve parser source locations in diagnostics. Empty stdin is a successful empty program; a NUL byte is rejected explicitly rather than treated as a string terminator. Arguments after - populate args, with -- available for literal option-looking arguments.
The former nift run ... and nift sh wrappers are removed. Use nift script.f and plain nift.
Executable scripts
There are two deliberately different ways to execute a Nift script. nift script.f is explicit Nift source execution and needs no shebang or execute permission. ./script.f is ordinary executable-process invocation: the OS requires the normal shebang and execute-bit semantics, exactly like Python, Bash or Ruby.
#!/usr/bin/env nift
/*
Build and deploy the project.
*/
print("hello") chmod +x deploy.f
./deploy.f A shebang is treated as a script header only when #! starts the first source line; it is not Nift syntax elsewhere. The leading shebang is stripped, so the same file runs identically through nift deploy.f and ./deploy.f. A Nift script is not special-cased when executed as a path: ./script.f, ./thing.sh, ./thing.py and binaries all go through the same OS process model. ./deploy.f also works from nift and from another .f script, where it is ordinary external-command execution (never in-process evaluation).
$ ./deploy.f staging --force ./deploy.f staging --force
r := run("./deploy.f", "staging", "--force")
print(r.exit_code)
User arguments are available inside the script as an immutable args array (the script path itself is not included): ./deploy.f staging --force gives ["staging", "--force"]. nift deploy.f staging --force supplies the same user arguments without requiring executable permission. --no-process blocks external ./script.f execution (both command-style and run()); launching the script itself is how the parent process started and is not the same operation.
Under a real shebang launch, cmd preserves the script spelling supplied by the OS (for example ./deploy.f), args contains the user arguments, and cwd, environment, exit status and signals retain ordinary process semantics. Standalone execution also provides immutable cmd and args bindings. cmd is the script invocation spelling (for example ./deploy.f), while args contains only user arguments. Use -- to force later option-looking tokens into args, for example nift deploy.f -- --no-process. The interactive shell uses <repl> as its stable cmd identity and an empty args array.
Interactive shell
$ nift
~/project$ x := 10
~/project$ x++
~/project$ x
11
~/project$ ls().prettify()
[
"README.md",
"content",
"docs"
]
~/project$ cd("/tmp")
/tmp$ The shell retains one root lexical environment between commands. Bare expressions display their safe compact value directly, so inspecting x, ls() or another data expression does not require print(). Use .prettify() when an expanded human-readable representation is easier to inspect. Its prompt shows the current working directory followed by $. Paths below the user home directory are shortened from /home/<user>/... to ~/...; on a colour-capable terminal the path is bold green while $ remains in the terminal default colour. cd(path) changes both the process working directory and the next prompt. Use .highlight() on a serializable value to request syntax-coloured compact inspection in an interactive colour-capable REPL; the underlying returned string remains ANSI-free.
Ordinary errors are reported without destroying the persistent session. Multiline input uses the parser-reported statement state: an incomplete prefix (an unclosed block, parenthesis, bracket or quoted string) keeps the shell reading with a continuation prompt, while a complete statement executes and a balanced-but-invalid form is reported without terminating the session.
On an interactive terminal the shell is a small line editor with TAB completion and arrow-key history. TAB completes Nift builtins, user/imported functions and current bindings, PATH executables and filesystem paths (directories gain a trailing /); a second TAB lists the remaining candidates and completed paths are quoted when they contain shell-special characters. The up/down arrows recall the persistent history, Ctrl-C cancels the current line, and Ctrl-D on an empty line exits. The same completion candidates are available non-interactively through nift complete <prefix>.