📄 process.py
字号:
# "C:\foo\"bar" arg1 arg2
idx = cmd.replace('\\"', 'XX').find('"', 1)
if idx == -1:
raise ProcessError("Malformed command: %r" % cmd)
first, rest = cmd[1:idx], cmd[idx+1:]
rest = rest.lstrip()
else:
if ' ' in cmd:
first, rest = cmd.split(' ', 1)
else:
first, rest = cmd, ""
# Ensure the first arg is a valid path to the appropriate file.
import which
if os.sep in first:
altpath = [os.path.dirname(first)]
firstbase = os.path.basename(first)
candidates = list(which.which(firstbase, path=altpath))
elif env:
altpath = _getPathFromEnv(env)
if altpath:
candidates = list(which.which(first, altpath.split(os.pathsep)))
else:
candidates = list(which.which(first))
else:
candidates = list(which.which(first))
if candidates:
return _joinArgv( [candidates[0]] ) + ' ' + rest
else:
raise ProcessError("Could not find an appropriate leading command "\
"for: %r" % cmd)
if sys.platform.startswith("win"):
def _SaferCreateProcess(appName, # app name
cmd, # command line
processSA, # process security attributes
threadSA, # thread security attributes
inheritHandles, # are handles are inherited
creationFlags, # creation flags
env, # environment
cwd, # current working directory
si): # STARTUPINFO pointer
"""If CreateProcess fails from environment type inconsistency then
fix that and try again.
win32process.CreateProcess requires that all environment keys and
values either be all ASCII or all unicode. Try to remove this burden
from the user of process.py.
"""
isWin9x = win32api.GetVersionEx()[3] == VER_PLATFORM_WIN32_WINDOWS
# On Win9x all keys and values of 'env' must be ASCII (XXX
# Actually this is probably only true if the Unicode support
# libraries, which are not installed by default, are not
# installed). On other Windows flavours all keys and values of
# 'env' must all be ASCII *or* all Unicode. We will try to
# automatically convert to the appropriate type, issuing a
# warning if such an automatic conversion is necessary.
#XXX Komodo 2.0 Beta 1 hack. This requirement should be
# pushed out to Komodo code using process.py. Or should it?
if isWin9x and env:
aenv = {}
for key, value in env.items():
aenv[str(key)] = str(value)
env = aenv
log.debug("""\
_SaferCreateProcess(appName=%r,
cmd=%r,
env=%r,
cwd=%r)
os.getcwd(): %r
""", appName, cmd, env, cwd, os.getcwd())
try:
hProcess, hThread, processId, threadId\
= win32process.CreateProcess(appName, cmd, processSA,
threadSA, inheritHandles,
creationFlags, env, cwd, si)
except TypeError, ex:
if ex.args == ('All dictionary items must be strings, or all must be unicode',):
# Try again with an all unicode environment.
#XXX Would be nice if didn't have to depend on the error
# string to catch this.
#XXX Removing this warning for 2.3 release. See bug
# 23215. The right fix is to correct the PHPAppInfo
# stuff to heed the warning.
#import warnings
#warnings.warn('env: ' + str(ex), stacklevel=4)
if isWin9x and env:
aenv = {}
try:
for key, value in env.items():
aenv[str(key)] = str(value)
except UnicodeError, ex:
raise ProcessError(str(ex))
env = aenv
elif env:
uenv = {}
for key, val in env.items():
try:
uenv[unicode(key)] = unicode(val) # default encoding
except UnicodeError:
try:
uenv[unicode(key, 'iso-8859-1')] = unicode(val, 'iso-8859-1') # backup encoding
except UnicodeError:
log.warn('Skipping environment variable "%s" in execution process: unable to convert to unicode using either the default encoding or ISO-8859-1' % (key))
env = uenv
hProcess, hThread, processId, threadId\
= win32process.CreateProcess(appName, cmd, processSA,
threadSA, inheritHandles,
creationFlags, env, cwd,
si)
else:
raise
return hProcess, hThread, processId, threadId
# Maintain references to all spawned ProcessProxy objects to avoid hangs.
# Otherwise, if the user lets the a ProcessProxy object go out of
# scope before the process has terminated, it is possible to get a
# hang (at least it *used* to be so when we had the
# win32api.CloseHandle(<stdin handle>) call in the __del__() method).
# XXX Is this hang possible on Linux as well?
# A reference is removed from this list when the process's .wait or
# .kill method is called.
# XXX Should an atexit() handler be registered to kill all curently
# running processes? Else *could* get hangs, n'est ce pas?
def _registerProcess(process):
global _processes
log.info("_registerprocess(process=%r)", process)
# Clean up zombie processes.
# If the user does not call .wait() or .kill() on processes then
# the ProcessProxy object will not get cleaned up until Python
# exits and _processes goes out of scope. Under heavy usage that
# is a big memory waste. Cleaning up here alleviates that.
for p in _processes[:]: # use copy of _process, because we may modifiy it
try:
# poll to see if is process still running
if sys.platform.startswith("win"):
timeout = 0
else:
timeout = os.WNOHANG
p.wait(timeout)
_unregisterProcess(p)
except ProcessError, ex:
if ex.errno == ProcessProxy.WAIT_TIMEOUT:
pass
else:
raise
_processes.append(process)
def _unregisterProcess(process):
global _processes
log.info("_unregisterProcess(process=%r)", process)
try:
_processes.remove(process)
del process
except ValueError:
pass
def _fixupCommand(cmd, env=None):
"""Fixup the command string so it is launchable via CreateProcess.
One cannot just launch, say "python", via CreateProcess. A full path
to an executable is required. In general there are two choices:
1. Launch the command string via the shell. The shell will find
the fullpath to the appropriate executable. This shell will
also be able to execute special shell commands, like "dir",
which don't map to an actual executable.
2. Find the fullpath to the appropriate executable manually and
launch that exe.
Option (1) is preferred because you don't have to worry about not
exactly duplicating shell behaviour and you get the added bonus of
being able to launch "dir" and friends.
However, (1) is not always an option. Doing so when the shell is
command.com (as on all Win9x boxes) or when using WinNT's cmd.exe,
problems are created with .kill() because these shells seem to eat
up Ctrl-C's and Ctrl-Break's sent via
win32api.GenerateConsoleCtrlEvent(). Strangely this only happens
when spawn via this Python interface. For example, Ctrl-C get
through to hang.exe here:
C:\> ...\w9xpopen.exe "C:\WINDOWS\COMMAND.COM /c hang.exe"
^C
but not here:
>>> p = ProcessOpen('hang.exe')
# This results in the same command to CreateProcess as
# above.
>>> p.kill()
Hence, for these platforms we fallback to option (2). Cons:
- cannot spawn shell commands like 'dir' directly
- cannot spawn batch files
"""
if sys.platform.startswith("win"):
# Fixup the command string to spawn. (Lifted from
# posixmodule.c::_PyPopenCreateProcess() with some modifications)
comspec = os.environ.get("COMSPEC", None)
win32Version = win32api.GetVersion()
if comspec is None:
raise ProcessError("Cannot locate a COMSPEC environment "\
"variable to use as the shell")
# Explicitly check if we are using COMMAND.COM. If we
# are then use the w9xpopen hack.
elif (win32Version & 0x80000000L == 0) and\
(win32Version & 0x5L >= 5) and\
os.path.basename(comspec).lower() != "command.com":
# 2000/XP and not using command.com.
if '"' in cmd or "'" in cmd:
cmd = comspec + ' /c "%s"' % cmd
else:
cmd = comspec + ' /c ' + cmd
elif (win32Version & 0x80000000L == 0) and\
(win32Version & 0x5L < 5) and\
os.path.basename(comspec).lower() != "command.com":
# NT and not using command.com.
try:
cmd = _whichFirstArg(cmd, env)
except ProcessError:
raise ProcessError("Could not find a suitable executable "\
"to launch for '%s'. On WinNT you must manually prefix "\
"shell commands and batch files with 'cmd.exe /c' to "\
"have the shell run them." % cmd)
else:
# Oh gag, we're on Win9x and/or using COMMAND.COM. Use the
# workaround listed in KB: Q150956
w9xpopen = os.path.join(
os.path.dirname(win32api.GetModuleFileName(0)),
'w9xpopen.exe')
if not os.path.exists(w9xpopen):
# Eeek - file-not-found - possibly an embedding
# situation - see if we can locate it in sys.exec_prefix
w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
'w9xpopen.exe')
if not os.path.exists(w9xpopen):
raise ProcessError(\
"Can not locate 'w9xpopen.exe' which is needed "\
"for ProcessOpen to work with your shell or "\
"platform.")
## This would be option (1):
#cmd = '%s "%s /c %s"'\
# % (w9xpopen, comspec, cmd.replace('"', '\\"'))
try:
cmd = _whichFirstArg(cmd, env)
except ProcessError:
raise ProcessError("Could not find a suitable executable "\
"to launch for '%s'. On Win9x you must manually prefix "\
"shell commands and batch files with 'command.com /c' "\
"to have the shell run them." % cmd)
cmd = '%s "%s"' % (w9xpopen, cmd.replace('"', '\\"'))
return cmd
class _FileWrapper:
"""Wrap a system file object, hiding some nitpicky details.
This class provides a Python file-like interface to either a Python
file object (pretty easy job), a file descriptor, or an OS-specific
file handle (e.g. Win32 handles to file objects on Windows). Any or
all of these object types may be passed to this wrapper. If more
than one is specified this wrapper prefers to work with certain one
in this order:
- file descriptor (because usually this allows for
return-immediately-on-read-if-anything-available semantics and
also provides text mode translation on Windows)
- OS-specific handle (allows for the above read semantics)
- file object (buffering can cause difficulty for interacting
with spawned programs)
It also provides a place where related such objects can be kept
alive together to prevent premature ref-counted collection. (E.g. on
Windows a Python file object may be associated with a Win32 file
handle. If the file handle is not kept alive the Python file object
will cease to function.)
"""
def __init__(self, file=None, descriptor=None, handle=None):
self._file = file
self._descriptor = descriptor
self._handle = handle
self._closed = 0
if self._descriptor is not None or self._handle is not None:
self._lineBuf = "" # to support .readline()
def __del__(self):
self.close()
def __getattr__(self, name):
"""Forward to the underlying file object."""
if self._file is not None:
return getattr(self._file, name)
else:
raise ProcessError("no file object to pass '%s' attribute to"
% name)
def _win32Read(self, nBytes):
try:
log.info("[%s] _FileWrapper.read: waiting for read on pipe",
id(self))
errCode, text = win32file.ReadFile(self._handle, nBytes)
except pywintypes.error, ex:
# Ignore errors for now, like "The pipe is being closed.",
# etc. XXX There *may* be errors we don't want to avoid.
log.info("[%s] _FileWrapper.read: error reading from pipe: %s",
id(self), ex)
return ""
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -