Do you find yourself using tools like make
to manage non-build-related scripts?
Build tools are great, but they are not optimized for general script management.
Run aims to be better at managing small scripts and wrappers, while incorporating a familiar make-like syntax.
Where make has the ubiquitous Makefile, run has the cleverly-named "Runfile"
By default, run will look for a file named "Runfile"
in the current directory, exiting with error if not found.
Read below for details on specifying alternative runfiles, as well as other special modes you might find useful.
In place of make's targets, runfiles contain 'commands'
.
Similar to make, a command's label is used to invoke it from the command-line.
Instead of recipes, each runfile command contains a 'script'
which is executed when the command is invoked.
You might be used to make's (default) behavior of executing each line of a recipe in a separate sub-shell.
In run, the entire script is executed within a single sub-shell.
- Simple Command Definitions
- Simple Title Definitions
- Title & Description
- Arguments
- Command-Line Options
- Run Tool Help
- Using an Alternative Runfile
- Runfile Variables
- Runfile Attributes
- Assertions
- Includes
- Includes - .ENV
- Invoking Other Commands & Runfiles
- Hidden / Private Commands
- Script Shells
Runfile
hello:
echo "Hello, world"
We'll see that hello
shows as an invokable command, but has no other help text.
list commands
$ run list
Commands:
list (builtin) List available commands
help (builtin) Show help for a command
version (builtin) Show run version
hello
show help for hello command
$ run help hello
hello: no help available.
invoke hello command
$ run hello
Hello, world
Run accepts the following pattern for command names:
alpha ::= 'a' .. 'z' | 'A' .. 'Z'
digit ::= '0' .. '9'
CMD_NAME ::= [ alpha | '_' ] ( [ alpha | digit | '_' | '-' ] )*
Some examples:
hello
hello_world
hello-world
HelloWorld
When registering commands, run treats the command name as case-insensitive and subject to command override rules.
case-insensitive override example
For example, run will generate an error if a command name is defined multiple times in the same runfile, even if the names use different cases:
Runfile
hello-world:
echo "Hello, world"
HELLO-WORLD:
echo "HELLO, WORLD"
list commands
$ run list
run: Runfile: command hello-world defined multiple times in the same file: lines 1 and 4
When invoking commands, run treats the command name as case-insensitive:
Runfile
Hello-World:
echo "Hello, world"
output
$ run Hello-World
$ run Hello-world
$ run hello-world
Hello, world
When displaying help text, run displays command names as they are originally defined:
list commands
$ run list
Commands:
...
Hello-World
...
show help for Hello-World command
$ run help hello-world
Hello-World: no help available.
We can add a simple title to our command, providing some help content.
Runfile
## Hello world example.
hello:
echo "Hello, world"
output
$ run list
Commands:
list (builtin) List available commands
help (builtin) Show help for a command
version (builtin) Show run version
hello Hello world example.
...
$ run help hello
hello:
Hello world example.
We can further flesh out the help content by adding a description.
Runfile
##
# Hello world example.
# Prints "Hello, world".
hello:
echo "Hello, world"
output
$ run list
Commands:
list (builtin) List available commands
help (builtin) Show help for a command
version (builtin) Show run version
hello Hello world example.
...
$ run help hello
hello:
Hello world example.
Prints "Hello, world".
Positional arguments are passed through to your command script.
Runfile
##
# Hello world example.
hello:
echo "Hello, ${1}"
output
$ run hello Newman
Hello, Newman
You can configure command-line options and access their values with environment variables.
Runfile
##
# Hello world example.
# Prints "Hello, <name>".
# OPTION NAME -n,--name <name> Name to say hello to
hello:
echo "Hello, ${NAME}"
output
$ run help hello
hello:
Hello world example.
Prints "Hello, <name>".
Options:
-h, --help
Show full help screen
-n, --name <name>
Name to say hello to
$ run hello --name=Newman
$ run hello -n Newman
Hello, Newman
You can use !
to indicate that an option is required:
# OPTION NAME! -n,--name <name> Name to say hello to
Required options will be indicated in help text:
-n, --name <name> (required)
Name to say hello to
An error will be generated if a required option is not provided:
hello: ERROR: Missing required option:
-n, --name <name>
Name to say hello to
Although options are already optional by default, you can use ?
to explicitly indicate that an option is optional:
# OPTION NAME? -n,--name <name> Name to say hello to
NOTE: This exists mostly for parity with !
and behaves the same as when it is not used
You can use ?=
to specify a default value for an option, which will be used if the option is not provided:
# OPTION NAME ?= Newman -n,--name <name> Name to say hello to
output
$ run hello
Hello, Newman
Note: Any standard variable assignment value can be used (quoted strings, variable references, etc)
Default values will be indicated in help text:
-n, --name <name> (default: Newman)
Declare flag options by omitting the '<...>'
segment.
Runfile
##
# Hello world example.
# OPTION NEWMAN --newman Say hello to Newman
hello:
NAME="World"
[[ -n "${NEWMAN}" ]] && NAME="Newman"
echo "Hello, ${NAME}"
output
$ run help hello
hello:
Hello world example.
...
--newman
Say hello to Newman
You can specify a default value for boolean options, but they behave slightly different from standard options:
# OPTION NEWMAN ?= enabled --newman Say hello to Newman
The content of the default value text is not used to determine the option's default true/false value.
Why?
Since boolean values are already always false
by default, providing a "default value" can only have the effect of defaulting the value to true
.
output
$ run hello
Hello, Newman
Even though a boolean option with provided default is always assumed to default to true, the default value text is still useful in that it will be displayed in the help text:
--newman (default: enabled)
This allows you to give better messaging than just "true" or "1" (i.e "enabled" in this example)
$ run help --newman=true # true | True | TRUE
$ run help --newman=1 # 1 | t | T
$ run help --newman # Empty value = true
$ run help # Default value = true if option has ?=
Hello, Newman
$ run help --newman=false # false | False | FALSE
$ run help --newman=0 # 0 | f | F
$ run help # Default value = false if option does not have ?=
Hello, World
If your command defines one or more options, but does not explicitly configure options -h
or --help
, then they are automatically registered to display the command's help text.
Runfile
##
# Hello world example.
# Prints "Hello, world".
hello:
echo "Hello, world"
output
$ run hello -h
$ run hello --help
hello:
Hello world example.
Prints "Hello, world".
If your command does not define any options within the Runfile, then run will pass all command line arguments directly through to the command script.
Runfile
##
# Echo example
# Prints the arguments passed into the script
#
echo:
echo script arguments = "${@}"
output
$ run echo -h --help Hello Newman
script arguments = -h --help Hello Newman
NOTE: As you likely surmised, help options (-h
& --help
) are not automatically registered when the command does not define any other options.
If your command script does define one or more options within the Runfile, you can still pass options directly through to the command script, but the syntax is a bit different:
Runfile
##
# Echo example
# Prints the arguments passed into the script
# Use -- to separate run options from script options
# OPTION ARG -a <arg> Contrived argument
#
echo:
echo ARG = "${ARG}"
echo script arguments = "${@}"
output
$ run echo -a my-arg -- -h --help Hello Newman
ARG = my-arg
script arguments = -h --help Hello Newman
Notice the '--'
in the argument list - Run will stop parsing options when it encounters the '--'
and pass the rest of the arguments through to the command script.
Invoking -h
or --help
with no command shows the help page for the run tool itself.
$ run --help
Usage:
run <command> [option ...]
(run <command>)
or run list
(list commands)
or run help <command>
(show help for <command>)
Options:
-r, --runfile <file>
Specify runfile (default='${RUNFILE:-Runfile}')
ex: run -r /my/runfile list
Note:
Options accept '-' | '--'
Values can be given as:
-o value | -o=value
Flags (booleans) can be given as:
-f | -f=true | -f=false
Short options cannot be combined
You can specify a runfile using the -r | --runfile
option:
$ run --runfile /path/to/my/Runfile <command>
NOTE: When specifying a runfile, the file does not have to be named "Runfile"
.
You can specify a runfile using the $RUNFILE
environment variable:
$ export RUNFILE="/path/to/my/Runfile"
$ run <command>
For some other interesting uses of $RUNFILE
, see:
NOTE: When specifying a runfile, the file does not have to be named "Runfile"
.
You can instruct run to look up the directory path in search of a runfile.
You do this using the $RUNFILE_ROOTS
path variable.
$RUNFILE_ROOTS
is treated as a list of path entries (using standard os path separator)- Behaves largely similar to GIT_CEILING_DIRECTORIES
- If
$PWD
is a child of a root entry, then run will walk up the folder hierarchy, checking each folder for the currently-configured Runfile. - Roots themselves are generally treated as exclusive (ie not checked)
$HOME
, if a configured root, is treated as inclusive (ie it is checked)
general usage
export RUNFILE_ROOTS="${HOME}" # Will walk up to $HOME (inclusively)
most permissive
export RUNFILE_ROOTS="/" # Will walk up to / (exclusively)
NOTE: $HOME
is given special treatment to support the case where a project is given its own user account and the Runfile lives in the home folder of that user.
For the case of creating globally available tasks, see the Special Modes section.
You can define variables within your runfile:
Runfile
NAME := "Newman"
##
# Hello world example.
# Tries to print "Hello, ${NAME}"
hello:
echo "Hello, ${NAME:-world}"
By default, variables are local to the runfile and are not part of your command's environment.
For example, you can access them within your command's description:
$ run help hello
hello:
Hello world example.
Tries to print "Hello, Newman"
But not within your commands script:
$ run hello
Hello, world
To make a variable available to your command script, you need to export
it:
Runfile
EXPORT NAME := "Newman"
##
# Hello world example.
# Tries to print "Hello, ${NAME}"
hello:
echo "Hello, ${NAME:-world}"
output
$ run hello
Hello, Newman
You can create variables on a per-command basis:
Runfile
##
# Hello world example.
# Prints "Hello, ${NAME}"
# EXPORT NAME := "world"
hello:
echo "Hello, ${NAME}"
help output
$ run help hello
hello:
Hello world example.
Prints "Hello, world"
command output
$ run hello
Hello, world
You can export previously-defined variables by name:
Runfile
HELLO := "Hello"
NAME := "world"
##
# Hello world example.
# EXPORT HELLO, NAME
hello:
echo "${HELLO}, ${NAME}"
You can declare exported variables before they are defined:
Runfile
EXPORT HELLO, NAME
HELLO := "Hello"
NAME := "world"
##
# Hello world example.
hello:
echo "${HELLO}, ${NAME}"
If you export a variable, but don't define it, you will get a WARNING
Runfile
EXPORT HELLO, NAME
NAME := "world"
##
# Hello world example.
hello:
echo "Hello, ${NAME}"
output
$ run hello
run: WARNING: exported variable not defined: 'HELLO'
Hello, world
You can reference other variables within your assignment:
Runfile
SALUTATION := "Hello"
NAME := "Newman"
EXPORT MESSAGE := "${SALUTATION}, ${NAME}"
##
# Hello world example.
hello:
echo "${MESSAGE}"
You can invoke sub-shells and capture their output within your assignment:
Runfile
SALUTATION := "Hello"
NAME := "$( echo 'Newman )" # Trivial example
EXPORT MESSAGE := "${SALUTATION}, ${NAME}"
##
# Hello world example.
hello:
echo "${MESSAGE}"
You can conditionally assign a variable, which only assigns a value if one does not already exist.
Runfile
EXPORT NAME ?= "world"
##
# Hello world example.
hello:
echo "Hello, ${NAME}"
example with default
$ run hello
Hello, world
example with override
NAME="Newman" run hello
Hello, Newman
Attributes are special variables used by the Run engine.
Their names start with .
to avoid colliding with runfile variables and environment variables.
Following is the list of Run's attributes:
Attribute | Description |
---|---|
.SHELL |
Contains the shell command that will be used to execute command scripts. See Script Shells for more details. |
.RUN |
Contains the absolute path of the run binary currently in use. Useful for Invoking Other Commands & Runfiles. |
.RUNFILE |
Contains the absolute path of the primary Runfile. |
.RUNFILE.DIR |
Contains the absolute path of the parent folder of the primary runfile. |
.SELF |
Contains the absolute path of the current (primary or included) runfile. |
.SELF.DIR |
Contains the absolute path of the parent folder of the current runfile. |
In order to access an attribute's value within your commands, you'll need to assign them to an exported variable.
Older versions of Run required you to use a variable assignment:
Runfile
EXPORT RUNFILE := ${.RUNFILE}
EXPORT RUNFILE_DIR := ${.RUNFILE.DIR}
## Prints the value of .RUNFILE
runfile:
echo "${RUNFILE}"
## Prints the value of .RUNFILE.DIR
runfile-dir:
echo "${RUNFILE_DIR}"
Newer versions of Run now support less verbose options:
You can quickly export an attribute with a default variable name:
Runfile
EXPORT .RUNFILE, .RUNFILE.DIR
## Prints the value of .RUNFILE
runfile:
echo "${RUNFILE}"
## Prints the value of .RUNFILE.DIR
runfile-dir:
echo "${RUNFILE_DIR}"
With this technique, Run uses the attribute's name to determine the exported variable's name by:
- Removing the leading
.
character - Substituting any remaining
.
characters with_
If you want to export an attribute with a non-default variable name, you can use the AS
syntax:
EXPORT .RUNFILE AS RF
EXPORT .RUNFILE.DIR AS RFD
## Prints the value of .RUNFILE
runfile:
echo "${RF}"
## Prints the value of .RUNFILE.DIR
runfile-dir:
echo "${RFD}"
Assertions let you check against expected conditions, exiting with an error message when checks fail.
Assertions have the following syntax:
ASSERT <condition> [ "<error message>" | '<error message>' ]
Note: The error message is optional and will default to "assertion failed"
if not provided
The following condition patterns are supported:
[ ... ]
[[ ... ]]
( ... )
(( ... ))
Note: Run does not interpret the condition. The condition text will be executed, unmodified (including surrounding braces/parens/etc), by the configured shell. Run will inspect the exit status of the check and pass/fail the assertion accordingly.
Here's an example that uses both global and command-level assertions:
Runfile
##
# Not subject to any assertions
world:
echo Hello, World
# Assertion applies to ALL following commands
ASSERT [ -n "${HELLO}" ] "Variable HELLO not defined"
##
# Subject to HELLO assertion, even though it doesn't use it
newman:
echo Hello, Newman
##
# Subject to HELLO assertion, and adds another
# ASSERT [ -n "${NAME}" ] 'Variable NAME not defined'
name:
echo ${HELLO}, ${NAME}
example with no vars
$ run world
Hello, World
$ run newman
run: ERROR: Runfile:7: Variable HELLO not defined
$ run name
run: ERROR: Runfile:7: Variable HELLO not defined
example with HELLO
$ HELLO=Hello run newman
Hello, Newman
$ HELLO=Hello run name
run: ERROR: Runfile:16: Variable NAME not defined
example with HELLO and NAME
$ HELLO=Hello NAME=Everybody run name
Hello, Everybody
Note: Assertions apply only to commands and are only checked when a command is invoked. Any globally-defined assertions will apply to ALL commands defined after the assertion.
Includes let you organize and configure commands across multiple Runfiles.
You can include other Runfiles using the following syntax:
INCLUDE <file pattern> | "<file pattern>" | '<file pattern>'
Simple example:
file layout
Runfile
Runfile-hello
Runfile
INCLUDE Runfile-hello
Runfile-hello
hello:
echo "Hello from Runfile-hello"
output
$ run hello
Hello from Runfile-hello
Run utilizes goreleaser/fileglob in order support file globbing for includes.
According to their README, fileglob
supports:
- Asterisk wildcards (
*
) - Super-asterisk wildcards (
**
) - Single symbol wildcards (
?
) - Character list matchers with negation and ranges (
[abc]
,[!abc]
,[a-c]
) - Alternative matchers (
{a,b}
) - Nested globbing (
{a,[bc]}
) - Escapable wildcards (
\{a\}/\*
)
Fileglob Example:
file layout
Runfile
1/1/Runfile-1
2/2/Runfile-2
3/3/Runfile-3
Runfile
INCLUDE **/Runfile-*
Include names / glob-patterns are resolved relative to the Primary runfile's containing directory.
When using a globbing pattern, Run considers it OK if the pattern results in no files being found.
This makes it possible to support features like an optional Runfile include directory, or the ability to start a project folder with no includes but have them automatically picked up as you add them.
Runfile
INCLUDE maybe_some_runfiles/Runfile-* # OK if no files found
To force an error if no files are found when using a globbing pattern, use !
:
Runfile
INCLUDE ! maybe_some_runfiles/Runfile-* # ERROR if no files found
When using a single filename (no globbing), Run considers it an error if the include file is not found.
Runfile
INCLUDE Runfile-must-exist # Errors if file not found
output
$ run list
run: include runfile not found: 'Runfile-must-exist'
To skip generating an error if no file is found when using a single filename, use ?
:
Runfile
INCLUDE ? Runfile-might-exist # OK if file not found
Run keeps track of already-included runfiles and will silently avoid including the same runfile multiple times.
Runfile
INCLUDE Runfile-hello
INCLUDE Runfile-hello # Silently skipped
Run allows you override commands, as long as they were originally registered in a different Runfile.
Runfile
## defined in Runfile
command1:
echo command1 from Runfile
INCLUDE Runfile-include
## defined in Runfile
command2:
echo command2 from Runfile
Runfile-include
## defined in Runfile-include
command1:
echo command1 from Runfile-include
## defined in Runfile-include
command2:
echo command2 from Runfile-include
list commands
$ run list
Commands:
...
command1 defined in Runfile-include
command2 defined in Runfile
Notice that the included runfile overrides command1
, but the primary runfile overrides command2
.
Run will error when attempting to register a command multiple times within the same Runfile:
Runfile
hello-world:
echo "Hello, world"
hello-world:
echo "Hello, world"
list commands
$ run list
run: Runfile: command hello-world defined multiple times in the same file: lines 1 and 4
Run's override matching is case-insensitive:
Runfile
## defined in Runfile
command1:
echo command1 from Runfile
include Runfile-include
Runfile-include
## defined in Runfile-include
COMMAND1:
echo command1 from Runfile-include
list commands
$ run list
Commands:
...
command1 defined in Runfile-include
Notice that COMMAND1
from the included runfile overrides command1
from the primary runfile.
Run keeps track of the original case used when a command is first registered, and uses it when displaying help:
Runfile
## defined in Runfile
COMMAND1:
echo command1 from Runfile
include Runfile-include
Runfile-include
## defined in Runfile-include
command1:
echo command1 from Runfile-include
list commands
$ run list
Commands:
...
COMMAND1 defined in Runfile-include
Notice that the displayed name comes from the original registration in the primary runfile.
Run keeps track of the title & description when a command is first registered, and uses it if an overriding command does not define its own documentation:
Runfile
## title defined in Runfile
command1:
echo command1 from Runfile
include Runfile-include
Runfile-include
command1:
echo command1 from Runfile-include
list commands
$ run list
Commands:
...
commmand1 title defined in Runfile
command output
$ run command1
command1 from Runfile-include
Notice that, even though command1
from the included runfile was invoked, the displayed title comes from the original registration in the primary runfile.
Run keeps track of the order in which commands are registered, and maintains that order even if a command is later overridden:
Runfile
## defined in Runfile
command1:
echo command1 from Runfile
## defined in Runfile
command2:
echo command2 from Runfile
## defined in Runfile
command3:
echo command3 from Runfile
include Runfile-include
Runfile-include
## defined in Runfile-include
command2:
echo command2 from Runfile-include
list commands
$ run list
Commands:
...
command1 defined in Runfile
command2 defined in Runfile-include
command3 defined in Runfile
Notice that command2
is still shown between command1
and command3
, matching the order in which it was originally registered.
.env
files allow users to manage runfile configuration without modifying the Runfile directly.
Your Runfile can include .env files using the following syntax:
INCLUDE.ENV <file pattern> | "<file pattern>" | '<file pattern>'
Simple example:
Runfile.env
HELLO=Newman
Runfile
INCLUDE.ENV Runfile.env
##
# export HELLO
hello:
echo "Hello, ${HELLO:-World}"
output
$ run hello
Hello, Newman
Notes:
- Variables are immediately available, as if they had been defined in the same place in the Runfile.
- Variables are not automatically exported.
- Run uses the subosito/gotenv library to parse command output
#
comments are supported and will be safely ignoredexport
keyword is optional and is (currently) ignored - This may be addressed in a future release- Simple variable references in assignments are supported, but variables defined within your Runfile are not (currently) accessible - This may be addressed in a future release
- Visit the gotenv project page to learn more about which
.env
features are supported
By default, Run considers it OK no .env file is found (using either a single filename or a globbing pattern).
To force an error if no file(s) are found, use !
:
Runfile
INCLUDE.ENV ! Runfile-might-not-exist.env # ERROR if no file(s) found
You can invoke other commands (with arguments) from your Runfile before or after your command executes:
Runfile
##
# RUN hello "Newman"
# RUN.AFTER goodbye
test:
echo "How are you?"
hello:
echo "Hello, ${1:-World}"
goodbye:
echo "Goodbye, now"
output
$ run test
Hello, Newman
How are you?
Goodbye, now
Note: Any standard variable assignment value can be used (quoted strings, variable references, etc)
Your command's exported environment variables are also exported to the invoked command:
exported variable example
##
# EXPORT NAME := "Newman"
# RUN hello
test:
echo "Goodbye, now"
hello:
echo "Hello, ${NAME:-world}"
output
$ run test
Hello, Newman
Goodbye, now
Notes:
RUN.BEFORE
is also supported, and behaves just likeRUN
- Commands are invoked in the order they are defined
- Your command only runs if all previous RUN commands return exit code zero (0)
- After commands only run if your command returns exit code zero (0)
- Execution halts if any RUN returns a non-zero exit code
- You cannot invoke builtin commands (help, version, etc)
A common occurrence in Runfiles is to have a central command which computes a set of variables, which is then invoked by multiple other commands that need to use those variables:
eval example
##
# export .RUN, .RUNFILE
test:
eval $( "$RUN" newman )
echo "Hello, ${HELLO:-World}"
## Generates script suitable for 'eval' by caller
newman:
echo "HELLO=Newman"
This technique works well, but Run also supports a similar feature using RUN.ENV
:
run.env example
##
# RUN.ENV newman
# ASSERT [ -n "${HELLO}" ] "HELLO not defined"
test:
echo "Hello, ${HELLO:-World}"
## Generates output compatible with simplified .env assignments
newman:
echo "# Let's say hi to Newman"
echo "export HELLO=Newman"
output
$ run newman
# Let's say hi to Newman
export HELLO=Newman
$ run test
Hello, Newman
Notes:
RUN.ENV
commands are run after EXPORTSRUN.ENV
commands are run before ASSERTS- Commands invoked via
RUN.ENV
are expected to generate relatively simple variable assignments - Run uses the subosito/gotenv library to parse command output
#
comments are supported and will be safely ignoredexport
keyword is optional and will be safely ignored- Simple variable references in assignments are supported, but variables defined within your Runfile are not (currently) accessible - This may be addressed in a future release
- Visit the gotenv project page to learn more about which
.env
features are supported
If you need more control while invoking other commands, Run makes it possible to invoke commands, or even other Runfiles, from within your command script.
Run exposes the following attributes:
.RUN
- Absolute path of the run binary currently in use.RUNFILE
- Absolute path of the current primary Runfile
NOTE: Even from inside an included Runfile, .RUNFILE
will always reference the primary Runfile
Your command script can use these to invoke other commands:
Runfile
##
# EXPORT .RUN, .RUNFILE
test:
"${RUN}" hello
hello:
echo "Hello, World"
output
$ run test
Hello, World
Hidden / Private Commands
Hidden Commands
You can mark a command as Hidden using a leading .
:
hidden command example
##
# Prints 'Hello, Newman', then 'Goodbye, now'
# RUN hello Newman
test:
echo "Goodbye, now"
## Hello command is hidden
.hello:
echo "Hello, ${1:-world}"
Hidden commands don't show up when listing commands:
list commands
$ run list
Commands:
...
test Prints 'Hello, Newman', then 'Goodbye, now'
But they can still be invoked by using their full name, with .
:
run hidden command
$ run .hello
Hello, world
You can mark a command as Private using a leading !
:
private command example
##
# Prints 'Hello, Newman', then 'Goodbye, now'
# RUN hello Newman
test:
echo "Goodbye, now"
## Hello command is private
!hello:
echo "Hello, ${1:-world}"
Private commands don't show up when listing commands:
list commands
$ run list
Commands:
...
test Prints 'Hello, Newman', then 'Goodbye, now'
And they cannot be invoked from outside the Runfile:
try to run private command
$ run hello
run: command not found: hello
$ run '!hello'
run: command not found: !hello
Run's default shell is 'sh'
, but you can specify other shells.
All the standard shells should work.
Each command can specify its own shell:
##
# Hello world example.
# NOTE: Requires ${.SHELL}
hello (bash):
echo "Hello, world"
You can set the default shell for the entire runfile:
Runfile
# Set default shell for all actions
.SHELL = bash
##
# Hello world example.
# NOTE: Requires ${.SHELL}
hello:
echo "Hello, world"
You can even specify executors that are not technically shells.
Runfile
## Hello world python example.
hello (python):
print("Hello, world from python!")
Run executes scripts using the following command:
/usr/bin/env $SHELL $TMP_SCRIPT_FILE [ARG ...]
Any executor that is on the PATH
, can be invoked via env
, and takes a filename as its first argument should work.
Run allows you to define custom #!
lines in your command script:
Here's an example of running a c
program from a shell script using a custom #!
header:
Runfile
##
# Hello world c example using #! executor.
# NOTE: Requires gcc
hello:
#!/usr/bin/env sh
sed -n -e '7,$p' < "$0" | gcc -x c -o "$0.$$.out" -
$0.$$.out "$0" "$@"
STATUS=$?
rm $0.$$.out
exit $STATUS
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello, world from c!\n");
return 0;
}
NOTE: The #!
executor does not use /user/bin/env
to invoke your script. Instead, it attempts to make the temporary script file executable then invoke it directly.
You can use a #
on the first column of a command script to ignore a line:
Runfile
hello:
# This comment WILL be present in the executed command script
echo "Hello, Newman"
# This comment block WILL NOT be present in the executed command script
# echo "Hello, World"
echo "Goodbye, now"
Note: Run detects and skips these comment lines when parsing the runfile, so the #
will work regardless of what language the script text is written in (i.e even if the target language doesn't support #
for comments).
In shebang mode
, you make your runfile executable and invoke commands directly through it:
runfile.sh
#!/usr/bin/env run shebang
## Hello example using shebang mode
hello:
echo "Hello, world"
output
$ chmod +x runfile.sh
$ ./runfile.sh hello
Hello, world
In shebang mode, the runfile filename replaces references to the run
command:
shebang mode help example
$ ./runfile.sh help
Usage:
runfile.sh <command> [option ...]
(run <command>)
or runfile.sh list
(list commands)
or runfile.sh help <command>
(show help for <command>)
...
shebang mode list example
$ ./runfile.sh list
Commands:
list (builtin) List available commands
help (builtin) Show help for a command
run-version (builtin) Show run version
hello Hello example using shebang mode
In shebang mode, the version
command is renamed to run-version
. This enables you to create your own version
command, while still providing access to run's version info, if needed.
runfile.sh
#!/usr/bin/env run shebang
## Show runfile.sh version
version:
echo "runfile.sh v1.2.3"
## Hello example using shebang mode
hello:
echo "Hello, world"
shebang mode version example
$ ./runfile.sh list
...
run-version (builtin) Show Run version
version Show runfile.sh version
...
$ ./runfile.sh version
runfile.sh v1.2.3
$ ./runfile.sh run-version
runfile.sh is powered by run v0.0.0. learn more at https://github.com/TekWizely/run
In main mode you use an executable runfile that consists of a single command, aptly named main
:
runfile.sh
#!/usr/bin/env run shebang
## Hello example using main mode
main:
echo "Hello, world"
In this mode, run's built-in commands are disabled and the main
command is invoked directly:
output
$ ./runfile.sh
Hello, world
In main mode, the runfile filename replaces references to command
name:
main mode help example
$ ./runfile.sh --help
runfile.sh:
Hello example using main mode
In main mode, help options (-h
& --help
) are automatically configured, even if no other options are defined.
This means you will need to use --
in order to pass options through to the main script.
A nice hack to make executing run tasks within your project more convenient is to use direnv to autoconfigure the $RUNFILE
environment variable:
create + edit + activate rc file
$ cd ~/my-project
$ direnv edit .
edit .envrc
export RUNFILE="${PWD}/Runfile"
Save & exit. This will activate immediately but will also activate whenever you cd
into your project's root folder.
$ cd ~/my-project
direnv: export +RUNFILE
verify
$ echo $RUNFILE
/home/user/my-project/Runfile
With this, you can execute run <cmd>
from anywhere in your project.
Bingo makes it easy to install (and update) golang apps directly from source:
install
$ bingo install github.com/TekWizely/run
update
$ bingo update run
See the Releases page as recent releases are accompanied by pre-compiled binaries for various platforms.
Run currently uses goreleaser to generate release assets.
Feel free to open an issue to discuss additional target platforms, or even create a PR against the .goreleaser.yml configuration.
Run is now available on homebrew core:
install run via brew core
$ brew install run
In addition to being available in brew core, I have also created a tap to ensure the latest version is always available:
install run directly from tap
$ brew install tekwizely/tap/run
install tap to track updates
$ brew tap tekwizely/tap
$ brew install run
For Nix users, a package is available on nixpkgs:
Supported Platforms:
- x86_64-darwin
- aarch64-darwin
- aarch64-linux
- i686-linux
- x86_64-linux
install run on NixOS
$ nix-env -iA nixos.run
install run on non-NixOs
$ nix-env -iA nixpkgs.run
For Archlinux users, a package is available on the AUR:
install run from AUR using yay
$ yay -S run-git
NPM & Yarn users can install run via the @tekwizely/run
package:
$ npm i '@tekwizely/run'
$ yarn add '@tekwizely/run'
I hope to have other packages available soon and will update the README as they become available.
To contribute to Run, follow these steps:
- Fork this repository.
- Create a branch:
git checkout -b <branch_name>
. - Make your changes and commit them:
git commit -m '<commit_message>'
- Push to the original branch:
git push origin <project_name>/<location>
- Create the pull request.
Alternatively see the GitHub documentation on creating a pull request.
If you want to contact me you can reach me at TekWize.ly@gmail.com.
The tekwizely/run
project is released under the MIT License. See LICENSE
file.
If you happened to find this project on your quest for bash-specific arg parsing solutions, I found this fantastic S/O post with many great suggestions:
Thanks goes to these wonderful people (emoji key):
chabad360 π π π |
Dawid Dziurla π |
Bob "Wombat" Hogg π |
Gys π |
Robin Burchell π» |
This project follows the all-contributors specification. Contributions of any kind welcome!