0
# Interactive Shell Sessions
1
2
Open and manage interactive shell sessions on remote hosts for complex multi-command workflows and real-time interaction. Enables stateful command execution where commands can build upon previous commands' results.
3
4
## Capabilities
5
6
### Opening Interactive Shells
7
8
Create interactive shell sessions that maintain state between command executions, useful for complex workflows that require environment setup or multi-step operations.
9
10
```python { .api }
11
def open_shell(self, encoding='utf-8', read_timeout=None):
12
"""
13
Open interactive shell sessions on hosts.
14
15
Parameters:
16
- encoding (str, optional): Shell output encoding (default: 'utf-8')
17
- read_timeout (float, optional): Timeout for reading shell output
18
19
Returns:
20
list[InteractiveShell]: List of shell objects (ParallelSSHClient)
21
InteractiveShell: Single shell object (SSHClient)
22
"""
23
```
24
25
Usage examples:
26
27
```python
28
from pssh.clients import ParallelSSHClient, SSHClient
29
30
# Parallel interactive shells
31
hosts = ['server1.example.com', 'server2.example.com']
32
client = ParallelSSHClient(hosts, user='admin')
33
34
shells = client.open_shell()
35
print(f"Opened {len(shells)} interactive shells")
36
37
# Single host interactive shell
38
single_client = SSHClient('server.example.com', user='admin')
39
shell = single_client.open_shell()
40
print("Opened interactive shell")
41
```
42
43
### InteractiveShell Class
44
45
Manage individual interactive shell sessions with stateful command execution and output access.
46
47
```python { .api }
48
class InteractiveShell:
49
def run(self, cmd):
50
"""
51
Execute command in the interactive shell.
52
53
Parameters:
54
- cmd (str): Command to execute in shell
55
56
Note: Command is executed in the shell's current context,
57
maintaining environment variables, working directory, etc.
58
"""
59
60
def close(self):
61
"""
62
Close the interactive shell and wait for completion.
63
64
This should be called when finished with the shell to
65
properly clean up resources and get final exit code.
66
"""
67
68
def __enter__(self):
69
"""
70
Context manager entry - returns self.
71
72
Returns:
73
InteractiveShell: Self for use in with statement
74
"""
75
76
def __exit__(self, exc_type, exc_val, exc_tb):
77
"""
78
Context manager exit - automatically closes the shell.
79
80
Parameters:
81
- exc_type: Exception type if any
82
- exc_val: Exception value if any
83
- exc_tb: Exception traceback if any
84
"""
85
86
# Properties
87
stdout: object # Generator yielding stdout lines
88
stderr: object # Generator yielding stderr lines
89
stdin: object # Stdin stream for shell input
90
exit_code: int # Shell exit code (available after close())
91
output: HostOutput # Complete output object
92
```
93
94
### Stdin Class
95
96
Manage stdin input for interactive shells, allowing you to send data to running commands.
97
98
```python { .api }
99
class Stdin:
100
def write(self, data):
101
"""
102
Write data to stdin.
103
104
Parameters:
105
- data (str or bytes): Data to write to stdin
106
107
Note: Data should end with newline if intended as command input
108
"""
109
110
def flush(self):
111
"""
112
Flush any pending stdin data.
113
114
Ensures all written data is sent to the remote shell immediately.
115
"""
116
```
117
118
### Stateful Command Execution
119
120
Execute multiple related commands in the same shell context, maintaining environment variables, working directory, and other shell state.
121
122
```python
123
# Single host interactive workflow
124
client = SSHClient('server.example.com', user='admin')
125
shell = client.open_shell()
126
127
# Set up environment (state persists)
128
shell.run('export APP_ENV=production')
129
shell.run('cd /opt/myapp')
130
shell.run('source venv/bin/activate')
131
132
# Execute commands in prepared environment
133
shell.run('python manage.py migrate')
134
shell.run('python manage.py collectstatic --noinput')
135
shell.run('systemctl restart myapp')
136
137
# Read output from all commands
138
for line in shell.stdout:
139
print(line)
140
141
# Close shell and get exit code
142
shell.close()
143
print(f"Shell exit code: {shell.exit_code}")
144
```
145
146
### Interactive Input with Stdin
147
148
Send input to interactive commands using the stdin stream for commands that require user input.
149
150
```python
151
# Interactive command requiring input
152
client = SSHClient('server.example.com', user='admin')
153
shell = client.open_shell()
154
155
# Start interactive command
156
shell.run('python -c "name = input(\'Enter name: \'); print(f\'Hello {name}\')"')
157
158
# Send input via stdin
159
shell.stdin.write('Alice\n')
160
shell.stdin.flush()
161
162
# Read response
163
for line in shell.stdout:
164
print(line) # Output: Hello Alice
165
166
shell.close()
167
168
# Multi-step interactive session
169
shell = client.open_shell()
170
171
# Start database client
172
shell.run('psql -U postgres -d mydb')
173
174
# Send SQL commands via stdin
175
shell.stdin.write('SELECT COUNT(*) FROM users;\n')
176
shell.stdin.flush()
177
178
shell.stdin.write('\\q\n') # Quit database client
179
shell.stdin.flush()
180
181
# Process output
182
for line in shell.stdout:
183
print(line)
184
185
shell.close()
186
```
187
188
### Parallel Interactive Workflows
189
190
Execute complex workflows across multiple hosts simultaneously while maintaining shell state on each host.
191
192
```python
193
# Parallel deployment workflow
194
hosts = ['web1.example.com', 'web2.example.com', 'web3.example.com']
195
client = ParallelSSHClient(hosts, user='deploy')
196
197
shells = client.open_shell()
198
199
# Execute deployment steps on all hosts
200
deployment_commands = [
201
'cd /opt/webapp',
202
'git fetch origin',
203
'git checkout v2.1.0',
204
'source venv/bin/activate',
205
'pip install -r requirements.txt',
206
'python manage.py migrate',
207
'systemctl restart webapp',
208
'systemctl status webapp'
209
]
210
211
# Run each command on all shells
212
for cmd in deployment_commands:
213
print(f"Executing: {cmd}")
214
for shell in shells:
215
shell.run(cmd)
216
217
# Wait a moment between commands
218
import time
219
time.sleep(2)
220
221
# Collect output from all hosts
222
for i, shell in enumerate(shells):
223
print(f"\n--- Output from {hosts[i]} ---")
224
for line in shell.stdout:
225
print(line)
226
227
shell.close()
228
print(f"Shell exit code: {shell.exit_code}")
229
```
230
231
### Error Handling in Interactive Shells
232
233
Handle errors and monitor command execution in interactive shell sessions.
234
235
```python
236
from pssh.exceptions import ShellError
237
238
try:
239
client = SSHClient('server.example.com', user='admin')
240
shell = client.open_shell()
241
242
# Execute potentially failing commands
243
shell.run('command_that_might_fail')
244
shell.run('echo "Command completed"')
245
246
# Check output for errors
247
output_lines = []
248
for line in shell.stdout:
249
output_lines.append(line)
250
if 'ERROR' in line or 'FAILED' in line:
251
print(f"Error detected: {line}")
252
253
shell.close()
254
255
if shell.exit_code != 0:
256
print(f"Shell ended with non-zero exit code: {shell.exit_code}")
257
258
except ShellError as e:
259
print(f"Shell error: {e}")
260
```
261
262
### Interactive Shells with Custom Encoding
263
264
Handle shells with different character encodings or binary output.
265
266
```python
267
# Shell with custom encoding
268
shell = client.open_shell(encoding='latin1')
269
270
# Commands that might produce non-UTF8 output
271
shell.run('cat /etc/passwd')
272
shell.run('ls -la /var/log')
273
274
for line in shell.stdout:
275
print(line) # Properly decoded using latin1
276
277
shell.close()
278
279
# Shell with binary/raw handling
280
shell = client.open_shell(encoding=None) # Raw bytes mode
281
282
shell.run('cat /bin/ls | head -n 1') # Binary output
283
for line in shell.stdout:
284
print(repr(line)) # Show raw bytes
285
286
shell.close()
287
```
288
289
## Advanced Interactive Shell Patterns
290
291
### Long-Running Interactive Sessions
292
293
Manage long-running interactive processes and real-time output monitoring.
294
295
```python
296
import time
297
from threading import Thread
298
299
def monitor_shell_output(shell, host):
300
"""Monitor shell output in real-time"""
301
try:
302
for line in shell.stdout:
303
print(f"[{host}] {line}")
304
except:
305
pass
306
307
# Start long-running process with real-time monitoring
308
client = SSHClient('server.example.com', user='admin')
309
shell = client.open_shell()
310
311
# Start background monitoring
312
monitor_thread = Thread(target=monitor_shell_output, args=(shell, 'server'))
313
monitor_thread.daemon = True
314
monitor_thread.start()
315
316
# Start long-running process
317
shell.run('tail -f /var/log/application.log')
318
319
# Let it run for a while
320
time.sleep(60)
321
322
# Stop the process and close shell
323
shell.run('pkill tail') # Stop tail command
324
shell.close()
325
```
326
327
### Multi-Stage Deployments
328
329
Execute complex deployment workflows with error checking and rollback capabilities.
330
331
```python
332
def safe_deployment_workflow(shell, host):
333
"""Execute deployment with rollback on failure"""
334
335
commands = [
336
('echo "Starting deployment"', True),
337
('cd /opt/webapp', True),
338
('cp -r current backup_$(date +%Y%m%d_%H%M%S)', True),
339
('git fetch origin', True),
340
('git checkout v2.0.0', True),
341
('source venv/bin/activate', True),
342
('pip install -r requirements.txt', True),
343
('python manage.py migrate', True),
344
('python manage.py test', False), # Allow this to fail
345
('systemctl restart webapp', True),
346
('sleep 5', True),
347
('curl -f http://localhost:8000/health', True),
348
('echo "Deployment successful"', True)
349
]
350
351
for cmd, critical in commands:
352
print(f"[{host}] Executing: {cmd}")
353
shell.run(cmd)
354
shell.run('echo "EXIT_CODE:$?"') # Capture exit code
355
356
# Check last command's exit code
357
last_output = []
358
for line in shell.stdout:
359
last_output.append(line)
360
if line.startswith('EXIT_CODE:'):
361
exit_code = int(line.split(':')[1])
362
if exit_code != 0 and critical:
363
print(f"[{host}] Critical command failed: {cmd}")
364
# Rollback
365
shell.run('systemctl stop webapp')
366
shell.run('git checkout HEAD~1')
367
shell.run('systemctl start webapp')
368
return False
369
elif exit_code != 0:
370
print(f"[{host}] Non-critical command failed: {cmd}")
371
break
372
373
return True
374
375
# Execute safe deployment on multiple hosts
376
hosts = ['web1.example.com', 'web2.example.com']
377
client = ParallelSSHClient(hosts, user='deploy')
378
shells = client.open_shell()
379
380
results = []
381
for i, shell in enumerate(shells):
382
success = safe_deployment_workflow(shell, hosts[i])
383
results.append((hosts[i], success))
384
shell.close()
385
386
# Report results
387
for host, success in results:
388
status = "SUCCESS" if success else "FAILED"
389
print(f"Deployment on {host}: {status}")
390
```
391
392
### Database Maintenance Workflows
393
394
Execute database maintenance tasks that require multiple sequential commands.
395
396
```python
397
def database_maintenance(shell, host):
398
"""Execute database maintenance workflow"""
399
400
# Set up database environment
401
shell.run('export PGPASSWORD=secretpassword')
402
shell.run('export PGUSER=dbadmin')
403
shell.run('export PGHOST=localhost')
404
shell.run('export PGDATABASE=production')
405
406
# Pre-maintenance checks
407
shell.run('echo "=== Database Status ==="')
408
shell.run('psql -c "SELECT version();"')
409
shell.run('psql -c "SELECT pg_size_pretty(pg_database_size(current_database()));"')
410
411
# Create backup
412
shell.run('echo "=== Creating Backup ==="')
413
shell.run('pg_dump production > /backup/production_$(date +%Y%m%d_%H%M%S).sql')
414
415
# Maintenance operations
416
shell.run('echo "=== Running Maintenance ==="')
417
shell.run('psql -c "VACUUM ANALYZE;"')
418
shell.run('psql -c "REINDEX DATABASE production;"')
419
420
# Post-maintenance verification
421
shell.run('echo "=== Verification ==="')
422
shell.run('psql -c "SELECT schemaname,tablename,n_tup_ins,n_tup_upd,n_tup_del FROM pg_stat_user_tables WHERE n_tup_ins+n_tup_upd+n_tup_del > 0;"')
423
424
# Collect all output
425
output_lines = []
426
for line in shell.stdout:
427
output_lines.append(line)
428
429
return output_lines
430
431
# Run maintenance on database servers
432
db_hosts = ['db1.example.com', 'db2.example.com']
433
client = ParallelSSHClient(db_hosts, user='postgres')
434
shells = client.open_shell()
435
436
for i, shell in enumerate(shells):
437
print(f"\n=== Maintenance on {db_hosts[i]} ===")
438
output = database_maintenance(shell, db_hosts[i])
439
440
for line in output:
441
print(f"[{db_hosts[i]}] {line}")
442
443
shell.close()
444
print(f"[{db_hosts[i]}] Shell exit code: {shell.exit_code}")
445
```
446
447
## Best Practices
448
449
### Resource Management
450
451
```python
452
# Always close shells to free resources
453
try:
454
shell = client.open_shell()
455
shell.run('some commands')
456
# Process output...
457
finally:
458
shell.close()
459
460
# Use context manager for automatic cleanup (recommended)
461
with client.open_shell() as shell:
462
shell.run('export ENV=production')
463
shell.run('cd /opt/app')
464
shell.run('python manage.py status')
465
466
# Process output
467
for line in shell.stdout:
468
print(line)
469
# Shell automatically closed when exiting with block
470
471
# Or use context-like pattern for functions
472
def with_shell(client, commands):
473
shell = client.open_shell()
474
try:
475
for cmd in commands:
476
shell.run(cmd)
477
return list(shell.stdout)
478
finally:
479
shell.close()
480
```
481
482
### Output Processing
483
484
```python
485
# Process output incrementally for long-running commands
486
shell = client.open_shell()
487
shell.run('long_running_command')
488
489
processed_lines = 0
490
for line in shell.stdout:
491
print(f"Line {processed_lines}: {line}")
492
processed_lines += 1
493
494
# Process in chunks
495
if processed_lines % 100 == 0:
496
print(f"Processed {processed_lines} lines so far...")
497
498
shell.close()
499
```
500
501
### Error Detection
502
503
```python
504
# Monitor for specific error patterns
505
shell = client.open_shell()
506
shell.run('risky_operation')
507
508
error_patterns = ['ERROR', 'FAILED', 'Exception', 'Traceback']
509
errors_found = []
510
511
for line in shell.stdout:
512
for pattern in error_patterns:
513
if pattern in line:
514
errors_found.append(line)
515
print(line)
516
517
shell.close()
518
519
if errors_found:
520
print("Errors detected:")
521
for error in errors_found:
522
print(f" {error}")
523
```