or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

command-execution.mdcontrib.mderror-handling.mdindex.mdio-handling.mdprocess-management.mdutilities.md

utilities.mddocs/

0

# Utilities

1

2

Additional utilities including directory manipulation, enhanced globbing, logging, and command introspection tools. These utilities enhance the core sh functionality with convenient helpers for common shell integration scenarios.

3

4

## Capabilities

5

6

### Directory Context Management

7

8

Change working directory temporarily using context managers, similar to shell pushd/popd functionality.

9

10

```python { .api }

11

@contextmanager

12

def pushd(path):

13

"""

14

Context manager to temporarily change working directory.

15

16

Parameters:

17

- path: str/Path = directory to change to

18

19

Yields:

20

str: The new current working directory

21

22

Raises:

23

OSError: If directory doesn't exist or can't be accessed

24

"""

25

```

26

27

Usage examples:

28

29

```python

30

import sh

31

32

# Temporary directory change

33

original_dir = sh.pwd().strip()

34

print(f"Starting in: {original_dir}")

35

36

with sh.pushd("/tmp"):

37

current = sh.pwd().strip()

38

print(f"Now in: {current}")

39

40

# All commands run in /tmp

41

files = sh.ls("-la")

42

sh.touch("test_file.txt")

43

44

# Automatically back to original directory

45

back_dir = sh.pwd().strip()

46

print(f"Back in: {back_dir}")

47

assert back_dir == original_dir

48

49

# Nested directory changes

50

with sh.pushd("/tmp"):

51

print(f"Level 1: {sh.pwd().strip()}")

52

53

with sh.pushd("subdir"):

54

print(f"Level 2: {sh.pwd().strip()}")

55

sh.touch("nested_file.txt")

56

57

print(f"Back to level 1: {sh.pwd().strip()}")

58

59

print(f"Back to original: {sh.pwd().strip()}")

60

61

# Error handling with pushd

62

try:

63

with sh.pushd("/nonexistent"):

64

print("This won't execute")

65

except OSError as e:

66

print(f"Directory change failed: {e}")

67

```

68

69

### Enhanced Globbing

70

71

Enhanced glob functionality that integrates with sh commands and provides additional features.

72

73

```python { .api }

74

def glob(pattern, *args, **kwargs):

75

"""

76

Enhanced glob function that returns results compatible with sh commands.

77

78

Parameters:

79

- pattern: str = glob pattern (supports *, ?, [], {})

80

- *args: additional patterns to match

81

- **kwargs: glob options

82

83

Returns:

84

GlobResults: List-like object with enhanced functionality

85

"""

86

87

class GlobResults(list):

88

"""Enhanced list of glob results with additional methods."""

89

def __init__(self, results): ...

90

```

91

92

Usage examples:

93

94

```python

95

import sh

96

97

# Basic globbing

98

log_files = sh.glob("/var/log/*.log")

99

print(f"Found {len(log_files)} log files")

100

101

# Use glob results with sh commands

102

for log_file in log_files:

103

size = sh.wc("-l", log_file)

104

print(f"{log_file}: {size.strip()} lines")

105

106

# Multiple patterns

107

config_files = sh.glob("*.conf", "*.cfg", "*.ini")

108

print("Configuration files:", config_files)

109

110

# Complex patterns

111

python_files = sh.glob("**/*.py", recursive=True)

112

test_files = sh.glob("**/test_*.py", "**/tests/*.py")

113

114

# Integration with other commands

115

sh.tar("czf", "backup.tar.gz", *sh.glob("*.txt", "*.md"))

116

117

# Filter glob results

118

large_files = []

119

for file_path in sh.glob("*"):

120

try:

121

size = int(sh.stat("-c", "%s", file_path))

122

if size > 1024 * 1024: # > 1MB

123

large_files.append(file_path)

124

except sh.ErrorReturnCode:

125

pass # Skip files we can't stat

126

127

print(f"Large files: {large_files}")

128

```

129

130

### Command Execution Logging

131

132

Built-in logging system for tracking command execution and debugging.

133

134

```python { .api }

135

class Logger:

136

"""

137

Memory-efficient command execution logger.

138

139

Attributes:

140

- name: str = logger name

141

- context: dict = additional context information

142

"""

143

144

def __init__(self, name: str, context: dict = None): ...

145

146

def log(self, level: str, message: str): ...

147

def info(self, message: str): ...

148

def debug(self, message: str): ...

149

def warning(self, message: str): ...

150

def error(self, message: str): ...

151

```

152

153

```python { .api }

154

def __call__(self, *args, _log=None, **kwargs):

155

"""

156

Execute command with logging.

157

158

Parameters:

159

- _log: Logger/bool = logger instance or True for default logging

160

161

Returns:

162

str: Command output

163

"""

164

```

165

166

Usage examples:

167

168

```python

169

import sh

170

import logging

171

172

# Enable default logging

173

logging.basicConfig(level=logging.DEBUG)

174

175

# Log all command executions

176

result = sh.ls("-la", _log=True)

177

178

# Custom logger

179

custom_logger = sh.Logger("deployment", {"version": "1.2.3", "env": "production"})

180

181

sh.git("pull", _log=custom_logger)

182

sh.docker("build", "-t", "myapp:latest", ".", _log=custom_logger)

183

sh.docker("push", "myapp:latest", _log=custom_logger)

184

185

# Conditional logging

186

debug_mode = True

187

if debug_mode:

188

logger = sh.Logger("debug")

189

else:

190

logger = None

191

192

sh.make("build", _log=logger)

193

194

# Log analysis

195

class CommandAuditor:

196

def __init__(self):

197

self.commands = []

198

self.logger = sh.Logger("auditor")

199

self.logger.log = self._capture_log

200

201

def _capture_log(self, level, message):

202

self.commands.append({

203

'level': level,

204

'message': message,

205

'timestamp': time.time()

206

})

207

208

def get_summary(self):

209

return {

210

'total_commands': len(self.commands),

211

'errors': len([c for c in self.commands if c['level'] == 'error']),

212

'commands': self.commands

213

}

214

215

auditor = CommandAuditor()

216

sh.ls(_log=auditor.logger)

217

sh.pwd(_log=auditor.logger)

218

print(auditor.get_summary())

219

```

220

221

### Command Introspection

222

223

Tools for inspecting and analyzing commands before or after execution.

224

225

```python { .api }

226

class Command:

227

def __str__(self) -> str:

228

"""String representation showing executable path and baked arguments."""

229

230

def __repr__(self) -> str:

231

"""Formal string representation of the Command object."""

232

233

def __eq__(self, other) -> bool:

234

"""Compare Command objects for equality."""

235

```

236

237

Usage examples:

238

239

```python

240

import sh

241

242

# Command discovery and validation

243

def validate_dependencies(commands):

244

"""Check if required commands are available."""

245

missing = []

246

available = []

247

248

for cmd_name in commands:

249

try:

250

cmd = sh.Command(cmd_name)

251

available.append({

252

'name': cmd_name,

253

'command': str(cmd),

254

'version': get_version(cmd)

255

})

256

except sh.CommandNotFound:

257

missing.append(cmd_name)

258

259

return available, missing

260

261

def get_version(cmd):

262

"""Try to get version information for a command."""

263

version_flags = ['--version', '-V', '-v']

264

265

for flag in version_flags:

266

try:

267

output = cmd(flag)

268

return output.split('\n')[0] # First line usually has version

269

except sh.ErrorReturnCode:

270

continue

271

272

return "unknown"

273

274

# Check deployment dependencies

275

required_tools = ['git', 'docker', 'kubectl', 'helm']

276

available, missing = validate_dependencies(required_tools)

277

278

print("Available tools:")

279

for tool in available:

280

print(f" {tool['name']}: {tool['command']} ({tool['version']})")

281

282

if missing:

283

print(f"Missing tools: {missing}")

284

exit(1)

285

286

# Command introspection

287

git_cmd = sh.git

288

print(f"Git command: {git_cmd}")

289

print(f"Git string representation: {str(git_cmd)}")

290

print(f"Git repr: {repr(git_cmd)}")

291

292

# Command comparison

293

def compare_commands(*cmd_names):

294

"""Compare different commands and their capabilities."""

295

results = {}

296

297

for name in cmd_names:

298

try:

299

cmd = sh.Command(name)

300

cmd_str = str(cmd)

301

results[name] = {

302

'available': True,

303

'command': cmd_str,

304

'repr': repr(cmd)

305

}

306

except sh.CommandNotFound:

307

results[name] = {'available': False}

308

309

return results

310

311

# Compare different versions of python

312

python_variants = compare_commands('python', 'python3', 'python3.9', 'python3.10')

313

for name, info in python_variants.items():

314

if info['available']:

315

print(f"{name}: {info['command']}")

316

else:

317

print(f"{name}: not available")

318

```

319

320

### Advanced Stream Buffering

321

322

Low-level stream buffering implementation for advanced users who need fine-grained control over I/O processing.

323

324

```python { .api }

325

class StreamBufferer:

326

"""

327

Internal buffering implementation for command I/O streams.

328

329

Most users don't need this - it's primarily used internally by sh

330

but available for advanced buffering control scenarios.

331

332

Parameters:

333

- buffer_size: int = 0 (unbuffered), 1 (line-buffered), N (N-byte buffered)

334

- encoding: str = character encoding for text processing

335

"""

336

337

def __init__(self, buffer_size: int = 0, encoding: str = 'utf-8'): ...

338

```

339

340

Usage examples:

341

342

```python

343

import sh

344

345

# Most users should use standard command execution

346

# This is only for advanced scenarios requiring custom buffering control

347

bufferer = sh.StreamBufferer(buffer_size=1024, encoding='utf-8')

348

349

# Note: StreamBufferer is primarily an internal implementation detail

350

# Standard sh command execution handles buffering automatically

351

```

352

353

### Environment and Configuration

354

355

Utilities for managing environment variables and command configuration.

356

357

```python { .api }

358

def __call__(self, *args, _env=None, _cwd=None, **kwargs):

359

"""

360

Execute command with environment customization.

361

362

Parameters:

363

- _env: dict = environment variables to set

364

- _cwd: str = working directory for command

365

366

Returns:

367

str: Command output

368

"""

369

```

370

371

Usage examples:

372

373

```python

374

import sh

375

import os

376

377

# Environment management

378

def with_env(**env_vars):

379

"""Context manager for temporary environment variables."""

380

old_env = {}

381

382

try:

383

# Set new environment variables

384

for key, value in env_vars.items():

385

old_env[key] = os.environ.get(key)

386

os.environ[key] = str(value)

387

yield

388

finally:

389

# Restore old environment

390

for key, old_value in old_env.items():

391

if old_value is None:

392

os.environ.pop(key, None)

393

else:

394

os.environ[key] = old_value

395

396

# Usage

397

with with_env(DEBUG=1, LOG_LEVEL="verbose"):

398

sh.my_app("--config", "production.conf")

399

400

# Per-command environment

401

current_env = os.environ.copy()

402

current_env.update({

403

'PYTHONPATH': '/custom/path',

404

'DEBUG': '1'

405

})

406

407

result = sh.python("script.py", _env=current_env)

408

409

# Configuration helpers

410

class ConfigManager:

411

def __init__(self, config_file="config.json"):

412

self.config = self.load_config(config_file)

413

414

def load_config(self, file_path):

415

"""Load configuration from file."""

416

try:

417

import json

418

with open(file_path) as f:

419

return json.load(f)

420

except FileNotFoundError:

421

return {}

422

423

def get_env_for_service(self, service_name):

424

"""Get environment variables for a specific service."""

425

service_config = self.config.get(service_name, {})

426

env = os.environ.copy()

427

env.update(service_config.get('env', {}))

428

return env

429

430

def run_service_command(self, service_name, command, *args):

431

"""Run a command with service-specific configuration."""

432

env = self.get_env_for_service(service_name)

433

cwd = self.config.get(service_name, {}).get('working_dir')

434

435

return command(*args, _env=env, _cwd=cwd)

436

437

# Usage

438

config = ConfigManager("services.json")

439

result = config.run_service_command("web", sh.npm, "start")

440

441

# Path management utilities

442

def ensure_command_available(cmd_name, install_hint=None):

443

"""Ensure a command is available, provide installation hint if not."""

444

try:

445

cmd = sh.Command(cmd_name)

446

return cmd

447

except sh.CommandNotFound:

448

hint = install_hint or f"Install {cmd_name} to continue"

449

raise sh.CommandNotFound(f"{cmd_name} not found. {hint}")

450

451

# Usage

452

git = ensure_command_available('git', 'Install git: sudo apt-get install git')

453

docker = ensure_command_available('docker', 'Install Docker: https://docs.docker.com/install/')

454

```

455

456

### Environment Variable Access

457

458

Access environment variables directly through the sh module interface using attribute access.

459

460

```python { .api }

461

# Environment variables are accessible as module attributes

462

# sh.ENVIRONMENT_VARIABLE returns the value of $ENVIRONMENT_VARIABLE

463

```

464

465

Usage examples:

466

467

```python

468

import sh

469

import os

470

471

# Access environment variables via sh module

472

home_dir = sh.HOME

473

print(f"Home directory: {home_dir}")

474

475

# Access PATH environment variable

476

path_var = sh.PATH

477

print(f"PATH: {path_var}")

478

479

# Check if environment variable exists

480

try:

481

custom_var = sh.MY_CUSTOM_VAR

482

print(f"Custom variable: {custom_var}")

483

except sh.CommandNotFound:

484

print("MY_CUSTOM_VAR not set")

485

486

# Compare with os.environ access

487

print("Via sh.HOME:", sh.HOME)

488

print("Via os.environ:", os.environ.get('HOME'))

489

490

# Dynamic environment variable access

491

def get_env_var(var_name):

492

"""Get environment variable via sh module."""

493

try:

494

return getattr(sh, var_name)

495

except sh.CommandNotFound:

496

return None

497

498

user = get_env_var('USER')

499

shell = get_env_var('SHELL')

500

editor = get_env_var('EDITOR')

501

502

print(f"User: {user}, Shell: {shell}, Editor: {editor}")

503

```

504

505

### Module Interface and Context Management

506

507

Advanced module interface features including the deprecated _args context manager and module-level baking functionality.

508

509

```python { .api }

510

def _args(**kwargs):

511

"""

512

Deprecated context manager for setting default command arguments.

513

514

Note: This is deprecated. Consider using sh.bake() or Command.bake() instead.

515

516

Parameters:

517

- **kwargs: Default arguments to apply to all commands in context

518

519

Returns:

520

ContextManager: Context manager for temporary default arguments

521

"""

522

523

def bake(**kwargs):

524

"""

525

Create a new sh module instance with baked-in default arguments.

526

527

Parameters:

528

- **kwargs: Default execution options for all commands

529

530

Returns:

531

SelfWrapper: New sh module instance with default arguments

532

"""

533

```

534

535

Usage examples:

536

537

```python

538

import sh

539

540

# Module-level baking (recommended approach)

541

sh_verbose = sh.bake(_out=lambda line: print(f"CMD: {line.strip()}"))

542

543

# All commands through sh_verbose will have verbose output

544

sh_verbose.ls("-la")

545

sh_verbose.pwd()

546

547

# Create sh instance with specific environment

548

production_sh = sh.bake(_env={"ENVIRONMENT": "production"})

549

production_sh.deploy_script()

550

551

# Create sh instance with common options

552

background_sh = sh.bake(_bg=True)

553

proc1 = background_sh.long_running_task()

554

proc2 = background_sh.another_task()

555

556

# Legacy _args usage (deprecated, avoid in new code)

557

with sh._args(_timeout=30):

558

# All commands in this block have 30-second timeout

559

sh.curl("http://slow-server.com")

560

sh.wget("http://large-file.com/download")

561

562

# Preferred modern approach using bake

563

timeout_sh = sh.bake(_timeout=30)

564

timeout_sh.curl("http://slow-server.com")

565

timeout_sh.wget("http://large-file.com/download")

566

```