ubelt.util_cmd module

This module exposes the ubelt.cmd() command, which provides a simple means for interacting with the commandline. While this does use subprocess.Popen under the hood, the key draw of ubelt.cmd() is that you can capture stdout/stderr in your program while simultaneously printing it to the terminal in real time.

Example

>>> import ubelt as ub
>>> # Running with verbose=1 will write to stdout in real time
>>> info = ub.cmd('echo "write your command naturally"', verbose=1)
write your command naturally
>>> # Unless `detatch=True`, `cmd` always returns an info dict.
>>> print('info = ' + ub.repr2(info))
info = {
    'command': 'echo "write your command naturally"',
    'cwd': None,
    'err': '',
    'out': 'write your command naturally\n',
    'proc': <...Popen...>,
    'ret': 0,
}
ubelt.util_cmd.cmd(command, shell=False, detach=False, verbose=0, tee=None, cwd=None, env=None, tee_backend='auto', check=False, **kwargs)[source]

Executes a command in a subprocess.

The advantage of this wrapper around subprocess is that (1) you control if the subprocess prints to stdout, (2) the text written to stdout and stderr is returned for parsing, (3) cross platform behavior that lets you specify the command as a string or tuple regardless of whether or not shell=True. (4) ability to detach, return the process object and allow the process to run in the background (eventually we may return a Future object instead).

Parameters
  • command (str or Sequence) – bash-like command string or tuple of executable and args

  • shell (bool, default=False) – if True, process is run in shell.

  • detach (bool, default=False) – if True, process is detached and run in background.

  • verbose (int, default=0) – verbosity mode. Can be 0, 1, 2, or 3.

  • tee (bool, optional) – if True, simultaneously writes to stdout while capturing output from the command. If not specified, defaults to True if verbose > 0. If detach is True, then this argument is ignored.

  • cwd (PathLike, optional) – path to run command

  • env (str, optional) – environment passed to Popen

  • tee_backend (str, optional) – backend for tee output. Valid choices are: “auto”, “select” (POSIX only), and “thread”.

  • check (bool, default=False) – if True, check that the return code was zero before returning, otherwise raise a CalledProcessError. Does nothing if detach is True.

  • **kwargs – only used to support deprecated arguments

Returns

info - information about command status.

if detach is False info contains captured standard out, standard error, and the return code if detach is False info contains a reference to the process.

Return type

dict

Notes

Inputs can either be text or tuple based. On UNIX we ensure conversion to text if shell=True, and to tuple if shell=False. On windows, the input is always text based. See 3 for a potential cross-platform shlex solution for windows.

CommandLine:

xdoctest -m ubelt.util_cmd cmd:6 python -c “import ubelt as ub; ub.cmd(‘ping localhost -c 2’, verbose=2)” pytest “$(python -c ‘import ubelt; print(ubelt.util_cmd.__file__)’)” -sv –xdoctest-verbose 2

References

1

https://stackoverflow.com/questions/11495783/redirect-subprocess-stderr-to-stdout

2

https://stackoverflow.com/questions/7729336/how-can-i-print-and-display-subprocess-stdout-and-stderr-output-without-distorti

3

https://stackoverflow.com/questions/33560364/python-windows-parsing-command-lines-with-shlex

Example

>>> info = cmd(('echo', 'simple cmdline interface'), verbose=1)
simple cmdline interface
>>> assert info['ret'] == 0
>>> assert info['out'].strip() == 'simple cmdline interface'
>>> assert info['err'].strip() == ''

Example

>>> info = cmd('echo str noshell', verbose=0)
>>> assert info['out'].strip() == 'str noshell'

Example

>>> # windows echo will output extra single quotes
>>> info = cmd(('echo', 'tuple noshell'), verbose=0)
>>> assert info['out'].strip().strip("'") == 'tuple noshell'

Example

>>> # Note this command is formatted to work on win32 and unix
>>> info = cmd('echo str&&echo shell', verbose=0, shell=True)
>>> assert info['out'].strip() == 'str' + chr(10) + 'shell'

Example

>>> info = cmd(('echo', 'tuple shell'), verbose=0, shell=True)
>>> assert info['out'].strip().strip("'") == 'tuple shell'

Example

>>> import pytest
>>> info = cmd('echo hi', check=True)
>>> import subprocess
>>> with pytest.raises(subprocess.CalledProcessError):
>>>     cmd('exit 1', check=True, shell=True)

Example

>>> import ubelt as ub
>>> from os.path import join, exists
>>> fpath1 = join(ub.get_app_cache_dir('ubelt'), 'cmdout1.txt')
>>> fpath2 = join(ub.get_app_cache_dir('ubelt'), 'cmdout2.txt')
>>> ub.delete(fpath1)
>>> ub.delete(fpath2)
>>> # Start up two processes that run simultaniously in the background
>>> info1 = ub.cmd(('touch', fpath1), detach=True)
>>> info2 = ub.cmd('echo writing2 > ' + fpath2, shell=True, detach=True)
>>> # Detatched processes are running in the background
>>> # We can run other code while we wait for them.
>>> while not exists(fpath1):
...     pass
>>> while not exists(fpath2):
...     pass
>>> # communicate with the process before you finish
>>> # (otherwise you may leak a text wrapper)
>>> info1['proc'].communicate()
>>> info2['proc'].communicate()
>>> # Check that the process actually did finish
>>> assert (info1['proc'].wait()) == 0
>>> assert (info2['proc'].wait()) == 0
>>> # Check that the process did what we expect
>>> assert ub.readfrom(fpath1) == ''
>>> assert ub.readfrom(fpath2).strip() == 'writing2'