@@ -2114,10 +2114,10 @@ def complete( # type: ignore[override]
21142114 ansi .style_aware_write (sys .stdout , '\n ' + err_str + '\n ' )
21152115 rl_force_redisplay ()
21162116 return None
2117- except Exception as e :
2117+ except Exception as ex :
21182118 # Insert a newline so the exception doesn't print in the middle of the command line being tab completed
21192119 self .perror ()
2120- self .pexcept (e )
2120+ self .pexcept (ex )
21212121 rl_force_redisplay ()
21222122 return None
21232123
@@ -2199,7 +2199,11 @@ def sigint_handler(self, signum: int, _: FrameType) -> None:
21992199
22002200 # Check if we are allowed to re-raise the KeyboardInterrupt
22012201 if not self .sigint_protection :
2202- raise KeyboardInterrupt ("Got a keyboard interrupt" )
2202+ self ._raise_keyboard_interrupt ()
2203+
2204+ def _raise_keyboard_interrupt (self ) -> None :
2205+ """Helper function to raise a KeyboardInterrupt"""
2206+ raise KeyboardInterrupt ("Got a keyboard interrupt" )
22032207
22042208 def precmd (self , statement : Union [Statement , str ]) -> Statement :
22052209 """Hook method executed just before the command is executed by
@@ -2430,9 +2434,9 @@ def runcmds_plus_hooks(
24302434 line , add_to_history = add_to_history , raise_keyboard_interrupt = stop_on_keyboard_interrupt
24312435 ):
24322436 return True
2433- except KeyboardInterrupt as e :
2437+ except KeyboardInterrupt as ex :
24342438 if stop_on_keyboard_interrupt :
2435- self .perror (e )
2439+ self .perror (ex )
24362440 break
24372441
24382442 return False
@@ -2623,12 +2627,17 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
26232627 # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs,
26242628 # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe
26252629 # processes close in the right order (most recent first).
2626- kwargs = dict ()
2630+ kwargs : Dict [ str , Any ] = dict ()
26272631 if sys .platform == 'win32' :
26282632 kwargs ['creationflags' ] = subprocess .CREATE_NEW_PROCESS_GROUP
26292633 else :
26302634 kwargs ['start_new_session' ] = True
26312635
2636+ # Attempt to run the pipe process in the user's preferred shell instead of the default behavior of using sh.
2637+ shell = os .environ .get ("SHELL" )
2638+ if shell :
2639+ kwargs ['executable' ] = shell
2640+
26322641 # For any stream that is a StdSim, we will use a pipe so we can capture its output
26332642 proc = subprocess .Popen ( # type: ignore[call-overload]
26342643 statement .pipe_to ,
@@ -2654,7 +2663,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
26542663 new_stdout .close ()
26552664 raise RedirectionError (f'Pipe process exited with code { proc .returncode } before command could run' )
26562665 else :
2657- redir_saved_state .redirecting = True
2666+ redir_saved_state .redirecting = True # type: ignore[unreachable]
26582667 cmd_pipe_proc_reader = utils .ProcReader (proc , cast (TextIO , self .stdout ), sys .stderr )
26592668 sys .stdout = self .stdout = new_stdout
26602669
@@ -3825,8 +3834,8 @@ def do_set(self, args: argparse.Namespace) -> None:
38253834 orig_value = settable .get_value ()
38263835 new_value = settable .set_value (utils .strip_quotes (args .value ))
38273836 # noinspection PyBroadException
3828- except Exception as e :
3829- self .perror (f"Error setting { args .param } : { e } " )
3837+ except Exception as ex :
3838+ self .perror (f"Error setting { args .param } : { ex } " )
38303839 else :
38313840 self .poutput (f"{ args .param } - was: { orig_value !r} \n now: { new_value !r} " )
38323841 return
@@ -3863,8 +3872,30 @@ def do_set(self, args: argparse.Namespace) -> None:
38633872 @with_argparser (shell_parser , preserve_quotes = True )
38643873 def do_shell (self , args : argparse .Namespace ) -> None :
38653874 """Execute a command as if at the OS prompt"""
3875+ import signal
38663876 import subprocess
38673877
3878+ kwargs : Dict [str , Any ] = dict ()
3879+
3880+ # Set OS-specific parameters
3881+ if sys .platform .startswith ('win' ):
3882+ # Windows returns STATUS_CONTROL_C_EXIT when application stopped by Ctrl-C
3883+ ctrl_c_ret_code = 0xC000013A
3884+ else :
3885+ # On POSIX, Popen() returns -SIGINT when application stopped by Ctrl-C
3886+ ctrl_c_ret_code = signal .SIGINT .value * - 1
3887+
3888+ # On POSIX with shell=True, Popen() defaults to /bin/sh as the shell.
3889+ # sh reports an incorrect return code for some applications when Ctrl-C is pressed within that
3890+ # application (e.g. less). Since sh received the SIGINT, it sets the return code to reflect being
3891+ # closed by SIGINT even though less did not exit upon a Ctrl-C press. In the same situation, other
3892+ # shells like bash and zsh report the actual return code of less. Therefore we will try to run the
3893+ # user's preferred shell which most likely will be something other than sh. This also allows the user
3894+ # to run builtin commands of their preferred shell.
3895+ shell = os .environ .get ("SHELL" )
3896+ if shell :
3897+ kwargs ['executable' ] = shell
3898+
38683899 # Create a list of arguments to shell
38693900 tokens = [args .command ] + args .command_args
38703901
@@ -3876,19 +3907,25 @@ def do_shell(self, args: argparse.Namespace) -> None:
38763907 # still receive the SIGINT since it is in the same process group as us.
38773908 with self .sigint_protection :
38783909 # For any stream that is a StdSim, we will use a pipe so we can capture its output
3879- proc = subprocess .Popen (
3910+ proc = subprocess .Popen ( # type: ignore[call-overload]
38803911 expanded_command ,
38813912 stdout = subprocess .PIPE if isinstance (self .stdout , utils .StdSim ) else self .stdout , # type: ignore[unreachable]
38823913 stderr = subprocess .PIPE if isinstance (sys .stderr , utils .StdSim ) else sys .stderr , # type: ignore[unreachable]
38833914 shell = True ,
3915+ ** kwargs ,
38843916 )
38853917
3886- proc_reader = utils .ProcReader (proc , cast (TextIO , self .stdout ), sys .stderr )
3918+ proc_reader = utils .ProcReader (proc , cast (TextIO , self .stdout ), sys .stderr ) # type: ignore[arg-type]
38873919 proc_reader .wait ()
38883920
38893921 # Save the return code of the application for use in a pyscript
38903922 self .last_result = proc .returncode
38913923
3924+ # If the process was stopped by Ctrl-C, then inform the caller by raising a KeyboardInterrupt.
3925+ # This is to support things like stop_on_keyboard_interrupt in run_cmds_plus_hooks().
3926+ if proc .returncode == ctrl_c_ret_code :
3927+ self ._raise_keyboard_interrupt ()
3928+
38923929 @staticmethod
38933930 def _reset_py_display () -> None :
38943931 """
@@ -4545,8 +4582,8 @@ def _generate_transcript(self, history: Union[List[HistoryItem], List[str]], tra
45454582 # then run the command and let the output go into our buffer
45464583 try :
45474584 stop = self .onecmd_plus_hooks (history_item , raise_keyboard_interrupt = True )
4548- except KeyboardInterrupt as e :
4549- self .perror (e )
4585+ except KeyboardInterrupt as ex :
4586+ self .perror (ex )
45504587 stop = True
45514588
45524589 commands_run += 1
0 commit comments