---
# System prepended metadata

title: EasyBuild 5.0 - new run function

---


### Goals

see also https://github.com/easybuilders/easybuild-framework/issues/4252

- one single `run` function that supports running (interactive) shell commands (incl. Lmod)
- sane API + defaults
- supports everything that current `run_cmd` and `run_cmd_qa` functions do
- enables new features like:
    - context-aware error parsing & reporting
    - wrapper functions for common commands like `run_make`, `run_cmake`, etc.
    - opt-in to jumping into an interactive shell session if command failed

### Current `run_cmd` and `run_cmd_qa` functions

```python
def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True, log_output=False, path=None,
            force_in_dry_run=False, verbose=True, shell=None, trace=True, stream_output=None, asynchronous=False):
    """
    Run specified command (in a subshell)
    :param cmd: command to run
    :param log_ok: only run output/exit code for failing commands (exit code non-zero)
    :param log_all: always log command output and exit code
    :param simple: if True, just return True/False to indicate success, else return a tuple: (output, exit_code)
    :param inp: the input given to the command via stdin
    :param regexp: regex used to check the output for errors;  if True it will use the default (see parse_log_for_error)
    :param log_output: indicate whether all output of command should be logged to a separate temporary logfile
    :param path: path to execute the command in; current working directory is used if unspecified
    :param force_in_dry_run: force running the command during dry run
    :param verbose: include message on running the command in dry run output
    :param shell: allow commands to not run in a shell (especially useful for cmd lists), defaults to True
    :param trace: print command being executed as part of trace output
    :param stream_output: enable streaming command output to stdout
    :param asynchronous: run command asynchronously (returns subprocess.Popen instance if set to True)
    """
```

```python
def run_cmd_qa(cmd, qa, no_qa=None, log_ok=True, log_all=False, simple=False, regexp=True, std_qa=None, path=None,
               maxhits=50, trace=True):
    """
    Run specified interactive command (in a subshell)
    :param cmd: command to run
    :param qa: dictionary which maps question to answers
    :param no_qa: list of patters that are not questions
    :param log_ok: only run output/exit code for failing commands (exit code non-zero)
    :param log_all: always log command output and exit code
    :param simple: if True, just return True/False to indicate success, else return a tuple: (output, exit_code)
    :param regexp: regex used to check the output for errors; if True it will use the default (see parse_log_for_error)
    :param std_qa: dictionary which maps question regex patterns to answers
    :param path: path to execute the command is; current working directory is used if unspecified
    :param maxhits: maximum number of cycles (seconds) without being able to find a known question
    :param trace: print command being executed as part of trace output
    """
```

### Designing `run` function

Default usage:

```python
run(cmd)
```

* exits with error on non-zero exit code
* always returns output (stdout+stderr combined) + exit code
    * always return named tuple as output value (less room for typos, immutable)
      ```python
      (output="all output", exit_code=0, stderr=None)
      ```
* always log output + exit code (only with `log.info`)

Example:

```python
res = run(cmd)
res.output
res.exit_code
```

Options:

* don't fail on non-zero exit code (`fail_on_error=True` by default)
* split stdout/stderr (`split_stderr=False` by default)
    * return value:
      ```python
      (output="stdout output", exit_code=0, stderr="errors + warnings")
      ```
* don't show command in terminal (for trace or dry run output) (`hidden=False` by default)
    * `hidden=True` is combo of `trace=False` + `verbose=False` in `run_cmd`
* input to be sent to stdin (`stdin=None` by default)
    * like `inp` in `run_cmd`
* also run command in dry run mode (`in_dry_run=False` by default)
    * like `force_in_dry_run` in `run_cmd`
* location to run command in (`work_dir=None` by default, implies current working directory)
    * like `path` in `run_cmd`
* do not run in shell (`shell=True` by default)
    * like `shell` in `run_cmd` (matches with `shell` in `subprocess.Popen`)
* enable logging of output to temporary file (`output_file=False` by default)
    * like `log_output` in `run_cmd`
    * rename tmp file to `.out` instead of `.log`
    * no timestamps, so not really a log
* enabling streaming of output to stdout (`stream_output=False` by default)
    * like `stream_output` in `run_cmd`
* run command asynchronously (`asynchronous=False` by default)
    * like `asynchronous` in `run_cmd`
    * (can't use `async=False` since `async` is a Python keyword)

For interactive commands:

* `qa_patterns` to provide question/answer mapping, questions can be regular expressions (`qa=None` by default)
    * combo of `qa` and `std_qa` in `run_cmd_qa`
    * `qa` should be a list of tuples `("question", "answer")`, since we want to control order
    * question could be a string value (exact match (end-of-line?), after re.escape) or a compiled regex (regex match)
* `qa_wait_patterns` to provide list of non-question patterns and how long to allow them
    * example: `[("not a question", 50)]`
    * combo of `no_qa` and `maxhits` in `run_cmd_qa`

Not replacing:

* cryptic `log_ok` and `log_all` options
* `simple` option to only return `True`/`False`
* `regexp` to specify custom error patterns (only used for logging currently)

Function signature:

```python
def run(cmd, fail_on_error=True, split_stderr=False, stdin=None,
        hidden=False, in_dry_run=False, work_dir=None, shell=True,
        output_file=False, stream_output=False, asynchronous=False,
        qa_patterns=None, qa_wait_patterns=None)
```