0
# Utilities and Helpers
1
2
Utility functions for common tasks including executable finding, command line parsing, and file system operations that support pexpect's functionality.
3
4
## Capabilities
5
6
### Executable Location
7
8
Functions for finding executable files in the system PATH.
9
10
```python { .api }
11
def which(filename, env=None):
12
"""
13
Find executable file in PATH.
14
15
Searches for the given filename in the directories listed in the PATH
16
environment variable and returns the full path to the first executable
17
file found.
18
19
Parameters:
20
- filename (str): Name of executable to find
21
- env (dict): Environment variables (uses os.environ if None)
22
23
Returns:
24
str or None: Full path to executable, or None if not found
25
"""
26
27
def is_executable_file(path):
28
"""
29
Check if a file is executable.
30
31
Checks that the path points to an executable regular file or symlink
32
to an executable file. Handles platform-specific executable detection.
33
34
Parameters:
35
- path (str): Path to check
36
37
Returns:
38
bool: True if file exists and is executable, False otherwise
39
"""
40
```
41
42
### Command Line Parsing
43
44
Functions for parsing and processing command line strings.
45
46
```python { .api }
47
def split_command_line(command_line):
48
"""
49
Split command line into argument list.
50
51
Parses a command line string into a list of arguments, handling
52
quoted strings and escape sequences properly.
53
54
Parameters:
55
- command_line (str): Command line string to parse
56
57
Returns:
58
list: List of command arguments
59
"""
60
```
61
62
### Low-Level I/O Utilities
63
64
Internal utility functions for handling I/O operations with proper interrupt handling.
65
66
```python { .api }
67
def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None):
68
"""
69
Wrapper around select.select() that ignores interrupts.
70
71
Provides robust select() operation that retries on EINTR,
72
making pexpect more reliable on systems with signal handling.
73
74
Parameters:
75
- iwtd (list): Input wait list (file descriptors to read)
76
- owtd (list): Output wait list (file descriptors to write)
77
- ewtd (list): Error wait list (file descriptors for exceptions)
78
- timeout (float): Timeout in seconds (None for blocking)
79
80
Returns:
81
tuple: (readable, writable, exceptional) file descriptor lists
82
"""
83
84
def poll_ignore_interrupts(fd_list, timeout=None):
85
"""
86
Wrapper around select.poll() that ignores interrupts.
87
88
Provides robust poll() operation that retries on EINTR.
89
90
Parameters:
91
- fd_list (list): List of (fd, event_mask) tuples
92
- timeout (int): Timeout in milliseconds (None for blocking)
93
94
Returns:
95
list: List of (fd, event) tuples for ready file descriptors
96
"""
97
```
98
99
### Type Compatibility
100
101
Constants and utilities for Python 2/3 compatibility.
102
103
```python { .api }
104
string_types: tuple
105
"""
106
Tuple of string types for isinstance() checks.
107
(str,) on Python 3, (unicode, str) on Python 2.
108
"""
109
```
110
111
## Usage Examples
112
113
### Finding Executables
114
115
```python
116
import pexpect
117
118
# Find common executables
119
ssh_path = pexpect.which('ssh')
120
if ssh_path:
121
print(f"SSH found at: {ssh_path}")
122
child = pexpect.spawn(ssh_path)
123
else:
124
print("SSH not found in PATH")
125
126
# Check multiple possible names
127
for cmd in ['python3', 'python', 'python2']:
128
python_path = pexpect.which(cmd)
129
if python_path:
130
print(f"Python found: {python_path}")
131
break
132
else:
133
print("No Python interpreter found")
134
135
# Use custom environment
136
custom_env = os.environ.copy()
137
custom_env['PATH'] = '/usr/local/bin:/usr/bin:/bin'
138
git_path = pexpect.which('git', env=custom_env)
139
print(f"Git in custom PATH: {git_path}")
140
```
141
142
### Validating Executable Files
143
144
```python
145
import pexpect
146
import os
147
148
# Check if file is executable before spawning
149
script_path = '/usr/local/bin/myscript.sh'
150
151
if os.path.exists(script_path):
152
if pexpect.is_executable_file(script_path):
153
print(f"{script_path} is executable")
154
child = pexpect.spawn(script_path)
155
else:
156
print(f"{script_path} exists but is not executable")
157
else:
158
print(f"{script_path} does not exist")
159
160
# Check multiple candidates
161
candidates = [
162
'/usr/bin/python3',
163
'/usr/local/bin/python3',
164
'/opt/python/bin/python3'
165
]
166
167
for candidate in candidates:
168
if pexpect.is_executable_file(candidate):
169
print(f"Using Python: {candidate}")
170
python = pexpect.spawn(candidate)
171
break
172
else:
173
print("No valid Python executable found")
174
```
175
176
### Command Line Parsing
177
178
```python
179
import pexpect
180
181
# Parse complex command lines
182
command_line = 'ssh -o "StrictHostKeyChecking no" user@host "ls -la /tmp"'
183
args = pexpect.split_command_line(command_line)
184
print("Parsed arguments:")
185
for i, arg in enumerate(args):
186
print(f" {i}: {arg}")
187
188
# Use with spawn
189
child = pexpect.spawn(args[0], args[1:])
190
191
# Handle commands with quotes and escapes
192
complex_cmd = r'echo "Hello \"World\"" && ls -la'
193
parsed = pexpect.split_command_line(complex_cmd)
194
print(f"Complex command parsed: {parsed}")
195
```
196
197
### Building Dynamic Commands
198
199
```python
200
import pexpect
201
202
def find_and_run(program, args=None, **kwargs):
203
"""
204
Find program in PATH and run it with pexpect.
205
"""
206
program_path = pexpect.which(program)
207
if not program_path:
208
raise FileNotFoundError(f"Program '{program}' not found in PATH")
209
210
if not pexpect.is_executable_file(program_path):
211
raise PermissionError(f"Program '{program_path}' is not executable")
212
213
# Build command
214
if args:
215
return pexpect.spawn(program_path, args, **kwargs)
216
else:
217
return pexpect.spawn(program_path, **kwargs)
218
219
# Usage examples
220
try:
221
# Run git with arguments
222
git = find_and_run('git', ['status', '--porcelain'])
223
git.expect(pexpect.EOF)
224
print("Git status:", git.before.decode())
225
git.close()
226
227
# Run Python interpreter
228
python = find_and_run('python3', ['-c', 'print("Hello from Python")'])
229
python.expect(pexpect.EOF)
230
print("Python output:", python.before.decode())
231
python.close()
232
233
except (FileNotFoundError, PermissionError) as e:
234
print(f"Error: {e}")
235
```
236
237
### Cross-Platform Executable Search
238
239
```python
240
import pexpect
241
import sys
242
import os
243
244
def find_editor():
245
"""Find a suitable text editor across platforms."""
246
247
if sys.platform == 'win32':
248
# Windows editors
249
editors = ['notepad.exe', 'wordpad.exe', 'write.exe']
250
else:
251
# Unix/Linux editors
252
editors = ['nano', 'vim', 'vi', 'emacs', 'gedit']
253
254
for editor in editors:
255
editor_path = pexpect.which(editor)
256
if editor_path and pexpect.is_executable_file(editor_path):
257
return editor_path
258
259
return None
260
261
# Find and use editor
262
editor = find_editor()
263
if editor:
264
print(f"Found editor: {editor}")
265
# Could spawn editor here
266
else:
267
print("No suitable editor found")
268
```
269
270
### Environment-Aware Executable Search
271
272
```python
273
import pexpect
274
import os
275
276
def find_in_custom_paths(program, extra_paths=None):
277
"""
278
Find executable in PATH plus additional custom paths.
279
"""
280
# Try standard PATH first
281
result = pexpect.which(program)
282
if result:
283
return result
284
285
# Try additional paths
286
if extra_paths:
287
original_path = os.environ.get('PATH', '')
288
try:
289
# Temporarily extend PATH
290
extended_path = os.pathsep.join(extra_paths + [original_path])
291
custom_env = os.environ.copy()
292
custom_env['PATH'] = extended_path
293
294
result = pexpect.which(program, env=custom_env)
295
return result
296
297
finally:
298
# PATH is restored automatically since we used a copy
299
pass
300
301
return None
302
303
# Usage
304
extra_search_paths = [
305
'/opt/local/bin',
306
'/usr/local/sbin',
307
'~/bin'
308
]
309
310
program = find_in_custom_paths('special_tool', extra_search_paths)
311
if program:
312
print(f"Found special_tool at: {program}")
313
```
314
315
### Robust Command Execution
316
317
```python
318
import pexpect
319
import shlex
320
321
def safe_spawn(command_line, **kwargs):
322
"""
323
Safely spawn a command with proper argument parsing.
324
"""
325
# Parse the command line
326
try:
327
if isinstance(command_line, str):
328
args = pexpect.split_command_line(command_line)
329
else:
330
args = command_line
331
332
if not args:
333
raise ValueError("Empty command")
334
335
program = args[0]
336
program_args = args[1:] if len(args) > 1 else []
337
338
# Find the executable
339
program_path = pexpect.which(program)
340
if not program_path:
341
raise FileNotFoundError(f"Command '{program}' not found")
342
343
# Verify it's executable
344
if not pexpect.is_executable_file(program_path):
345
raise PermissionError(f"Command '{program_path}' is not executable")
346
347
# Spawn with full path
348
return pexpect.spawn(program_path, program_args, **kwargs)
349
350
except Exception as e:
351
raise RuntimeError(f"Failed to spawn '{command_line}': {e}")
352
353
# Usage examples
354
try:
355
# Safe command execution
356
child = safe_spawn('ls -la /tmp')
357
child.expect(pexpect.EOF)
358
print("Directory listing:")
359
print(child.before.decode())
360
child.close()
361
362
# With complex quoting
363
child = safe_spawn('find /tmp -name "*.tmp" -type f')
364
child.expect(pexpect.EOF)
365
print("Temp files:")
366
print(child.before.decode())
367
child.close()
368
369
except RuntimeError as e:
370
print(f"Command failed: {e}")
371
```
372
373
### String Type Compatibility
374
375
```python
376
import pexpect
377
from pexpect.utils import string_types
378
379
def handle_mixed_input(data):
380
"""
381
Handle both string and bytes input across Python versions.
382
"""
383
if isinstance(data, string_types):
384
# It's a string type (str in Python 3, str/unicode in Python 2)
385
print(f"Got string: {data}")
386
return data.encode('utf-8')
387
elif isinstance(data, bytes):
388
print(f"Got bytes: {data}")
389
return data
390
else:
391
raise TypeError(f"Expected string or bytes, got {type(data)}")
392
393
# Usage
394
text_data = "Hello, World!"
395
byte_data = b"Hello, World!"
396
397
processed_text = handle_mixed_input(text_data)
398
processed_bytes = handle_mixed_input(byte_data)
399
400
print(f"Processed text: {processed_text}")
401
print(f"Processed bytes: {processed_bytes}")
402
```
403
404
## Integration Examples
405
406
### Command Validation Pipeline
407
408
```python
409
import pexpect
410
import os
411
412
class CommandValidator:
413
"""Validate and prepare commands for pexpect execution."""
414
415
def __init__(self, extra_paths=None):
416
self.extra_paths = extra_paths or []
417
418
def validate_command(self, command_line):
419
"""
420
Validate that a command can be executed.
421
422
Returns:
423
tuple: (program_path, args) if valid
424
425
Raises:
426
ValueError: If command is invalid or not found
427
"""
428
# Parse command line
429
args = pexpect.split_command_line(command_line)
430
if not args:
431
raise ValueError("Empty command line")
432
433
program = args[0]
434
program_args = args[1:]
435
436
# Find executable
437
program_path = pexpect.which(program)
438
if not program_path and self.extra_paths:
439
# Try extra paths
440
old_path = os.environ.get('PATH', '')
441
try:
442
new_path = os.pathsep.join(self.extra_paths + [old_path])
443
env = os.environ.copy()
444
env['PATH'] = new_path
445
program_path = pexpect.which(program, env=env)
446
finally:
447
pass
448
449
if not program_path:
450
raise ValueError(f"Command '{program}' not found in PATH")
451
452
if not pexpect.is_executable_file(program_path):
453
raise ValueError(f"File '{program_path}' is not executable")
454
455
return program_path, program_args
456
457
def safe_spawn(self, command_line, **kwargs):
458
"""Spawn command after validation."""
459
program_path, args = self.validate_command(command_line)
460
return pexpect.spawn(program_path, args, **kwargs)
461
462
# Usage
463
validator = CommandValidator(extra_paths=['/opt/local/bin', '/usr/local/sbin'])
464
465
try:
466
child = validator.safe_spawn('git status --porcelain')
467
child.expect(pexpect.EOF)
468
print("Git status output:")
469
print(child.before.decode())
470
child.close()
471
472
except ValueError as e:
473
print(f"Command validation failed: {e}")
474
```