{"id":2266,"library":"runs","title":"Runs","description":"Runs is a Python library (version 1.3.0) that enhances the standard `subprocess` module by providing improved functions to execute blocks of text as sequences of shell commands. It adds features like multi-command execution, line continuations, comment handling, optional logging, error handling, lazy evaluation, and defaults to UTF-8 encoding. It offers more robust handling for scenarios that often trip up the native `subprocess` functions.","status":"active","version":"1.3.0","language":"python","source_language":"en","source_url":"https://github.com/rec/runs/","tags":["subprocess","shell","cli","automation","command-execution"],"install":[{"cmd":"pip install runs","lang":"bash","label":"Install stable version"}],"dependencies":[],"imports":[{"note":"Used for executing commands and waiting for completion, returning a list of results.","symbol":"run","correct":"from runs import run"},{"note":"Equivalent to subprocess.call() but for multiple commands/text blocks.","symbol":"call","correct":"from runs import call"},{"note":"Similar to subprocess.check_call(), raises CalledProcessError on non-zero exit codes.","symbol":"check_call","correct":"from runs import check_call"},{"note":"Similar to subprocess.check_output(), captures output and raises CalledProcessError on failure.","symbol":"check_output","correct":"from runs import check_output"}],"quickstart":{"code":"from runs import run\n\n# Run a block of text as multiple commands\noutput = run('''\n  echo \"Hello from Runs!\"\n  ls -l\n''')\n\nfor line in output:\n    print(line)\n\n# Run with error handling and echo\ntry:\n    result = run('false; echo ok', on_exception=False, echo=True)\nexcept Exception as e:\n    print(f\"Caught expected error: {e}\")\n\n# Get stdout of a single command\nstdout_list = run('echo \"Single command output\"')\nprint(stdout_list[0])","lang":"python","description":"This example demonstrates how to use `runs.run()` to execute a multi-line block of commands and capture their output. It also shows how to enable echoing commands and handle exceptions from failing subprocesses. The library provides a more convenient interface for common shell interactions than the raw `subprocess` module."},"warnings":[{"fix":"Use explicit line continuations for multi-line shell commands within a single text block, or pass commands as a list of strings if precise command separation is critical, e.g., `runs.run(['command arg1 arg2', 'another_command'])`.","message":"By default, the `runs` library splits input text into commands by newlines. Ensure that multi-line commands intended as a single unit use line continuations (e.g., `\\`) as expected by your shell, or pass commands as a list of strings.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Avoid constructing command strings with unsanitized user input. If user input must be part of a command, pass the command and its arguments as a list of strings (where each element is an argument) rather than a single string, to bypass shell interpretation. Alternatively, use `shlex.quote()` to properly escape individual arguments.","message":"While `runs` aims to improve `subprocess` error handling, raw shell commands executed with `shell=True` (which `runs` implies when passing a single string block) are susceptible to shell injection if untrusted input is included. Always sanitize or escape user-provided data.","severity":"gotcha","affected_versions":"All versions"},{"fix":"For asynchronous or parallel execution, consider directly using `subprocess.Popen` or dedicated async/parallel processing libraries like `asyncio.create_subprocess_exec` or `multiprocessing`.","message":"The `runs` library processes commands sequentially. For long-running or concurrent tasks, using `runs` might block your main program. It does not inherently provide asynchronous execution or process management capabilities beyond waiting for completion.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Adjust your code to iterate over the returned list if you expect multiple outputs, or access `result[0]` if only the first command's output is relevant. Remember that each element in the list is the `stdout` of a command, not a `CompletedProcess` object.","message":"The library's functions (e.g., `run()`, `check_output()`) return a list of strings, one for each command executed in the input block. This differs from `subprocess.run()` which returns a single `CompletedProcess` object.","severity":"breaking","affected_versions":"All versions"}],"env_vars":null,"search_vec":"'-8':51 '1.3.0':8 'add':30 'autom':71 'block':21 'cli':70 'command':28,35,73 'command-execut':72 'comment':39 'continu':38 'default':48 'encod':52 'enhanc':10 'error':43 'evalu':46 'execut':20,36,74 'featur':31 'function':18,67 'handl':40,44,57 'improv':17 'lazi':45 'librari':6 'like':32 'line':37 'log':42 'modul':14 'multi':34 'multi-command':33 'nativ':65 'offer':54 'often':61 'option':41 'provid':16 'python':5 'robust':56 'run':1,2 'scenario':59 'sequenc':25 'shell':27,69 'standard':12 'subprocess':13,66,68 'text':23 'trip':62 'utf':50 'version':7","created_at":"2026-04-09T18:50:37.454681+00:00","updated_at":"2026-04-16T21:14:19.696722+00:00","problems":[{"fix":"Install the library using pip: `pip install runs`","cause":"The 'runs' library has not been installed in your current Python environment.","error":"ModuleNotFoundError: No module named 'runs'"},{"fix":"Pass the command as a single string: `runs.execute(\"ls -l\")`","cause":"The 'runs.execute()' function expects a single string containing the shell command(s), not a list of arguments, when 'shell=True' (which is the default).","error":"TypeError: expected str, bytes or os.PathLike object, not list"},{"fix":"Verify that the shell command is correct, exists in the system's PATH, and has the necessary permissions. You can also disable checking with `check=False` if a non-zero exit status is expected for your use case.","cause":"The shell command executed by 'runs.execute()' returned a non-zero exit status, indicating an error during its execution (e.g., command not found, incorrect arguments, permission denied).","error":"runs.CalledProcessError: Command 'nonexistent_command' returned non-zero exit status 127."},{"fix":"Iterate over the generator to access the results: `for result in runs.lazy_execute(\"echo hello\"): print(result.stdout)`","cause":"When using 'runs.lazy_execute()', it returns a generator object, which needs to be iterated over to yield the 'CompletedProcess' objects that contain attributes like 'stdout'.","error":"AttributeError: 'generator' object has no attribute 'stdout'"}],"ecosystem":"pypi","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":"1.3.0","cli_name":"","cli_version":null,"type":"library","homepage":null,"github":null,"docs":null,"changelog":null,"pypi":"https://pypi.org/project/runs/","npm":null,"openapi_spec":null,"status_page":null,"smithery":null,"categories":["devops","http-networking"],"base_url":null,"auth_type":null,"provenance":{"verified_status":"passing","verified_at":"2026-06-28","last_verified":"2026-08-28","next_check":"2026-07-28","install_tag":null}}