or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdfile-transfer.mdindex.mdinteractive-shells.mdoutput-handling.mdparallel-operations.mdsingle-host-operations.md

interactive-shells.mddocs/

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

```