@@ -1519,18 +1519,19 @@ def _complete_worker(self, text: str, state: int) -> Optional[str]:
15191519 # Check if any portion of the display matches appears in the tab completion
15201520 display_prefix = os .path .commonprefix (self .display_matches )
15211521
1522- # For delimited matches, we check what appears before the display
1523- # matches (common_prefix) as well as the display matches themselves.
1524- if (' ' in common_prefix ) or (display_prefix and ' ' in '' .join (self .display_matches )):
1522+ # For delimited matches, we check for a space in what appears before the display
1523+ # matches (common_prefix) as well as in the display matches themselves.
1524+ if ' ' in common_prefix or (display_prefix
1525+ and any (' ' in match for match in self .display_matches )):
15251526 add_quote = True
15261527
15271528 # If there is a tab completion and any match has a space, then add an opening quote
1528- elif common_prefix and ' ' in '' . join ( self .completion_matches ):
1529+ elif common_prefix and any ( ' ' in match for match in self .completion_matches ):
15291530 add_quote = True
15301531
15311532 if add_quote :
15321533 # Figure out what kind of quote to add and save it as the unclosed_quote
1533- if '"' in '' . join ( self .completion_matches ):
1534+ if any ( '"' in match for match in self .completion_matches ):
15341535 unclosed_quote = "'"
15351536 else :
15361537 unclosed_quote = '"'
@@ -1540,7 +1541,7 @@ def _complete_worker(self, text: str, state: int) -> Optional[str]:
15401541 # Check if we need to remove text from the beginning of tab completions
15411542 elif text_to_remove :
15421543 self .completion_matches = \
1543- [m .replace (text_to_remove , '' , 1 ) for m in self .completion_matches ]
1544+ [match .replace (text_to_remove , '' , 1 ) for match in self .completion_matches ]
15441545
15451546 # Check if we need to restore a shortcut in the tab completions
15461547 # so it doesn't get erased from the command line
@@ -2027,35 +2028,44 @@ def _redirect_output(self, statement: Statement) -> Tuple[bool, utils.Redirectio
20272028 subproc_stdin = io .open (read_fd , 'r' )
20282029 new_stdout = io .open (write_fd , 'w' )
20292030
2030- # We want Popen to raise an exception if it fails to open the process. Thus we don't set shell to True.
2031+ # Set options to not forward signals to the pipe process. If a Ctrl-C event occurs,
2032+ # our sigint handler will forward it only to the most recent pipe process. This makes
2033+ # sure pipe processes close in the right order (most recent first).
2034+ if sys .platform == 'win32' :
2035+ creationflags = subprocess .CREATE_NEW_PROCESS_GROUP
2036+ start_new_session = False
2037+ else :
2038+ creationflags = 0
2039+ start_new_session = True
2040+
2041+ # For any stream that is a StdSim, we will use a pipe so we can capture its output
2042+ proc = subprocess .Popen (statement .pipe_to ,
2043+ stdin = subproc_stdin ,
2044+ stdout = subprocess .PIPE if isinstance (self .stdout , utils .StdSim ) else self .stdout ,
2045+ stderr = subprocess .PIPE if isinstance (sys .stderr , utils .StdSim ) else sys .stderr ,
2046+ creationflags = creationflags ,
2047+ start_new_session = start_new_session ,
2048+ shell = True )
2049+
2050+ # Popen was called with shell=True so the user can chain pipe commands and redirect their output
2051+ # like: !ls -l | grep user | wc -l > out.txt. But this makes it difficult to know if the pipe process
2052+ # started OK, since the shell itself always starts. Therefore, we will wait a short time and check
2053+ # if the pipe process is still running.
20312054 try :
2032- # Set options to not forward signals to the pipe process. If a Ctrl-C event occurs,
2033- # our sigint handler will forward it only to the most recent pipe process. This makes
2034- # sure pipe processes close in the right order (most recent first).
2035- if sys .platform == 'win32' :
2036- creationflags = subprocess .CREATE_NEW_PROCESS_GROUP
2037- start_new_session = False
2038- else :
2039- creationflags = 0
2040- start_new_session = True
2041-
2042- # For any stream that is a StdSim, we will use a pipe so we can capture its output
2043- proc = \
2044- subprocess .Popen (statement .pipe_to ,
2045- stdin = subproc_stdin ,
2046- stdout = subprocess .PIPE if isinstance (self .stdout , utils .StdSim ) else self .stdout ,
2047- stderr = subprocess .PIPE if isinstance (sys .stderr , utils .StdSim ) else sys .stderr ,
2048- creationflags = creationflags ,
2049- start_new_session = start_new_session )
2055+ proc .wait (0.2 )
2056+ except subprocess .TimeoutExpired :
2057+ pass
20502058
2051- saved_state .redirecting = True
2052- saved_state .pipe_proc_reader = utils .ProcReader (proc , self .stdout , sys .stderr )
2053- sys .stdout = self .stdout = new_stdout
2054- except Exception as ex :
2055- self .perror ('Failed to open pipe because - {}' .format (ex ), traceback_war = False )
2059+ # Check if the pipe process already exited
2060+ if proc .returncode is not None :
2061+ self .perror ('Pipe process exited with code {} before command could run' .format (proc .returncode ))
20562062 subproc_stdin .close ()
20572063 new_stdout .close ()
20582064 redir_error = True
2065+ else :
2066+ saved_state .redirecting = True
2067+ saved_state .pipe_proc_reader = utils .ProcReader (proc , self .stdout , sys .stderr )
2068+ sys .stdout = self .stdout = new_stdout
20592069
20602070 elif statement .output :
20612071 import tempfile
@@ -2072,7 +2082,7 @@ def _redirect_output(self, statement: Statement) -> Tuple[bool, utils.Redirectio
20722082 if statement .output == constants .REDIRECTION_APPEND :
20732083 mode = 'a'
20742084 try :
2075- new_stdout = open (statement .output_to , mode )
2085+ new_stdout = open (utils . strip_quotes ( statement .output_to ) , mode )
20762086 saved_state .redirecting = True
20772087 sys .stdout = self .stdout = new_stdout
20782088 except OSError as ex :
@@ -3021,21 +3031,8 @@ def do_shell(self, args: argparse.Namespace) -> None:
30213031 # Create a list of arguments to shell
30223032 tokens = [args .command ] + args .command_args
30233033
3024- # Support expanding ~ in quoted paths
3025- for index , _ in enumerate (tokens ):
3026- if tokens [index ]:
3027- # Check if the token is quoted. Since parsing already passed, there isn't
3028- # an unclosed quote. So we only need to check the first character.
3029- first_char = tokens [index ][0 ]
3030- if first_char in constants .QUOTES :
3031- tokens [index ] = utils .strip_quotes (tokens [index ])
3032-
3033- tokens [index ] = os .path .expanduser (tokens [index ])
3034-
3035- # Restore the quotes
3036- if first_char in constants .QUOTES :
3037- tokens [index ] = first_char + tokens [index ] + first_char
3038-
3034+ # Expand ~ where needed
3035+ utils .expand_user_in_tokens (tokens )
30393036 expanded_command = ' ' .join (tokens )
30403037
30413038 # Prevent KeyboardInterrupts while in the shell process. The shell process will
@@ -3334,18 +3331,21 @@ def load_ipy(app):
33343331 help = 'output commands to a script file, implies -s' ),
33353332 ACTION_ARG_CHOICES , ('path_complete' ,))
33363333 setattr (history_action_group .add_argument ('-t' , '--transcript' ,
3337- help = 'output commands and results to a transcript file, implies -s' ),
3334+ help = 'output commands and results to a transcript file,\n '
3335+ 'implies -s' ),
33383336 ACTION_ARG_CHOICES , ('path_complete' ,))
33393337 history_action_group .add_argument ('-c' , '--clear' , action = 'store_true' , help = 'clear all history' )
33403338
33413339 history_format_group = history_parser .add_argument_group (title = 'formatting' )
3342- history_script_help = 'output commands in script format, i.e. without command numbers'
3343- history_format_group .add_argument ('-s' , '--script' , action = 'store_true' , help = history_script_help )
3344- history_expand_help = 'output expanded commands instead of entered command'
3345- history_format_group .add_argument ('-x' , '--expanded' , action = 'store_true' , help = history_expand_help )
3340+ history_format_group .add_argument ('-s' , '--script' , action = 'store_true' ,
3341+ help = 'output commands in script format, i.e. without command\n '
3342+ 'numbers' )
3343+ history_format_group .add_argument ('-x' , '--expanded' , action = 'store_true' ,
3344+ help = 'output fully parsed commands with any aliases and\n '
3345+ 'macros expanded, instead of typed commands' )
33463346 history_format_group .add_argument ('-v' , '--verbose' , action = 'store_true' ,
3347- help = 'display history and include expanded commands if they'
3348- ' differ from the typed command' )
3347+ help = 'display history and include expanded commands if they\n '
3348+ 'differ from the typed command' )
33493349
33503350 history_arg_help = ("empty all history items\n "
33513351 "a one history item by number\n "
0 commit comments