Shell
Run shell commands.
Functions⚓︎
capture_shell ⚓︎
capture_shell(cmd, *, timeout=120, cwd=None, printer=None, validate_cmd=False)
Run shell command, return the output, and optionally print in real time.
WARNING: This function uses shell=True which can be a security risk. Only use with trusted input or enable validate_cmd for basic protection.
Inspired by: https://stackoverflow.com/a/38745040/3219667
| PARAMETER | DESCRIPTION |
|---|---|
cmd
|
shell command
TYPE:
|
timeout
|
process timeout in seconds. Defaults to 2 minutes. Use None for no timeout.
TYPE:
|
cwd
|
optional path for shell execution
TYPE:
|
printer
|
optional callable to output the lines in real time
TYPE:
|
validate_cmd
|
if True, validates command for dangerous patterns. Default False to preserve backward compatibility and allow legitimate shell features.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
stripped output
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
CalledProcessError
|
if return code is non-zero |
TimeoutExpired
|
if timeout is reached |
ValueError
|
if validate_cmd=True and dangerous patterns are detected |
Source code in corallium/shell.py
def capture_shell(
cmd: str,
*,
timeout: int | None = 120,
cwd: Path | None = None,
printer: Callable[[str], None] | None = None,
validate_cmd: bool = False,
) -> str:
"""Run shell command, return the output, and optionally print in real time.
WARNING: This function uses shell=True which can be a security risk.
Only use with trusted input or enable validate_cmd for basic protection.
Inspired by: https://stackoverflow.com/a/38745040/3219667
Args:
cmd: shell command
timeout: process timeout in seconds. Defaults to 2 minutes. Use None for no timeout.
cwd: optional path for shell execution
printer: optional callable to output the lines in real time
validate_cmd: if True, validates command for dangerous patterns. Default False
to preserve backward compatibility and allow legitimate shell features.
Returns:
str: stripped output
Raises:
CalledProcessError: if return code is non-zero
TimeoutExpired: if timeout is reached
ValueError: if validate_cmd=True and dangerous patterns are detected
"""
if validate_cmd:
_validate_shell_command(cmd)
LOGGER.debug('Running', cmd=cmd, timeout=timeout, cwd=cwd, printer=printer, validate_cmd=validate_cmd)
if timeout and timeout < 0:
raise ValueError('Negative timeouts are not allowed')
start = time()
lines = []
with subprocess.Popen(
cmd,
cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
shell=True,
) as proc:
if not (stdout := proc.stdout):
raise NotImplementedError('Failed to read stdout from process.')
return_code = None
while return_code is None:
if timeout is not None and time() - start >= timeout:
proc.kill()
break
if line := stdout.readline():
lines.append(line)
if printer:
printer(line.rstrip())
else:
return_code = proc.poll()
output = ''.join(lines)
if return_code is None:
# Process was killed due to timeout
raise subprocess.TimeoutExpired(cmd=cmd, timeout=float(timeout or 0), output=output)
if return_code != 0:
raise subprocess.CalledProcessError(returncode=return_code, cmd=cmd, output=output)
duration = time() - start
LOGGER.debug('Shell command completed', cmd=cmd, returncode=0, duration_seconds=round(duration, 2), cwd=cwd)
return output
capture_shell_async
async
⚓︎
capture_shell_async(cmd, *, timeout=120, cwd=None, validate_cmd=False)
Run a shell command asynchronously and return the output.
WARNING: This function uses shell=True which can be a security risk. Only use with trusted input or enable validate_cmd for basic protection.
print(asyncio.run(capture_shell_async('ls ~/.config')))
| PARAMETER | DESCRIPTION |
|---|---|
cmd
|
shell command
TYPE:
|
timeout
|
process timeout in seconds. Defaults to 2 minutes. Use None for no timeout.
TYPE:
|
cwd
|
optional path for shell execution
TYPE:
|
validate_cmd
|
if True, validates command for dangerous patterns. Default False to preserve backward compatibility and allow legitimate shell features.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
stripped output
TYPE:
|
Source code in corallium/shell.py
async def capture_shell_async(
cmd: str,
*,
timeout: int | None = 120,
cwd: Path | None = None,
validate_cmd: bool = False,
) -> str:
"""Run a shell command asynchronously and return the output.
WARNING: This function uses shell=True which can be a security risk.
Only use with trusted input or enable validate_cmd for basic protection.
```py
print(asyncio.run(capture_shell_async('ls ~/.config')))
```
Args:
cmd: shell command
timeout: process timeout in seconds. Defaults to 2 minutes. Use None for no timeout.
cwd: optional path for shell execution
validate_cmd: if True, validates command for dangerous patterns. Default False
to preserve backward compatibility and allow legitimate shell features.
Returns:
str: stripped output
"""
if validate_cmd:
_validate_shell_command(cmd)
LOGGER.debug('Running', cmd=cmd, timeout=timeout, cwd=cwd, validate_cmd=validate_cmd)
start = time()
return await asyncio.wait_for(
_capture_shell_async(cmd=cmd, cwd=cwd, start_time=start),
timeout=timeout or None,
)
run_shell ⚓︎
run_shell(cmd, *, timeout=120, cwd=None, validate_cmd=False)
Run a shell command without capturing the output.
WARNING: This function uses shell=True which can be a security risk. Only use with trusted input or enable validate_cmd for basic protection.
| PARAMETER | DESCRIPTION |
|---|---|
cmd
|
shell command
TYPE:
|
timeout
|
process timeout in seconds. Defaults to 2 minutes. Use None for no timeout.
TYPE:
|
cwd
|
optional path for shell execution
TYPE:
|
validate_cmd
|
if True, validates command for dangerous patterns. Default False to preserve backward compatibility and allow legitimate shell features.
TYPE:
|
Source code in corallium/shell.py
def run_shell(cmd: str, *, timeout: int | None = 120, cwd: Path | None = None, validate_cmd: bool = False) -> None:
"""Run a shell command without capturing the output.
WARNING: This function uses shell=True which can be a security risk.
Only use with trusted input or enable validate_cmd for basic protection.
Args:
cmd: shell command
timeout: process timeout in seconds. Defaults to 2 minutes. Use None for no timeout.
cwd: optional path for shell execution
validate_cmd: if True, validates command for dangerous patterns. Default False
to preserve backward compatibility and allow legitimate shell features.
"""
if validate_cmd:
_validate_shell_command(cmd)
LOGGER.debug('Running', cmd=cmd, timeout=timeout, cwd=cwd, validate_cmd=validate_cmd)
start = time()
subprocess.run(
cmd,
timeout=timeout or None,
cwd=cwd,
stdout=sys.stdout,
stderr=sys.stderr,
check=True,
shell=True,
)
duration = time() - start
LOGGER.debug('Shell command completed', cmd=cmd, returncode=0, duration_seconds=round(duration, 2), cwd=cwd)