Executors
Executors are the engines responsible for executing each item of the sequence of command lines defined in your command. Yes, too many words. Let's clarify that. By default, the command lines will be executed by the Command-line executor. However, you can add a prefix to the line, and a different executor will run it.
Let's explore all the possibilities and understand each one of them.
Command-line Executor
The Command-line Executor is the most basic one. It'll execute the instructions in your terminal. It doesn't require any prefixes and supports any command-line instruction you can use on your favorite terminal.
Before executing each instruction, the variables will be interpolated.
1...
2 {
3 "name": "command",
4 "execute": [
5 "echo 'create directory ${dir}'",
6 "mkdir ${dir}",
7 "cat files.txt | xargs -I{} bash -c 'echo {} > ${dir}/{}.txt'"
8 ],
9 ...
10 }
11...For each instruction it executes, its output will be printed to the standard output, and the errors will be printed to the error output.
If you don't want the output printed, please check the Nout Executor.
Set Executor
The set executor is responsible for adding variables or changing their values. It has the set: prefix, allowing you to define one or multiple variables.
1set:<VARIABLE NAME>=<VARIABLE VALUE>
2set:<VARIABLE1>=<VALUE1>;<VARIABLE2>=<VALUE2>;...Here are some examples:
1...
2 {
3 "name": "command",
4 "execute": [
5 "set:name=John Doe",
6 "set:firstName=John;lastName=Doe",
7 "set:fullName='${firstName} ${lastName}'",
8 "echo 'Hello, ${fullName}!'"
9 ],
10 ...
11 }
12...You can set the value from some instruction output. Just use an exclamation mark after the equals sign to indicate that it is a command line instruction.
1...
2 {
3 "name": "command",
4 "execute": [
5 "set:addresses=!cat addresses.txt",
6 ...
7 ],
8 ...
9 }
10...You can also parse a JSON string into a navigable object using the json: prefix. This allows you to access nested fields with dot notation and array indexing:
1...
2 {
3 "name": "command",
4 "execute": [
5 "set:person=json:${data}",
6 "log:${person.name} is ${person.age}"
7 ],
8 ...
9 }
10...1> aux4 command --data '{"name":"Alice","age":30}'1Alice is 30You can also build an object field by field using dot notation on the left side of the set:. Each assignment sets a field on the object, creating the base object (and any intermediate objects) automatically if it doesn't exist yet. Existing fields are preserved:
1...
2 {
3 "name": "command",
4 "execute": [
5 "set:person.name=Alice",
6 "set:person.age=json:30",
7 "set:person.address.city=NYC",
8 "log:${person.name} is ${person.age} and lives in ${person.address.city}"
9 ],
10 ...
11 }
12...1> aux4 command1Alice is 30 and lives in NYCA bare value is stored as a string (person.age would be "30"); use the json: prefix, as shown above, when you need a number, boolean, array, or object as the field value.
This executor doesn't print anything to the console output.
Log Executor
The log executor only prints to the standard output. It has the log: prefix and it also interpolates the variables. It works like the echo command, but it has a huge benefit. It doesn't require aux4 to request the OS to execute an external command. It runs directly, so it's faster than using echo.
1...
2 {
3 "name": "create-dir",
4 "execute": [
5 ...
6 "log:the directory ${dir} was created successfully"
7 ],
8 ...
9 }
10...Debug Executor
The debug executor works very similarly to the log executor. The main difference is that output goes to the error output instead of the standard output. So, it shouldn't impact your command if you redirect the output to another file or command.
Also, the debug executor will print the message only if you define an environment variable AUX4_DEBUG=true.
1...
2 {
3 "name": "create-dir",
4 "execute": [
5 "debug:before creating ${dir}",
6 ...
7 "log:the directory ${dir} was created successfully",
8 "debug:after creating ${dir}"
9 ],
10 ...
11 }
12...When you run without the environment variable, it prints just the standard output.
1> aux4 create-dir --dir test1the directory test was created successfullyHowever, when the environment variable AUX4_DEBUG is true, it'll include the debug messages.
1> export AUX4_DEBUG=true1> aux4 create-dir --dir test1before creating test
2the directory test was created successfully
3after creating testNout Executor
Nout stands for No Output. The name says everything, it doesn't print the output of that command-line. But it still saves the output to the response variable, in case you need to use it later.
1...
2 {
3 "name": "list-addresses",
4 "execute": [
5 "nout:cat addresses.txt",
6 ...
7 ],
8 ...
9 }
10...Redirect Shortcuts
Inside a command line you can discard a command's output with three readable shortcuts instead of raw shell redirections. Unlike the Nout Executor — which silences a whole execute step and keeps the output in the response variable — these operate on a single command, so you can silence one command in a compound line while the rest runs normally.
| shortcut | shell equivalent | effect |
|---|---|---|
| >ignore | >/dev/null 2>&1 | discard everything the command prints |
| >ignoreError | 2>/dev/null | discard only the error stream (stderr) |
| >ignoreOutput | >/dev/null | discard only the normal output (stdout) |
1...
2 {
3 "name": "reschedule",
4 "execute": [
5 "aux4 cron remove --name job >ignore; aux4 cron add --name job --every '1 day'"
6 ],
7 ...
8 }
9...The remove may fail if the job was never scheduled — >ignore hides its output and error so the line reads cleanly and continues to add.
They are only recognized as standalone tokens, so a real redirect such as 2>errors.log, or a file literally named >ignore.log, is left untouched.
Confirm Executor
The confirm executor prompts a question to the user and requires their confirmation (yes or no).
1...
2 {
3 "name": "user-action",
4 "execute": [
5 "confirm:Are you sure",
6 "log:The user is confident, we can execute"
7 ],
8 ...
9 }
10...When you run the command, it will prompt the message.
1> aux4 user-action1? Are you sure? [y/N] |1Are you sure: y
2The user is confident, we can execute itIn case the user says no.
1✗ Are you sure:
2User abortedExit Executor
The exit executor stops the command immediately with a given exit code. It has the exit: prefix and the form exit:<code>:<message>.
1exit:<code>:<message>- A non-zero code stops the command and prints the message in red to standard error.
- A zero code is a clean early stop: it prints the message to standard output and exits successfully.
- The message is optional (exit:1 exits silently), and a non-numeric argument becomes the message with the default code 1 (exit:something failed). Colons in the message are preserved, and variables are interpolated.
It is most useful together with the when executor to guard a command:
1...
2 {
3 "name": "send",
4 "execute": [
5 "when:${email}==:exit:1:email is required",
6 "log:sending to ${email}"
7 ],
8 ...
9 }
10...1> aux4 send1email is requiredThe command stops with exit code 1 and never reaches the log: step. When --email is provided, the condition is false and execution continues.
JSON Executor
The JSON executor will parse the output of the instruction to JSON and store it in the response variable, which you can use as an object or array.
1...
2 {
3 "name": "command",
4 "execute": [
5 "json:cat addresses.json",
6 "echo '${response[0].state} ${response[0].zipCode}'"
7 ],
8 ...
9 }
10...This executor doesn't print any output.
Each Executor
The each executor will iterate over the output of the previous instruction. If the response variable is a string, it will iterate over the lines. If the response variable is an array, it will iterate over the elements of that array.
1...
2 {
3 "name": "command",
4 "execute": [
5 "json:cat addresses.json",
6 "each:echo '${item.state} ${item.zipCode}'"
7 ],
8 ...
9 }
10...If the response is a string, it will iterate over the lines. You can also use the index variable to get the index of the item.
1...
2 {
3 "name": "command",
4 "execute": [
5 "cat addresses.txt",
6 "each:echo ${index} ${item}"
7 ],
8 ...
9 }
10...In case some of the lines you are iterating over will produce some error, you can use the ignoreErrors variable to ignore them.
1...
2 {
3 "name": "command",
4 "execute": [
5 "set:ignoreErrors=true",
6 "cat list.txt",
7 "each:cat ${item}"
8 ],
9 ...
10 }Range Executor
The range executor generates an array of numbers and stores it in the response variable. It's especially useful with the each executor for iterating a fixed number of times.
- range:N — generates [0, 1, ..., N-1]
- range:X-Y — generates [X, X+1, ..., Y]
It supports variable interpolation.
1...
2 {
3 "name": "repeat",
4 "execute": [
5 "range:${n}",
6 "each:echo step ${item}"
7 ],
8 "help": {
9 "text": "iterate N times",
10 "variables": [
11 {
12 "name": "n",
13 "text": "number of iterations"
14 }
15 ]
16 }
17 }
18...1> aux4 repeat --n 31step 0
2step 1
3step 2With a start-end range:
1...
2 {
3 "name": "ports",
4 "execute": [
5 "range:8080-8083",
6 "each:echo checking port ${item}"
7 ],
8 "help": {
9 "text": "check port range"
10 }
11 }
12...1> aux4 ports1checking port 8080
2checking port 8081
3checking port 8082
4checking port 8083Profile Executor
The profile executor will redirect the following arguments to another profile. As we mentioned before, it's possible to have multiple profiles, but when you run aux4 it will list only the commands from the main profile. So you have to create a command that will be the trigger to switch to another profile.
- Main profile (lines 3-25)
- test command (lines 6-14)
- ops command (lines 15-23)
- Test profile (lines 26-39)
- run command (lines 29-37)
- Ops profile (lines 40-53)
- deploy command (lines 43-51)
1{
2 "profiles": [
3 {
4 "name": "main",
5 "commands": [
6 {
7 "name": "test",
8 "execute": [
9 "profile:test"
10 ],
11 "help": {
12 "text": "manage the tests"
13 }
14 },
15 {
16 "name": "ops",
17 "execute": [
18 "profile:ops"
19 ],
20 "help": {
21 "text": "manage the ops"
22 }
23 }
24 ]
25 },
26 {
27 "name": "test",
28 "commands": [
29 {
30 "name": "run",
31 "execute": [
32 "log:run tests"
33 ],
34 "help": {
35 "text": "execute the tests"
36 }
37 }
38 ]
39 },
40 {
41 "name": "ops",
42 "commands": [
43 {
44 "name": "deploy",
45 "execute": [
46 "log:deploy the app"
47 ],
48 "help": {
49 "text": "deploy the application"
50 }
51 }
52 ]
53 }
54 ]
55}If you run aux4 it will just list the commands from main.
1> aux41test
2manage the tests
3
4ops
5manage the opsWhen you switch the profile, it behaves the same as running aux4 but pointing to that profile instead of the main profile.
If you run aux4 test, it will switch to the test profile. Since it doesn't have a following argument, it will list the commands of the test profile.
1> aux4 test1run
2execute the testsHowever, if you run aux4 test run it will execute the run command from the test profile.
1> aux4 test run1run testsStdin Executor
The stdin executor will send the Standard Input (stdin) to the command-line instruction.
1...
2 {
3 "name": "get-first-item",
4 "execute": [
5 "stdin:jq '.[0]'"
6 ],
7 ...
8 }
9...When you execute you can redirect the output to the aux4 command.
1> cat array.json | aux4 get-first-item1{
2 "name": "john",
3 "age": 30
4}File Executor
The file executor writes content to a file. It has the file: prefix with the format file:<path>:<content>.
1...
2 {
3 "name": "save-config",
4 "execute": [
5 "file:config.txt:host=${host}\nport=${port}"
6 ],
7 ...
8 }
9...To append to a file instead of overwriting, prefix the content with +:
1...
2 {
3 "name": "log-event",
4 "execute": [
5 "file:events.log:+${timestamp} ${event}"
6 ],
7 ...
8 }
9...Both the path and content support variable interpolation. If the file doesn't exist, it will be created. Append mode (+) also creates the file if it doesn't exist.
This executor doesn't print anything to the console output.
Comment
You can add comments to your execute instructions by starting the line with #. Comments are ignored and not executed.
1...
2 {
3 "name": "hello",
4 "execute": [
5 "# This is a comment and should be ignored",
6 "log:hello world",
7 "# Another comment - this is also ignored",
8 "log:goodbye"
9 ],
10 ...
11 }
12...Comments are useful for documenting your command logic. Even if a comment contains variables like $name, it will still be ignored.
Alias Executor
The alias executor is used when you want to share the standard input/output/error, and the parameters with that instruction. For all the executors, the instructions will be executed in an isolated environment (with its own input/output/error), but the alias will execute it using the same environment you're using to execute the aux4 command.
Here are some examples. First we can use the same we used in the previous example for the stdin executor.
1...
2 {
3 "name": "get-first-item",
4 "execute": [
5 "alias:jq '.[0]'"
6 ],
7 ...
8 }
9...You also can redirect the input for that.
1> cat array.json | aux4 get-first-item1{
2 "name": "john",
3 "age": 30
4}In the next example, let's say we have two commands, one of them uses confirm executor (line 13). Note that this line uses three variables: name, env, and time.
The env variable is set in the line 5, by the set executor. The time variable is set in the line 6 by passing it as a parameter. The name variable is not defined anywhere, so it's expected as a parameter of execute command.
1...
2 {
3 "name": "execute",
4 "execute": [
5 "set:env=dev",
6 "alias:aux4 confirm --time today"
7 ],
8 ...
9 },
10 {
11 "name": "confirm",
12 "execute": [
13 "confirm:${name} are you sure you want to run it in ${env} ${time}",
14 "log:confirmed"
15 ],
16 "help": {
17 ...
18 "variables": [
19 {
20 "name": "name",
21 "text": "the name"
22 },
23 {
24 "name": "env",
25 "text": "the environment"
26 },
27 {
28 "name": "time",
29 "text": "the execution time"
30 }
31 ]
32 }
33 ...
34 }
35...When we run the execute command passing the name as parameter. You can see all the parameters are passed to the confirm command.
1> aux4 execute --name David1? David are you sure you want to run it in dev today? [y/N] |However, if you don't pass the name as parameter, the confirm command will prompt the user to input it.
1> aux4 execute1✔ name the name: |aux4 Executor
The aux4 executor runs another aux4 command in-process without spawning a new shell. Use the aux4: prefix or aux4 prefix. The command runs in the main profile.
1...
2 {
3 "name": "deploy",
4 "execute": [
5 "aux4:config get --name production",
6 "log:Deploying to ${response.host}"
7 ],
8 ...
9 }
10...This is faster than using the command-line executor to call aux4 because it doesn't create a new process.
When Executor
The when executor conditionally executes a command only if the condition matches. Unlike if() which always runs one branch (&& or ||), when: silently skips when the condition is not met.
1when:<condition>:<command>Equality
Use == and != to compare values:
1...
2 {
3 "name": "greet",
4 "execute": [
5 "when:${env}==prod:log:Running in production",
6 "when:${env}!=prod:log:Running in ${env}"
7 ],
8 ...
9 }
10...1> aux4 greet --env prod1Running in production1> aux4 greet --env dev1Running in devTruthy Check
When no operator is used, the condition checks if the value is non-empty:
1...
2 {
3 "name": "check",
4 "execute": [
5 "when:${value}:log:Value is ${value}",
6 "log:done"
7 ],
8 ...
9 }
10...1> aux4 check1done1> aux4 check --value hello1Value is hello
2doneNumeric Comparison
Use >, <, >=, <= for numeric comparisons:
1...
2 {
3 "name": "check-count",
4 "execute": [
5 "when:${count}>5:echo more than 5",
6 "when:${count}<=5:echo 5 or less"
7 ],
8 ...
9 }
10...1> aux4 check-count --count 101more than 5Logical Operators
Use && (AND) and || (OR) to combine conditions:
1...
2 {
3 "name": "check-range",
4 "execute": [
5 "when:${age}>=18 && ${age}<65:echo working age",
6 "when:${age}<18:echo minor",
7 "when:${age}>=65:echo senior"
8 ],
9 ...
10 }
11...1> aux4 check-range --age 301working age1...
2 {
3 "name": "check-mode",
4 "execute": [
5 "when:${mode}==debug || ${verbose}==true:log:Debug enabled"
6 ],
7 ...
8 }
9...Nested Executors
The command after the condition can use any executor prefix:
1...
2 {
3 "name": "setup",
4 "execute": [
5 "when:${mode}==set:set:greeting=hello world",
6 "when:${mode}==set:log:${greeting}"
7 ],
8 ...
9 }
10...1> aux4 setup --mode set1hello world