xonsh / xonsh

:shell: Python-powered shell. Full-featured and cross-platform.
http://xon.sh
Other
8.17k stars 632 forks source link

Implement `set -e` alternative #5311

Open ading2210 opened 3 months ago

ading2210 commented 3 months ago

xonfig

``` $ xonfig +------------------+-----------------+ | xonsh | 0.15.1 | | Python | 3.11.2 | | PLY | 3.11 | | have readline | True | | prompt toolkit | None | | shell type | readline | | history backend | json | | pygments | None | | on posix | True | | on linux | True | | distro | unknown | | on wsl | False | | on darwin | False | | on windows | False | | on cygwin | False | | on msys2 | False | | is superuser | False | | default encoding | utf-8 | | xonsh encoding | utf-8 | | encoding errors | surrogateescape | | xontrib | [] | | RC file | [] | +------------------+-----------------+ ```

Expected Behavior

In bash, doing cmd || true will ignore any errors that the command generates if set -e is on. I would expect that the behavior is similar in Xonsh with $RAISE_SUBPROC_ERROR, which is the equivalent setting.

For example:

bash

bash -c "
set -e
false || true
echo "success"
"
# success

Current Behavior

A subprocess.CalledProcessError is called if any of the commands in the expression fail.

Traceback (if applicable)

```xsh xonsh $RAISE_SUBPROC_ERROR = True $XONSH_SHOW_TRACEBACK = True false || true ``` ```xsh # :1:0:6 - false || true # :1:0:6 + ![false] # :1:8:14 - false || true # :1:8:14 + ![true] # xonsh: To log full traceback to a file set: $XONSH_TRACEBACK_LOGFILE = Traceback (most recent call last): File "", line 1, in File "/home/user/.local/pipx/venvs/xonsh/lib/python3.11/site-packages/xonsh/built_ins.py", line 206, in subproc_captured_hiddenobject return xonsh.procs.specs.run_subproc(cmds, captured="hiddenobject", envs=envs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.local/pipx/venvs/xonsh/lib/python3.11/site-packages/xonsh/procs/specs.py", line 908, in run_subproc return _run_specs(specs, cmds) ^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/.local/pipx/venvs/xonsh/lib/python3.11/site-packages/xonsh/procs/specs.py", line 943, in _run_specs command.end() File "/home/user/.local/pipx/venvs/xonsh/lib/python3.11/site-packages/xonsh/procs/pipelines.py", line 459, in end self._end(tee_output=tee_output) File "/home/user/.local/pipx/venvs/xonsh/lib/python3.11/site-packages/xonsh/procs/pipelines.py", line 478, in _end self._raise_subproc_error() File "/home/user/.local/pipx/venvs/xonsh/lib/python3.11/site-packages/xonsh/procs/pipelines.py", line 603, in _raise_subproc_error raise subprocess.CalledProcessError(rtn, spec.args, output=self.output) subprocess.CalledProcessError: Command '['false']' returned non-zero exit status 1. ```

Steps to Reproduce

  1. Open xonsh
  2. Run $RAISE_SUBPROC_ERROR = True
  3. Run false || true

For community

⬇️ Please click the 👍 reaction instead of leaving a +1 or 👍 comment

anki-code commented 3 months ago

Indeed, $RAISE_SUBPROC_ERROR is to raise error in any case when any command fails but set -e treat the line as whole:

Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists of Commands), or a compound command (see Compound Commands) returns a non-zero status.

So we need additional setting e.g. $XONSH_RAISE_SUBPROC_PIPELINE_ERROR to do the same as bash. PR is welcome!

anki-code commented 3 months ago

JFYI. For this:

set -e
ls nonono || true     # false || true
echo "success"

the xonsh equvalent for now is this:

$RAISE_SUBPROC_ERROR = True
try:
    ls nonono
except:               # OR except subprocess.CalledProcessError:
    pass
echo "success"
anki-code commented 3 months ago

You can create syntax sugar for this using macro call:

import subprocess
def ignore_error(cmd):
    """Ignore failing of the command."""
    try:
        execx(cmd)
    except subprocess.CalledProcessError:
        pass

$RAISE_SUBPROC_ERROR = False      
ignore_error!(echo 1 and (ls nonono or echo 2))
echo success
# success

$RAISE_SUBPROC_ERROR = True        
ignore_error!(echo 1 and (ls nonono or echo 2))
echo success
# success
ading2210 commented 3 months ago

I'm aware that I can do a try-except block to ignore errors, though that still doesn't help with the underlying problem of boolean logic being broken with subprocess return codes.

anki-code commented 1 month ago

I figured this out the case.

What bash doing. In fact it implicitly overrides the execution of a logical expression and the execution of process. If we run echo 1 and ls nonono the result of this logical will be treated as "return code" and if it's not 0 and set -e the error will be raised.

In xonsh we have separation between process running and logical expression running. If we run echo 1 and ls nonono we have 2 separate processes and logical expression and RAISE_SUBPROC_ERROR will work on process level.

In fact in this issue was requested an additional mode like RAISE_COMMAND_ERROR that will work for the whole logical expression.

Current behavior:

$RAISE_SUBPROC_ERROR=False
echo 1 and (ls no or echo 2)
# run logical: `echo 1`, `ls no`, `echo 2`.
__xonsh__.history[-1]
# rtn=0
echo 1 and (echo 2 or ls no)
# run logical: `echo 1`, `echo 2`.
__xonsh__.history[-1]
# rtn=0

$RAISE_SUBPROC_ERROR=True
echo 1 and (ls no or echo 2)
# run logical: `echo 1`, `ls no` (raise and stop).
__xonsh__.history[-1]
# rtn=2
echo 1 and (echo 2 or ls no)
# run logical: `echo 1`, `echo 2`.
__xonsh__.history[-1]
# rtn=0