Pure Python module for spawning child applications and controlling them automatically with expect-like functionality.
—
Utility functions for common tasks including executable finding, command line parsing, and file system operations that support pexpect's functionality.
Functions for finding executable files in the system PATH.
def which(filename, env=None):
"""
Find executable file in PATH.
Searches for the given filename in the directories listed in the PATH
environment variable and returns the full path to the first executable
file found.
Parameters:
- filename (str): Name of executable to find
- env (dict): Environment variables (uses os.environ if None)
Returns:
str or None: Full path to executable, or None if not found
"""
def is_executable_file(path):
"""
Check if a file is executable.
Checks that the path points to an executable regular file or symlink
to an executable file. Handles platform-specific executable detection.
Parameters:
- path (str): Path to check
Returns:
bool: True if file exists and is executable, False otherwise
"""Functions for parsing and processing command line strings.
def split_command_line(command_line):
"""
Split command line into argument list.
Parses a command line string into a list of arguments, handling
quoted strings and escape sequences properly.
Parameters:
- command_line (str): Command line string to parse
Returns:
list: List of command arguments
"""Internal utility functions for handling I/O operations with proper interrupt handling.
def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None):
"""
Wrapper around select.select() that ignores interrupts.
Provides robust select() operation that retries on EINTR,
making pexpect more reliable on systems with signal handling.
Parameters:
- iwtd (list): Input wait list (file descriptors to read)
- owtd (list): Output wait list (file descriptors to write)
- ewtd (list): Error wait list (file descriptors for exceptions)
- timeout (float): Timeout in seconds (None for blocking)
Returns:
tuple: (readable, writable, exceptional) file descriptor lists
"""
def poll_ignore_interrupts(fd_list, timeout=None):
"""
Wrapper around select.poll() that ignores interrupts.
Provides robust poll() operation that retries on EINTR.
Parameters:
- fd_list (list): List of (fd, event_mask) tuples
- timeout (int): Timeout in milliseconds (None for blocking)
Returns:
list: List of (fd, event) tuples for ready file descriptors
"""Constants and utilities for Python 2/3 compatibility.
string_types: tuple
"""
Tuple of string types for isinstance() checks.
(str,) on Python 3, (unicode, str) on Python 2.
"""import pexpect
# Find common executables
ssh_path = pexpect.which('ssh')
if ssh_path:
print(f"SSH found at: {ssh_path}")
child = pexpect.spawn(ssh_path)
else:
print("SSH not found in PATH")
# Check multiple possible names
for cmd in ['python3', 'python', 'python2']:
python_path = pexpect.which(cmd)
if python_path:
print(f"Python found: {python_path}")
break
else:
print("No Python interpreter found")
# Use custom environment
custom_env = os.environ.copy()
custom_env['PATH'] = '/usr/local/bin:/usr/bin:/bin'
git_path = pexpect.which('git', env=custom_env)
print(f"Git in custom PATH: {git_path}")import pexpect
import os
# Check if file is executable before spawning
script_path = '/usr/local/bin/myscript.sh'
if os.path.exists(script_path):
if pexpect.is_executable_file(script_path):
print(f"{script_path} is executable")
child = pexpect.spawn(script_path)
else:
print(f"{script_path} exists but is not executable")
else:
print(f"{script_path} does not exist")
# Check multiple candidates
candidates = [
'/usr/bin/python3',
'/usr/local/bin/python3',
'/opt/python/bin/python3'
]
for candidate in candidates:
if pexpect.is_executable_file(candidate):
print(f"Using Python: {candidate}")
python = pexpect.spawn(candidate)
break
else:
print("No valid Python executable found")import pexpect
# Parse complex command lines
command_line = 'ssh -o "StrictHostKeyChecking no" user@host "ls -la /tmp"'
args = pexpect.split_command_line(command_line)
print("Parsed arguments:")
for i, arg in enumerate(args):
print(f" {i}: {arg}")
# Use with spawn
child = pexpect.spawn(args[0], args[1:])
# Handle commands with quotes and escapes
complex_cmd = r'echo "Hello \"World\"" && ls -la'
parsed = pexpect.split_command_line(complex_cmd)
print(f"Complex command parsed: {parsed}")import pexpect
def find_and_run(program, args=None, **kwargs):
"""
Find program in PATH and run it with pexpect.
"""
program_path = pexpect.which(program)
if not program_path:
raise FileNotFoundError(f"Program '{program}' not found in PATH")
if not pexpect.is_executable_file(program_path):
raise PermissionError(f"Program '{program_path}' is not executable")
# Build command
if args:
return pexpect.spawn(program_path, args, **kwargs)
else:
return pexpect.spawn(program_path, **kwargs)
# Usage examples
try:
# Run git with arguments
git = find_and_run('git', ['status', '--porcelain'])
git.expect(pexpect.EOF)
print("Git status:", git.before.decode())
git.close()
# Run Python interpreter
python = find_and_run('python3', ['-c', 'print("Hello from Python")'])
python.expect(pexpect.EOF)
print("Python output:", python.before.decode())
python.close()
except (FileNotFoundError, PermissionError) as e:
print(f"Error: {e}")import pexpect
import sys
import os
def find_editor():
"""Find a suitable text editor across platforms."""
if sys.platform == 'win32':
# Windows editors
editors = ['notepad.exe', 'wordpad.exe', 'write.exe']
else:
# Unix/Linux editors
editors = ['nano', 'vim', 'vi', 'emacs', 'gedit']
for editor in editors:
editor_path = pexpect.which(editor)
if editor_path and pexpect.is_executable_file(editor_path):
return editor_path
return None
# Find and use editor
editor = find_editor()
if editor:
print(f"Found editor: {editor}")
# Could spawn editor here
else:
print("No suitable editor found")import pexpect
import os
def find_in_custom_paths(program, extra_paths=None):
"""
Find executable in PATH plus additional custom paths.
"""
# Try standard PATH first
result = pexpect.which(program)
if result:
return result
# Try additional paths
if extra_paths:
original_path = os.environ.get('PATH', '')
try:
# Temporarily extend PATH
extended_path = os.pathsep.join(extra_paths + [original_path])
custom_env = os.environ.copy()
custom_env['PATH'] = extended_path
result = pexpect.which(program, env=custom_env)
return result
finally:
# PATH is restored automatically since we used a copy
pass
return None
# Usage
extra_search_paths = [
'/opt/local/bin',
'/usr/local/sbin',
'~/bin'
]
program = find_in_custom_paths('special_tool', extra_search_paths)
if program:
print(f"Found special_tool at: {program}")import pexpect
import shlex
def safe_spawn(command_line, **kwargs):
"""
Safely spawn a command with proper argument parsing.
"""
# Parse the command line
try:
if isinstance(command_line, str):
args = pexpect.split_command_line(command_line)
else:
args = command_line
if not args:
raise ValueError("Empty command")
program = args[0]
program_args = args[1:] if len(args) > 1 else []
# Find the executable
program_path = pexpect.which(program)
if not program_path:
raise FileNotFoundError(f"Command '{program}' not found")
# Verify it's executable
if not pexpect.is_executable_file(program_path):
raise PermissionError(f"Command '{program_path}' is not executable")
# Spawn with full path
return pexpect.spawn(program_path, program_args, **kwargs)
except Exception as e:
raise RuntimeError(f"Failed to spawn '{command_line}': {e}")
# Usage examples
try:
# Safe command execution
child = safe_spawn('ls -la /tmp')
child.expect(pexpect.EOF)
print("Directory listing:")
print(child.before.decode())
child.close()
# With complex quoting
child = safe_spawn('find /tmp -name "*.tmp" -type f')
child.expect(pexpect.EOF)
print("Temp files:")
print(child.before.decode())
child.close()
except RuntimeError as e:
print(f"Command failed: {e}")import pexpect
from pexpect.utils import string_types
def handle_mixed_input(data):
"""
Handle both string and bytes input across Python versions.
"""
if isinstance(data, string_types):
# It's a string type (str in Python 3, str/unicode in Python 2)
print(f"Got string: {data}")
return data.encode('utf-8')
elif isinstance(data, bytes):
print(f"Got bytes: {data}")
return data
else:
raise TypeError(f"Expected string or bytes, got {type(data)}")
# Usage
text_data = "Hello, World!"
byte_data = b"Hello, World!"
processed_text = handle_mixed_input(text_data)
processed_bytes = handle_mixed_input(byte_data)
print(f"Processed text: {processed_text}")
print(f"Processed bytes: {processed_bytes}")import pexpect
import os
class CommandValidator:
"""Validate and prepare commands for pexpect execution."""
def __init__(self, extra_paths=None):
self.extra_paths = extra_paths or []
def validate_command(self, command_line):
"""
Validate that a command can be executed.
Returns:
tuple: (program_path, args) if valid
Raises:
ValueError: If command is invalid or not found
"""
# Parse command line
args = pexpect.split_command_line(command_line)
if not args:
raise ValueError("Empty command line")
program = args[0]
program_args = args[1:]
# Find executable
program_path = pexpect.which(program)
if not program_path and self.extra_paths:
# Try extra paths
old_path = os.environ.get('PATH', '')
try:
new_path = os.pathsep.join(self.extra_paths + [old_path])
env = os.environ.copy()
env['PATH'] = new_path
program_path = pexpect.which(program, env=env)
finally:
pass
if not program_path:
raise ValueError(f"Command '{program}' not found in PATH")
if not pexpect.is_executable_file(program_path):
raise ValueError(f"File '{program_path}' is not executable")
return program_path, program_args
def safe_spawn(self, command_line, **kwargs):
"""Spawn command after validation."""
program_path, args = self.validate_command(command_line)
return pexpect.spawn(program_path, args, **kwargs)
# Usage
validator = CommandValidator(extra_paths=['/opt/local/bin', '/usr/local/sbin'])
try:
child = validator.safe_spawn('git status --porcelain')
child.expect(pexpect.EOF)
print("Git status output:")
print(child.before.decode())
child.close()
except ValueError as e:
print(f"Command validation failed: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-pexpect