Execute a command as if at the OS prompt.
(self, args: argparse.Namespace)
| 4742 | # Preserve quotes since we are passing these strings to the shell |
| 4743 | @with_argparser(_build_shell_parser, preserve_quotes=True) |
| 4744 | def do_shell(self, args: argparse.Namespace) -> None: |
| 4745 | """Execute a command as if at the OS prompt.""" |
| 4746 | import signal |
| 4747 | import subprocess |
| 4748 | |
| 4749 | kwargs: dict[str, Any] = {} |
| 4750 | |
| 4751 | # Set OS-specific parameters |
| 4752 | if sys.platform.startswith("win"): |
| 4753 | # Windows returns STATUS_CONTROL_C_EXIT when application stopped by Ctrl-C |
| 4754 | ctrl_c_ret_code = 0xC000013A |
| 4755 | else: |
| 4756 | # On POSIX, Popen() returns -SIGINT when application stopped by Ctrl-C |
| 4757 | ctrl_c_ret_code = signal.SIGINT.value * -1 |
| 4758 | |
| 4759 | # On POSIX with shell=True, Popen() defaults to /bin/sh as the shell. |
| 4760 | # sh reports an incorrect return code for some applications when Ctrl-C is pressed within that |
| 4761 | # application (e.g. less). Since sh received the SIGINT, it sets the return code to reflect being |
| 4762 | # closed by SIGINT even though less did not exit upon a Ctrl-C press. In the same situation, other |
| 4763 | # shells like bash and zsh report the actual return code of less. Therefore, we will try to run the |
| 4764 | # user's preferred shell which most likely will be something other than sh. This also allows the user |
| 4765 | # to run builtin commands of their preferred shell. |
| 4766 | shell = os.environ.get("SHELL") |
| 4767 | if shell: |
| 4768 | kwargs["executable"] = shell |
| 4769 | |
| 4770 | # Create a list of arguments to shell |
| 4771 | tokens = [args.command, *args.command_args] |
| 4772 | |
| 4773 | # Expand ~ where needed |
| 4774 | utils.expand_user_in_tokens(tokens) |
| 4775 | expanded_command = " ".join(tokens) |
| 4776 | |
| 4777 | # Prevent KeyboardInterrupts while in the shell process. The shell process will |
| 4778 | # still receive the SIGINT since it is in the same process group as us. |
| 4779 | with self.sigint_protection: |
| 4780 | # For any stream that is a StdSim, we will use a pipe so we can capture its output |
| 4781 | proc = subprocess.Popen( # noqa: S602 |
| 4782 | expanded_command, |
| 4783 | stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable] |
| 4784 | stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr, |
| 4785 | shell=True, |
| 4786 | **kwargs, |
| 4787 | ) |
| 4788 | |
| 4789 | proc_reader = utils.ProcReader(proc, self.stdout, sys.stderr) |
| 4790 | proc_reader.wait() |
| 4791 | |
| 4792 | # Save the return code of the application for use in a pyscript |
| 4793 | self.last_result = proc.returncode |
| 4794 | |
| 4795 | # If the process was stopped by Ctrl-C, then inform the caller by raising a KeyboardInterrupt. |
| 4796 | # This is to support things like stop_on_keyboard_interrupt in runcmds_plus_hooks(). |
| 4797 | if proc.returncode == ctrl_c_ret_code: |
| 4798 | self._raise_keyboard_interrupt() |
| 4799 | |
| 4800 | @staticmethod |
| 4801 | def _reset_py_display() -> None: |