I'm going to tell you a true story. It's a little embarrassing.
A few months ago, at 11:47 PM, I shipped the wrong build to the Play Store.
Two hours before our launch announcement. A debug AAB. Not signed. Rejected on contact. I sat in my chair making the universal sound of an engineer who has just understood the past two minutes — a single, low, animal nooooo.
I fixed it in seven minutes. The wrong build never reached a user. Nobody else even noticed.
What stuck with me wasn't the mistake. It was that I had been doing the same twelve-step release ritual for four months and had never once gotten it perfectly right on the first try. The mistakes were always small. Wrong flag. Forgot a step. Pasted the wrong SHA. Each one cost ten minutes. None of them were technically a big deal.
But I'd been quietly leaking thirty to sixty minutes a week to a process I supposedly knew how to do.
So that weekend I did the dumbest possible thing: I logged my own terminal for a week.
What the data said
Command sequences typed ≥3 times: 11
Most-repeated sequence: release ritual (19×)
Average time per ritual: 12 minutes
Time spent retyping that week: 3.8 hours
Mistakes during those retypes: 5
Time lost to those mistakes: ~80 minutes
Four hours. Of that one ritual.
The bench-press moment for me wasn't the number. It was looking at the data and realizing: I wasn't slow at my job. I was slow at the parts of my job that should have been a script.
I'd been measuring myself against the wrong baseline. The real baseline isn't how fast can a human do this? — it's how fast can a human delegate this?
The forty-line file
I sat down Sunday afternoon and wrote a bash script. Not a clever one. Forty lines, mostly echo and flutter build. Took ninety minutes including testing.
A heavily-trimmed version of what I wrote:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
VERSION="${1:?Usage: $0 <version+buildCode>}"
# Validate before mutating
[[ -z "$(git status --porcelain)" ]] || { echo "Tree dirty"; exit 1; }
git rev-parse "v${VERSION}" >/dev/null 2>&1 && { echo "Tag exists"; exit 1; }
echo "▸ Bumping pubspec to ${VERSION}…"
sed -i.bak "s/^version:.*/version: ${VERSION}/" pubspec.yaml && rm pubspec.yaml.bak
echo "▸ Building…"
flutter clean >/dev/null
flutter pub get >/dev/null
flutter build appbundle --release
flutter build apk --release
echo "▸ Tagging…"
git add pubspec.yaml
git commit -m "release: ${VERSION}"
git tag "v${VERSION}"
echo "✓ Done. AAB: build/app/outputs/bundle/release/app-release.aab"
That's it. There's no magic. The pattern is just write down what you would've typed.
What changed:
| Before | After | |
|---|---|---|
| Time per release | 12 min hands-on | 30 seconds of attention + build time |
| Mistakes per month | ~3 | 0 |
| Releases per month | 4 (I avoided it) | 11 (I stopped avoiding it) |
The last row is the one I want to talk about.
The thing nobody told me about bash scripts
I expected to save time. I did — about six hours a month.
What I did not expect was the second row: I started releasing almost 3× more often.
Same engineer. Same project. Same code velocity. But once the release stopped feeling expensive, I stopped flinching at the idea of cutting one. Bug fix on Tuesday afternoon? Ship it Tuesday afternoon. Used to be: bug fix Tuesday, ship Friday after I'd worked up the energy to do the ritual.
This is the part I want you to internalize, because the productivity blogs all miss it:
Bash scripts don't save time. They remove dread. Dread is what stops you from doing the right thing.
The 12-minute ritual wasn't really 12 minutes. It was a 12-minute tax my brain was paying every time I considered shipping. The brain doesn't do cost-benefit analysis on small recurring tasks. It just labels them annoying and avoids them when it can.
A script doesn't just compress the 12 minutes. It removes the label. The task becomes weightless. And weightless tasks happen more often.
Things I started doing weekly only after I scripted them:
- Cutting Play Store releases.
- Wiping and re-seeding my local DB before a feature.
- Generating a fresh
.envfor a teammate. - Running the slow integration test suite on a feature branch.
- Posting a release note to our internal Slack channel.
None of these were hard. Each one was 5–15 minutes of typing. So I just… didn't, most of the time. Now I do. Same person, same skills, totally different output.
The rule I now actually follow
If I'm typing the same sequence for the third time, I stop and write the script. The third time is the cheapest time — I already know the edge cases. The tenth time, I have to remember them.
Five mechanical rules I always follow inside the script itself:
-
set -euo pipefailat the top. Fail fast, fail loud. -
cd "$(dirname "${BASH_SOURCE[0]}")/.."on line 2. Now it works from anywhere. - Validate before mutating. Cheap checks first; expensive work last.
- Echo what you're about to do. Future-you, tired, will need the breadcrumbs.
-
Ask before anything destructive. A two-line
[y/N]prompt costs nothing.
That's the entire framework. There is no level beyond this.
Where your scripts are hiding
If you're reading this thinking "yeah but I don't have anything to automate" — you do. You just haven't labeled it yet. Patterns hide inside the things you've been calling "just my workflow."
Here are eight categories where scripts almost always lurk. At least three of them apply to your job. Probably six.
1. Build & release rituals
The pubspec / package.json bump → build → tag → upload → notify dance. The single highest-leverage category — it touches production, it's done often, and the cost of getting it wrong is the steepest. Mine was a Flutter release.sh. Yours might be deploy.sh, publish-npm.sh, cut-release.sh.
2. Environment bootstrap
Setting up a fresh machine or a new teammate. git clone → install deps → seed env → start services → run migrations → open IDE. The teammate who joins next week will save half a day. You'll save the next time your laptop dies.
3. Database & state resets
Wipe local DB → run migrations → seed test data → reset Redis → clear queue. The ritual you do every time you start a new feature, and the one you skip when you're tired (which is the day you ship a bug because your local data was stale).
4. Local dev orchestration
Start the API, start the worker, start the frontend, tail all three logs in colored panes. Three commands in three terminals becomes one command in one. Bonus: it works for your teammate too.
5. Test slicing
"Run only the integration tests for the auth module, against the staging DB, with verbose output, and stop on first failure." That's not a command. That's a script with three flags.
6. Git hygiene
Sync the fork. Prune merged branches. Squash the 14-commit feature branch into 3 clean ones. Rebase onto main with a clean diff. The "I should clean up my branches" task that everybody promises themselves and nobody does — until it's a script.
7. Cleanup & reset
Clear caches. Kill stuck Docker containers. Wipe node_modules and reinstall. Reset CocoaPods. The "turn it off and on again" of your specific stack. Three minutes of typing becomes a single command you trust at 11 PM.
8. Communication & reporting
Post "deploying X" to a Slack channel. Generate this week's changelog from commits. Send the standup status. Email the weekly metrics screenshot. Anything you write that follows a template is a script with printf and a webhook.
If you can name the sequence, you can script it. The hard part isn't writing the script — it's letting yourself believe you're allowed to spend an hour on something that isn't a "real task."
What I'd tell my year-ago self
The folder of bash scripts isn't what makes someone a productive engineer. It's the evidence that they think about their own time the way they think about a system: bottlenecks worth fixing, not friction worth absorbing.
You don't need to be a "shell wizard." You need to be willing to spend ninety minutes on a Sunday writing forty lines of bash so that your future self stops paying interest on a debt you didn't realize you were carrying.
Open a terminal. Look at your last hundred lines of history. Find the sequence you've typed three times this week.
That's your script.
Top comments (0)