mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:23:03 +02:00
289 lines
7.6 KiB
Markdown
289 lines
7.6 KiB
Markdown
|
|
# Shell Script Quality & Efficiency Guidelines
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
This repository uses **three layers of shell script quality control**:
|
|||
|
|
|
|||
|
|
1. **shellcheck** - Syntax and common errors (pre-commit)
|
|||
|
|
2. **polling antipatterns detector** - Fork-storm prevention (pre-commit, NEW)
|
|||
|
|
3. **shell.instructions** - Best practices (in-editor, via Copilot)
|
|||
|
|
|
|||
|
|
## What Changed
|
|||
|
|
|
|||
|
|
### New: Polling Antipatterns Pre-commit Hook
|
|||
|
|
|
|||
|
|
**File**: `scripts/check_polling_antipatterns.sh`
|
|||
|
|
**Hook ID**: `no-polling-antipatterns`
|
|||
|
|
**When**: Automatically runs on `.sh` files during `pre-commit run` or `git commit`
|
|||
|
|
|
|||
|
|
The hook detects and **blocks commits** of shell scripts with these anti-patterns:
|
|||
|
|
|
|||
|
|
| Anti-pattern | Why bad | Detector |
|
|||
|
|
| --------------------------------------- | ------------------ | -------------------- |
|
|||
|
|
| `while true; do [check]; sleep 1; done` | 60k forks/hour | Loop + sleep pattern |
|
|||
|
|
| `$(date +...)` in monitoring | 10ms fork per call | Subprocess date |
|
|||
|
|
| `pgrep/xdotool` in polling | 5ms fork per call | Process inspection |
|
|||
|
|
| `\| awk \| grep \| tr` chains | Fork per pipe | Heavy piping |
|
|||
|
|
| `sleep 0.5` aggressive | Fork storm | Sub-second polling |
|
|||
|
|
|
|||
|
|
### Updated: Shell Instructions
|
|||
|
|
|
|||
|
|
**File**: `/home/kuhy/.copilot/instructions/shell.instructions.md`
|
|||
|
|
**New section**: "⚡ Efficient Polling & Monitoring Scripts"
|
|||
|
|
|
|||
|
|
Explains the **R1-R8 rules** for writing zero-fork polling scripts:
|
|||
|
|
|
|||
|
|
- R1: Zero forks in hot path
|
|||
|
|
- R2: Use /proc and /sys directly
|
|||
|
|
- R3: Event-driven over polling
|
|||
|
|
- R4: i3blocks `interval=persist`
|
|||
|
|
- R5: Increase polling intervals
|
|||
|
|
- R6: Cache expensive values
|
|||
|
|
- R7: Profile before deployment
|
|||
|
|
- R8: Recognize fork-storm signatures
|
|||
|
|
|
|||
|
|
## Usage
|
|||
|
|
|
|||
|
|
### For Developers
|
|||
|
|
|
|||
|
|
#### 1. Write compliant polling scripts
|
|||
|
|
|
|||
|
|
Follow the patterns in `.copilot/instructions/shell.instructions.md`:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
# ✅ Zero-fork polling script example
|
|||
|
|
|
|||
|
|
set -u
|
|||
|
|
|
|||
|
|
emit() {
|
|||
|
|
printf ' %s\n' "$1"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Read from /proc directly (no fork)
|
|||
|
|
read -r uptime_s _ < /proc/uptime
|
|||
|
|
current_time=${uptime_s%%.*}
|
|||
|
|
|
|||
|
|
# Event-driven if possible, else increase interval
|
|||
|
|
emit "Time: $current_time"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. Pre-commit runs automatically
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Commits that violate anti-patterns are blocked:
|
|||
|
|
git commit -m "Add new polling script"
|
|||
|
|
# ❌ BLOCKED if script violates rules
|
|||
|
|
|
|||
|
|
# Fix the script:
|
|||
|
|
# - Replace $(date) with /proc reads
|
|||
|
|
# - Replace while true + sleep with event-driven I/O
|
|||
|
|
# - Remove aggressive sleep intervals
|
|||
|
|
# - Reduce piped commands
|
|||
|
|
|
|||
|
|
git commit -m "Add new polling script"
|
|||
|
|
# ✅ PASSES
|
|||
|
|
|
|||
|
|
# Or run manually:
|
|||
|
|
pre-commit run no-polling-antipatterns --files my_script.sh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. Use diagnostic tools
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /home/kuhy/testsAndMisc
|
|||
|
|
|
|||
|
|
# Find all polling anti-patterns in repo
|
|||
|
|
./run.sh --diagnose
|
|||
|
|
|
|||
|
|
# Profile for 30s to find active fork storms
|
|||
|
|
./run.sh --profile 30
|
|||
|
|
|
|||
|
|
# Generate resource report
|
|||
|
|
./run.sh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### For Code Reviewers
|
|||
|
|
|
|||
|
|
When reviewing shell scripts:
|
|||
|
|
|
|||
|
|
1. **Check if hook ran**: Pre-commit output should show `no-polling-antipatterns` passed
|
|||
|
|
2. **Look for**:
|
|||
|
|
- `while true` + `sleep` → suggest event-driven
|
|||
|
|
- `$(date ...)` → suggest `/proc/uptime`
|
|||
|
|
- Multiple pipes → suggest bash builtins
|
|||
|
|
- `pgrep` in loops → suggest caching
|
|||
|
|
3. **Reference**: Point to shell.instructions section "⚡ Efficient Polling & Monitoring Scripts"
|
|||
|
|
|
|||
|
|
## Examples
|
|||
|
|
|
|||
|
|
### Example 1: Polling Loop ❌ → ✅
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# ❌ FAILS pre-commit check
|
|||
|
|
#!/bin/bash
|
|||
|
|
while true; do
|
|||
|
|
now=$(date +%s)
|
|||
|
|
echo "Current: $now"
|
|||
|
|
sleep 1
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# ✅ PASSES - uses /proc/uptime, adaptive sleep
|
|||
|
|
#!/bin/bash
|
|||
|
|
emit() { printf ' %s\n' "$1"; }
|
|||
|
|
|
|||
|
|
emit "$(initial_value)"
|
|||
|
|
while true; do
|
|||
|
|
read -r uptime_s _ < /proc/uptime
|
|||
|
|
emit "Current: ${uptime_s%%.*}"
|
|||
|
|
|
|||
|
|
if is_active; then
|
|||
|
|
sleep 0.5
|
|||
|
|
else
|
|||
|
|
sleep 3 # Adaptive polling
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Example 2: i3blocks Status Script ❌ → ✅
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# ❌ INEFFICIENT - forked every 5 seconds
|
|||
|
|
#!/bin/bash
|
|||
|
|
# battery.sh
|
|||
|
|
cap=$(cat /sys/class/power_supply/BAT0/capacity)
|
|||
|
|
echo " $cap%"
|
|||
|
|
|
|||
|
|
# i3blocks config:
|
|||
|
|
# [battery]
|
|||
|
|
# interval=5
|
|||
|
|
# Result: 720 checks/hour × 1 fork = 720 forks/hour
|
|||
|
|
|
|||
|
|
# ✅ OPTIMIZED - zero fork when idle
|
|||
|
|
#!/bin/bash
|
|||
|
|
# battery.sh
|
|||
|
|
set -u
|
|||
|
|
emit() { printf ' %s%%\n' "$1"; }
|
|||
|
|
|
|||
|
|
read -r cap < /sys/class/power_supply/BAT0/capacity
|
|||
|
|
emit "$cap"
|
|||
|
|
|
|||
|
|
# Watch for power supply changes (blocks when idle)
|
|||
|
|
udevadm monitor --udev --property --subsystem-match=power_supply |
|
|||
|
|
while IFS='=' read -r key value || true; do
|
|||
|
|
[[ $key == POWER_SUPPLY_CAPACITY ]] || continue
|
|||
|
|
read -r cap < /sys/class/power_supply/BAT0/capacity
|
|||
|
|
emit "$cap"
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# i3blocks config:
|
|||
|
|
# [battery]
|
|||
|
|
# interval=persist
|
|||
|
|
# Result: Zero CPU when plugged in, one fork per cable plug/unplug
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Example 3: Process Monitoring ❌ → ✅
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# ❌ FAILS - pgrep in loop = fork per second × N processes
|
|||
|
|
while true; do
|
|||
|
|
if pgrep -f "python" > /dev/null; then
|
|||
|
|
echo "Python running"
|
|||
|
|
fi
|
|||
|
|
sleep 1
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# ✅ PASSES - adaptive polling with cached check
|
|||
|
|
focus_running=0
|
|||
|
|
while true; do
|
|||
|
|
if is_focus_app_running; then
|
|||
|
|
focus_running=1
|
|||
|
|
else
|
|||
|
|
if ((focus_running)); then
|
|||
|
|
echo "Focus ended"
|
|||
|
|
focus_running=0
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if ((focus_running)); then
|
|||
|
|
sleep 0.5 # Active
|
|||
|
|
else
|
|||
|
|
sleep 3 # Idle
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Integration with Existing Tools
|
|||
|
|
|
|||
|
|
### With shellcheck
|
|||
|
|
|
|||
|
|
Pre-commit runs both:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pre-commit run shellcheck --files my_script.sh
|
|||
|
|
pre-commit run no-polling-antipatterns --files my_script.sh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### With formatting
|
|||
|
|
|
|||
|
|
Polling linter runs alongside code formatters:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pre-commit run --all-files
|
|||
|
|
# → trailing-whitespace, shellcheck, no-polling-antipatterns, ruff, etc.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### With CI/CD
|
|||
|
|
|
|||
|
|
Pre-commit hooks are required before:
|
|||
|
|
|
|||
|
|
- `git commit` (pre-commit hook)
|
|||
|
|
- `git push` (pre-push hook, includes slower tests)
|
|||
|
|
|
|||
|
|
Scripts that fail the polling detector must be fixed before pushing.
|
|||
|
|
|
|||
|
|
## Exemptions
|
|||
|
|
|
|||
|
|
Some scripts are exempted (e.g., C/CPP test utilities):
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# .pre-commit-config.yaml
|
|||
|
|
- id: no-polling-antipatterns
|
|||
|
|
exclude: ^(\.git/|C/|CPP/|phone_focus_mode/lib/tests/)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
To add an exemption, modify `.pre-commit-config.yaml` and explain why in a comment.
|
|||
|
|
|
|||
|
|
## Common Questions
|
|||
|
|
|
|||
|
|
**Q: My status-bar script is trivial—do I need to optimize it?**
|
|||
|
|
A: Yes! A 100-byte script that forks once per second still costs ~30 CPU-seconds per day. Use `/proc` reads instead.
|
|||
|
|
|
|||
|
|
**Q: Can I suppress the antipatterns check?**
|
|||
|
|
A: No, by design. (See `.git/hooks/` and `userMemory: lint-rules.md` for why). Instead, fix the underlying issue—it's usually a 2-line change.
|
|||
|
|
|
|||
|
|
**Q: What if my script MUST call `date`?**
|
|||
|
|
A: Use bash builtin: `printf -v now '%(%Y-%m-%d)T' -1` (no fork).
|
|||
|
|
|
|||
|
|
**Q: Why block on every commit?**
|
|||
|
|
A: Polling fork-storms cause system-wide slowdown. This saves hundreds of CPU-hours per year per developer machine.
|
|||
|
|
|
|||
|
|
## Resources
|
|||
|
|
|
|||
|
|
- **Shell Instructions**: `.copilot/instructions/shell.instructions.md` (in-editor, Copilot knowledge)
|
|||
|
|
- **Efficiency Skill**: `.github/skills/efficient-polling-scripts/SKILL.md` (detailed patterns)
|
|||
|
|
- **Live Tools**:
|
|||
|
|
- `./run.sh --diagnose` - Audit repo for patterns
|
|||
|
|
- `./run.sh --profile 30` - Profile live system
|
|||
|
|
- `scripts/check_polling_antipatterns.sh` - Manual check
|
|||
|
|
|
|||
|
|
## Summary
|
|||
|
|
|
|||
|
|
| Layer | Tool | Purpose |
|
|||
|
|
| --------------------- | ----------------------------- | -------------------------------------------- |
|
|||
|
|
| **1. Syntax** | shellcheck | Catch bugs, unused variables, quoting issues |
|
|||
|
|
| **2. Efficiency** | check_polling_antipatterns.sh | Block fork-storm patterns |
|
|||
|
|
| **3. Best Practices** | shell.instructions.md | Guide developers toward optimal patterns |
|
|||
|
|
|
|||
|
|
All three work together to ensure shell scripts are **safe, efficient, and maintainable**.
|