Allow stdout to redirect to stderr

This commit is contained in:
Anthony Scopatz 2017-01-11 23:29:28 -05:00
parent c411ef7245
commit 74480d8b2d
6 changed files with 81 additions and 10 deletions

View file

@ -725,7 +725,7 @@ with the following syntax:
>>> COMMAND err>o
>>> COMMAND e>out
>>> COMMAND e>o
>>> COMMAND 2>&1 # included for Bash compatibility
>>> COMMAND 2>&1 # included for Bash compatibility
This merge can be combined with other redirections, including pipes (see the
section on `Pipes`_ above):
@ -737,6 +737,16 @@ section on `Pipes`_ above):
It is worth noting that this last example is equivalent to: ``COMMAND a> combined.txt``
Similarly, you can also send stdout to stderr with the following syntax:
.. code-block:: xonshcon
>>> COMMAND out>err
>>> COMMAND out>e
>>> COMMAND o>err
>>> COMMAND o>e
>>> COMMAND 1>&2 # included for Bash compatibility
Redirecting ``stdin``
---------------------
@ -1037,8 +1047,8 @@ regex globbing:
def regexsearch(s):
s = expand_path(s)
return reglob(s)
<function xonsh.built_ins.regexsearch>
Note that both help and superhelp return the object that they are inspecting.

13
news/to2.rst Normal file
View file

@ -0,0 +1,13 @@
**Added:**
* Users may now redirect stdout to stderr in subprocess mode.
**Changed:** None
**Deprecated:** None
**Removed:** None
**Fixed:** None
**Security:** None

View file

@ -157,6 +157,25 @@ def test_script(case):
assert exp_rtn == rtn
ALL_PLATFORMS_STDERR = [
# test redirecting a function alias
("""
def _f(args, stdout):
print('Wow Mom!', file=stdout)
aliases['f'] = _f
f o>e
""", "Wow Mom!\n", 0),
]
@pytest.mark.parametrize('case', ALL_PLATFORMS_STDERR)
def test_script_stder(case):
script, exp_err, exp_rtn = case
out, err, rtn = run_xonsh(script, stderr=sp.PIPE)
assert exp_err == err
assert exp_rtn == rtn
@skip_if_on_windows
@pytest.mark.parametrize('cmd, fmt, exp', [
('pwd', None, lambda: os.getcwd() + '\n'),

View file

@ -1816,8 +1816,8 @@ def test_redirect_all(case):
assert check_xonsh_ast({}, '$[echo "test" {}> test.txt < input.txt]'.format(case), False)
@pytest.mark.parametrize('r',
['e>o', 'e>out', 'err>o', 'err>o',
' 2>1', 'e>1', 'err>1', '2>out',
['e>o', 'e>out', 'err>o',
'2>1', 'e>1', 'err>1', '2>out',
'2>o', 'err>&1', 'e>&1', '2>&1'
])
@pytest.mark.parametrize('o', ['', 'o', 'out', '1'])
@ -1826,6 +1826,17 @@ def test_redirect_error_to_output(r, o):
assert check_xonsh_ast({}, '$[< input.txt echo "test" {} {}> test.txt]'.format(r, o), False)
assert check_xonsh_ast({}, '$[echo "test" {} {}> test.txt < input.txt]'.format(r, o), False)
@pytest.mark.parametrize('r',
['o>e', 'o>err', 'out>e',
'1>2', 'o>2', 'out>2', '1>err',
'1>e', 'out>&2', 'o>&2', '1>&2'
])
@pytest.mark.parametrize('e', ['e', 'err', '2'])
def test_redirect_output_to_error(r, e):
assert check_xonsh_ast({}, '$[echo "test" {} {}> test.txt]'.format(r, e), False)
assert check_xonsh_ast({}, '$[< input.txt echo "test" {} {}> test.txt]'.format(r, e), False)
assert check_xonsh_ast({}, '$[echo "test" {} {}> test.txt < input.txt]'.format(r, e), False)
def test_macro_call_empty():
assert check_xonsh_ast({}, 'f!()', False)

View file

@ -265,6 +265,10 @@ _E2O_MAP = LazyObject(lambda: frozenset({'{}>{}'.format(e, o)
for e in _REDIR_ERR
for o in _REDIR_OUT
if o != ''}), globals(), '_E2O_MAP')
_O2E_MAP = LazyObject(lambda: frozenset({'{}>{}'.format(o, e)
for e in _REDIR_ERR
for o in _REDIR_OUT
if o != ''}), globals(), '_O2E_MAP')
def _is_redirect(x):
@ -323,10 +327,14 @@ def _parse_redirects(r):
def _redirect_streams(r, loc=None):
"""Returns stdin, stdout, stderr tuple of redirections."""
stdin = stdout = stderr = None
no_ampersand = r.replace('&', '')
# special case of redirecting stderr to stdout
if r.replace('&', '') in _E2O_MAP:
if no_ampersand in _E2O_MAP:
stderr = subprocess.STDOUT
return stdin, stdout, stderr
elif no_ampersand in _O2E_MAP:
stdout = 2 # using 2 as a flag, rather than using a file object
return stdin, stdout, stderr
# get streams
orig, mode, dest = _parse_redirects(r)
if mode == 'r':
@ -718,6 +726,10 @@ def _update_last_spec(last):
r, w = pty.openpty() if use_tty else os.pipe()
last.stderr = safe_open(w, 'w')
last.captured_stderr = safe_open(r, 'r')
# redirect stdout to stderr, if we should
if isinstance(last.stdout, int) and last.stdout == 2:
# need to use private interface to avoid duplication.
last._stdout = last.stderr
def cmds_to_specs(cmds, captured=False):

View file

@ -227,10 +227,16 @@ SearchPath = r"((?:[rgp]+|@\w*)?)`([^\n`\\]*(?:\\.[^\n`\\]*)*)`"
# longest operators first (e.g., if = came before ==, == would get
# recognized as two instances of =).
_redir_names = ('out', 'all', 'err', 'e', '2', 'a', '&', '1', 'o')
_e2o_map = ('err>out', 'err>&1', '2>out', 'err>o', 'err>1', 'e>out', 'e>&1',
'2>&1', 'e>o', '2>o', 'e>1', '2>1')
IORedirect = group(group(*_e2o_map), '{}>>?'.format(group(*_redir_names)))
_redir_check = set(_e2o_map)
_redir_map = (
# stderr to stdout
'err>out', 'err>&1', '2>out', 'err>o', 'err>1', 'e>out', 'e>&1',
'2>&1', 'e>o', '2>o', 'e>1', '2>1',
# stdout to stderr
'out>err', 'out>&2', '1>err', 'out>e', 'out>2', 'o>err', 'o>&2',
'1>&2', 'o>e', '1>e', 'o>2', '1>2',
)
IORedirect = group(group(*_redir_map), '{}>>?'.format(group(*_redir_names)))
_redir_check = set(_redir_map)
_redir_check = {'{}>'.format(i) for i in _redir_names}.union(_redir_check)
_redir_check = {'{}>>'.format(i) for i in _redir_names}.union(_redir_check)
_redir_check = frozenset(_redir_check)