def testStdinRedirect(self): waiter = process.Waiter(_JOB_STATE, _EXEC_OPTS) fd_state = process.FdState(_ERRFMT, _JOB_STATE) PATH = '_tmp/one-two.txt' # Write two lines with open(PATH, 'w') as f: f.write('one\ntwo\n') # Should get the first line twice, because Pop() closes it! r = redirect(Id.Redir_Less, runtime.NO_SPID, redir_loc.Fd(0), redirect_arg.Path(PATH)) fd_state.Push([r], waiter) line1, _ = builtin_misc.ReadLineFromStdin('\n') fd_state.Pop() fd_state.Push([r], waiter) line2, _ = builtin_misc.ReadLineFromStdin('\n') fd_state.Pop() # sys.stdin.readline() would erroneously return 'two' because of buffering. self.assertEqual('one', line1) self.assertEqual('one', line2)
def PushStdinFromPipe(self, r): # type: (int) -> bool """Save the current stdin and make it come from descriptor 'r'. 'r' is typically the read-end of a pipe. For 'lastpipe'/ZSH semantics of echo foo | read line; echo $line """ new_frame = _FdFrame() self.stack.append(new_frame) self.cur_frame = new_frame self._PushDup(r, redir_loc.Fd(0)) return True
def testStdinRedirect(self): PATH = '_tmp/one-two.txt' # Write two lines with open(PATH, 'w') as f: f.write('one\ntwo\n') # Should get the first line twice, because Pop() closes it! r = redirect(Id.Redir_Less, runtime.NO_SPID, redir_loc.Fd(0), redirect_arg.Path(PATH)) self.fd_state.Push([r], self.waiter) line1, _ = builtin_misc._ReadUntilDelim('\n') self.fd_state.Pop() self.fd_state.Push([r], self.waiter) line2, _ = builtin_misc._ReadUntilDelim('\n') self.fd_state.Pop() # sys.stdin.readline() would erroneously return 'two' because of buffering. self.assertEqual('one', line1) self.assertEqual('one', line2)
def _ApplyRedirect(self, r, waiter): # type: (redirect, Waiter) -> None arg = r.arg UP_arg = arg with tagswitch(arg) as case: if case(redirect_arg_e.Path): arg = cast(redirect_arg__Path, UP_arg) if r.op_id in (Id.Redir_Great, Id.Redir_AndGreat): # > &> # NOTE: This is different than >| because it respects noclobber, but # that option is almost never used. See test/wild.sh. mode = posix.O_CREAT | posix.O_WRONLY | posix.O_TRUNC elif r.op_id == Id.Redir_Clobber: # >| mode = posix.O_CREAT | posix.O_WRONLY | posix.O_TRUNC elif r.op_id in (Id.Redir_DGreat, Id.Redir_AndDGreat): # >> &>> mode = posix.O_CREAT | posix.O_WRONLY | posix.O_APPEND elif r.op_id == Id.Redir_Less: # < mode = posix.O_RDONLY elif r.op_id == Id.Redir_LessGreat: # <> mode = posix.O_CREAT | posix.O_RDWR else: raise NotImplementedError(r.op_id) # NOTE: 0666 is affected by umask, all shells use it. try: open_fd = posix.open(arg.filename, mode, 0o666) except OSError as e: self.errfmt.Print_("Can't open %r: %s" % (arg.filename, pyutil.strerror(e)), span_id=r.op_spid) raise # redirect failed new_fd = self._PushDup(open_fd, r.loc) if new_fd != NO_FD: posix.close(open_fd) # Now handle &> and &>> and their variants. These pairs are the same: # # stdout_stderr.py &> out-err.txt # stdout_stderr.py > out-err.txt 2>&1 # # stdout_stderr.py 3&> out-err.txt # stdout_stderr.py 3> out-err.txt 2>&3 # # Ditto for {fd}> and {fd}&> if r.op_id in (Id.Redir_AndGreat, Id.Redir_AndDGreat): self._PushDup(new_fd, redir_loc.Fd(2)) elif case(redirect_arg_e.CopyFd): # e.g. echo hi 1>&2 arg = cast(redirect_arg__CopyFd, UP_arg) if r.op_id == Id.Redir_GreatAnd: # 1>&2 self._PushDup(arg.target_fd, r.loc) elif r.op_id == Id.Redir_LessAnd: # 0<&5 # The only difference between >& and <& is the default file # descriptor argument. self._PushDup(arg.target_fd, r.loc) else: raise NotImplementedError() elif case(redirect_arg_e.MoveFd): # e.g. echo hi 5>&6- arg = cast(redirect_arg__MoveFd, UP_arg) new_fd = self._PushDup(arg.target_fd, r.loc) if new_fd != NO_FD: posix.close(arg.target_fd) UP_loc = r.loc if r.loc.tag_() == redir_loc_e.Fd: fd = cast(redir_loc__Fd, UP_loc).fd else: fd = NO_FD self.cur_frame.saved.append(_RedirFrame(new_fd, fd, False)) elif case(redirect_arg_e.CloseFd): # e.g. echo hi 5>&- self._PushCloseFd(r.loc) elif case(redirect_arg_e.HereDoc): arg = cast(redirect_arg__HereDoc, UP_arg) # NOTE: Do these descriptors have to be moved out of the range 0-9? read_fd, write_fd = posix.pipe() self._PushDup(read_fd, r.loc) # stdin is now the pipe # We can't close like we do in the filename case above? The writer can # get a "broken pipe". self._PushClose(read_fd) thunk = _HereDocWriterThunk(write_fd, arg.body) # TODO: Use PIPE_SIZE to save a process in the case of small here docs, # which are the common case. (dash does this.) start_process = True #start_process = False if start_process: here_proc = Process(thunk, self.job_state) # NOTE: we could close the read pipe here, but it doesn't really # matter because we control the code. _ = here_proc.Start() #log('Started %s as %d', here_proc, pid) self._PushWait(here_proc, waiter) # Now that we've started the child, close it in the parent. posix.close(write_fd) else: posix.write(write_fd, arg.body) posix.close(write_fd)