今日已更新 344 条资讯 | 累计 37249 条内容
关于我们

标签:#bash

找到 19 篇相关文章

AI 资讯

"Log this once" is a tense change, not a rate limit

A sensor on my machine returned nothing at all — empty stdout, empty stderr, exit code 2 — on every invocation for 36 days. It was not crashed. It was not misconfigured. It was doing exactly what one line of well-intentioned code told it to do: announce a condition once . The line looked like this, and I suspect you have written it: if [ ! -f " $OFFLINEFILE " ] ; then echo "body context n/a — phone unreachable" > &2 touch " $OFFLINEFILE " fi exit 2 Read it as a rate limiter and it is obviously fine: don't spam the log with the same message every five minutes. Read it as what it actually is and it is a bug, because the guard does not limit a rate. It changes the tense of the sentence. Every number, code listing, and command output below was re-measured on the machine while writing this, not quoted from the commit that fixed it. Two of the things I expected to find turned out to be false; both are in section 6, and one of them is the most interesting part. 1. Present tense, past tense phone unreachable is a claim in the present tense . It is a statement about the world right now, and it is what a reader of this tool wants: is the body sensor readable at this moment? Wrapping it in [ ! -f "$SENTINEL" ] silently rewrites it into the past tense : the phone became unreachable, at some earlier point, at least once. That is a different proposition. It is true exactly once per transition and false forever after, which is why the guard can never fire twice, and why the sentinel's own mtime is the only surviving record of when the sentence was last true. The two propositions coincide on the first run. That is the whole trap. A first-time-only notice is indistinguishable from a live one for the length of one invocation, which is exactly the length of the test you will write for it. 2. What the reader got instead Here is the tool, before the fix, run twice in a row against a phone that is genuinely away. I pulled the pre-fix version straight out of git into a scratch path and ra

2026-08-28 原文 →
开发者

Blue-green deployment that left the old environment running for weeks, doubling infrastructure cost

The deploy worked. The bill doubled. The blue-green cutover went perfectly. Traffic shifted to green, health checks passed, the team signed off, and moved on. It was one of those rare deployments that goes exactly as planned. Six weeks later, a cost anomaly surfaced in the monthly AWS review. Infrastructure spend had been running at roughly double what it should have been since the deployment date. Every EC2 instance, every RDS node, every load balancer from the blue environment was still running. Serving zero traffic. Billed at full price. For six weeks. Nobody had decommissioned it because nobody owned it after cutover. The team that ran the deployment assumed operations would clean it up. Operations assumed the team that deployed it would tear it down. The blue environment sat in a perfect ownership gap, healthy and idle and expensive, while both teams closed their tickets and moved on. This is the part blue-green deployment guides don't emphasize enough. The strategy is excellent for zero downtime releases and instant rollback capability. The rollback window is the dangerous part. It's open-ended by default, which means the old environment stays alive until someone makes a deliberate decision to shut it down. That decision requires ownership, and ownership requires someone to be responsible for it after the deployment is considered done. The fix is treating decommissioning as part of the deployment itself, not cleanup that happens afterward. Tag every blue environment resource at launch with a TTL: aws ec2 create-tags \ --resources i-1234567890abcdef0 \ --tags Key = DeploymentColor,Value = blue \ Key = CutoverDate,Value = 2026-01-14 \ Key = TTL,Value = 2026-01-21 Then wire Cost Anomaly Detection to alert when a specific environment tag is still generating spend past its TTL. The old environment doesn't get to become invisible just because traffic moved away from it. The deeper issue is that blue-green deployments create a window of parallel infrastructure that m

2026-08-27 原文 →
AI 资讯

Which Skill Is Quietly Burning Your Tokens? Find Out From transcript.jsonl

Your monthly Claude Code bill went up 20%. You know that much. What you don't know is which Skill did it — and nothing in the tooling will tell you. Run /usage in Claude Code and you get claude-sonnet-4-6: ¥3,240 — a per-model total and nothing else . "More expensive than last week" is visible. "Which Skill caused it" is not. usage-breakdown.sh closes that gap. It's a 106-line shell script that parses transcript.jsonl with Python and tallies call counts per Skill, Agent, and MCP server using Counter . This article walks through how the script works and how to run it, with the actual code and actual numbers. Why This Approach Works What Claude Code Is Actually Recording Claude Code streams every operation during a session into .jsonl files under ~/.claude/projects/ . It's JSONL — one event per line, one file per session. The files sit under a <project-id>/ directory. The skeleton of a single record looks like this: { "message" : { "role" : "assistant" , "content" : [ { "type" : "tool_use" , "name" : "Skill" , "input" : { "skill" : "pre-completion-self-audit" } } ] } } Inside message.content[] sit "type": "tool_use" blocks. The name field is the name of the tool that was invoked. The Bash tool, the Edit tool, the Skill tool, the Agent tool, MCP calls — all of it is recorded in this same format. Once I noticed that, the thought was: run this through a Counter and everything becomes visible. For the Skill tool, the skill name lives in input.skill ; for the Agent tool it's input.subagent_type ; and for MCP servers, the tool-name convention mcp__<server>__<tool> lets you extract the server name by splitting on __ . The structure is consistent, so the parser comes out surprisingly simple. What /usage Doesn't Tell You What Claude Code's /usage command outputs is a per-model cost total for a period. Model Cost claude-sonnet-4-6 ¥3,240 claude-opus-4-8 ¥ 892 Useful as far as it goes, but the breakdown of that cost is invisible . You can't see which session, which Skill, how ma

2026-08-26 原文 →
AI 资讯

Four Alarm Slots, Three Failure Modes: Building a Nightly Drain That Survives Sleep, Races, and Timeouts

Every night my Mac quietly rewrites my long-term memory. Not metaphorically — a shell script drains that day's Claude Code conversation logs into an Obsidian vault, commits them to a private repo, and leaves a briefing on my desktop. It took three real outages to make it reliable. This is the script, the three failures, and the design that came out of them. Why This Setup Works Claude Code's "memory" disappears by default Claude Code sessions are independent of one another. The root cause of a bug you found during a long working session today, the reason you settled on a particular architecture after trial and error, the accumulated knowledge that "this direction already failed once" — none of it is available in the next conversation once you close the session. Even on a paid plan, even with the most capable model available, if context isn't carried over you have to explain everything from scratch every time. Many people have had the experience of thinking "I already looked this up before" or "I should have failed at this once already, and yet here I am heading down the same road again." In a phase where you're shipping personal projects in volume, this problem is fatal. Once three or four projects are running in parallel, tracking "where each project currently stands" by hand hits a wall fast. And Claude, unable to reference previous conversations, repeats the same deliberations. The solution is to build an environment, not a task My first attempt at this problem was "I'll write up a summary by hand every day." It didn't last. When work has momentum you don't feel like writing a summary, and when you're tired you can write even less. A system that depends on human willpower doesn't function during a high-volume solo-dev phase. The answer was to build an environment that automatically drains Claude's conversation logs into Obsidian every night. Once the environment is in place, willpower and motivation are irrelevant. The Mac just does it. The reason I chose Obsidia

2026-08-24 原文 →
AI 资讯

How to Fix 'command not found' (Without Reinstalling Everything)

Adapted from the Command Line Essentials Companion Guide . You install something, open a fresh terminal, type the command, and get bash: python3: command not found — or on Windows, 'python3' is not recognized as an internal or external command . The installer said it finished successfully. You can probably even find the program in your applications folder. And yet the terminal insists it doesn't exist. The instinct at this point is usually to reinstall, or install a second copy from somewhere else, hoping one of them "takes." That almost never fixes it, because reinstalling doesn't address what's actually wrong. What the error is actually telling you When you type a command, the shell doesn't scan your whole computer looking for it. It checks a specific, ordered list of directories — stored in an environment variable called PATH — and stops at the first match it finds. command not found doesn't mean the program doesn't exist anywhere on your machine. It means none of the directories in that list happen to contain it. That distinction matters, because it splits into three genuinely different problems: A typo. gerp isn't a command; grep is. This is the most common cause by a wide margin, and the easiest to rule out first. It isn't installed at all. The program genuinely doesn't exist on this machine yet. It's installed, but not somewhere the shell is looking. This is the one that catches people off guard — the software is sitting on disk, correctly installed, just outside every directory PATH currently checks. Reinstalling only ever fixes cause 2. If your actual problem is 1 or 3, a second install just gives you a second copy of a program that was never the issue. The fix, step by step Check for a typo first. Read the command back character by character. It sounds too simple to be worth a step, but it resolves this error more often than everything else combined. Confirm whether it's installed at all , independent of whether the shell can currently find it: which pytho

2026-08-20 原文 →
AI 资讯

Four Failures That Made a Weekly launchd Job Actually Run

Every skill my AI setup learns lives in one folder on my laptop — and none of it reaches the repo I created yesterday. That gap is why I built a weekly job that pushes my accumulated skills into every project on the machine. This is what it does, and the four failures I hit getting it to run unattended. Why this mechanism works Claude Code's ~/.claude/skills/auto/ is essentially a personal "habits library." Workarounds, completion criteria, and verification commands discovered mid-task get written out to skill files automatically by the AI, and can be referenced immediately on the next request — that's how the mechanism is designed. Reality is a little different, though. Skills keep piling up in .claude/skills/auto/ . But a project in a freshly created git repo, a side-gig job opened for the first time in weeks, a set of tools written in another language — those don't have the skills at all to begin with . Unless a human copies them by hand, or I type "refer to that skill" every single time, the habits I so carefully accumulated are completely dead in other projects. The structure of the problem looks like this. Skills accumulate in one place, .claude/skills/auto/ (global) They're actually referenced only "when that project has .agents/ or .claude/skills/ " (local) That bridging doesn't happen each time you create a new project (zero start) This isn't "growing your environment," it's "regrowing it every time." Once monthly revenue crosses a certain line, the number of concurrent jobs rises, and there are weeks where I cut two or three new repos. Each time, noticing the missing skills, copying manually, verifying — that work quietly eats time. Not the duration of a single tool call, but the opportunity cost of "if that skill had been here, this would have taken three minutes." The weekly auto-distribution script solves this. Early every Sunday morning, it scans all git repositories and pours the skills in. Without a human doing anything, the project you open on Monda

2026-08-17 原文 →
AI 资讯

A green test is not a running reflex, and a running one is not a placed one

We run about 283 scheduled jobs across a handful of machines. Each one is a shell script that declares its own schedule in a header comment, ships its own --test , and gets wired into cron automatically once that test passes. It is a tidy arrangement and it has a hole in it that took us five separate incidents to see, because every one of those incidents looked healthy from every angle we had built. Every number, command and file listing below was re-measured on one 16-core Ubuntu 24.04 box while writing this, not quoted from the commit that fixed it. Two of the numbers came out different, and one of the mechanisms did not reproduce at all. Those are the interesting parts. The hole is that "green" is a conjunction pretending to be a single fact. For a scheduled job to be doing its work, at least four things have to be true at once: the test passes, the test asserts the thing the job does, the job is actually scheduled, it is scheduled where its consumer exists . We had instrumentation for (1). We had a habit — a good one — of insisting on (2). We had nothing whatsoever for (4), and it turns out (4) is the one that runs silently for weeks. 1. The edge detector that compared the state against itself The first one is almost embarrassing in the diff and was invisible for six weeks in production. We have a job that fuses four inputs into one node health label — HEALTHY , DEGRADED , CRITICAL — writes it to a state file, and with --edge prints a line only when the label changes . Cron runs it every five minutes; a separate log records the transitions. The --edge path did this: write_state " $label " # $STATE now holds the new label prev = $( cat " $STATE " ) # ...and prev is read from it [ " $prev " = " $label " ] && exit 0 prev is read after the write. It equals $label by construction. The equality test held on every single run, --edge exited 0 with empty output on every real transition, and the transition log could not append. What makes it worth writing about is not the

2026-08-17 原文 →
AI 资讯

A Month With Bash — Part 3: Building Projects

A Month With Bash — Part 3: Building Projects After all the expansions and syntax, I moved on to regex in bash. It wasn't too hard since I'd already worked with regex in Python, but alongside it I learned grep , sed , and awk — tools that turned out to be extremely useful for automation. I built a few mini projects and started automating some of my small day-to-day tasks. I won't go too deep into that here, but you can check out my learning-bash GitHub repo, which has all my learning scripts. From there I covered conditionals, loops, and repetitive tasks. Finally I learned about array variables in bash and shell options, went even further testing different ways of looping, and that's when I started actual project building(I am still building ) #!/usr/bin/env bash ## looping with range functions -- somehow # python style looping {start..end} for i in { 1..10 } ; do # this uses brace expansion so using vars wont work becase of execution sequencing echo $i done clear ## c - slyle looping for (( i = 0 ; i < 10 ; i++ )) ; do # variables works here well echo "hello $i " done ## using variables to loop clear start = 1 stop = 10 step = 2 for i in $( seq $start $stop ) ; do # this uses the seq command echo "hello world" done Conclusion Spending so much time on bash wasn't a waste. Not only did it force me to learn a huge number of commands, it changed how I think about my own machine — most of what I used to do manually, I can now automate. That shift alone made the month worth it. i am still learning and trying to get the best practices and things not to do THANK YOU FOR READING THIS FAR. That is a rough summary of me writing bash for a month there is really a lot left unsaid here but still building and learning. If you are just starting out with bash or if you haven't tried it hope this helps feel free to drop questions advice and corrections

2026-08-04 原文 →
AI 资讯

A Month With Bash — Part 2: Expansions

A Month With Bash — Part 2: Expansions Continuing from where I left off, the next thing I learned was special parameters in bash: "$*" $# $? $@ $N $- $0 Another important concept I picked up is how bash executes shell scripts. Bash is one of those languages that interprets each line as it goes — but it doesn't stop if a line fails. It continues on unless you explicitly set set -o pipefail (or -e , depending on what you want it to catch). Generally, the procedure looks like this: Tokenizing : splitting the line into tokens, usually split using the IFS value. Brace expansion : a mechanism by which arbitrary strings can be generated. echo file { 1,2,3 } .txt ## output: file1.txt file2.txt file3.txt Bash preserves the order from left to right. Tilde expansion : this is where expansion of special symbols takes place. ~ represents the HOME built-in variable ~+ represents PWD , the current working directory and others DIR = ~/Desktop # this is $HOME/Desktop echo " $DIR " Parameter expansion : introduced with the $ symbol. # ${} — the braces can be omitted for normal variables but not for array-type variables Command substitution : very important — it lets you assign the output of a command to a variable, and use commands inside if and for statements. Done with $(command to execute) . week_name = " $( date +%A ) " # gets the current day of the week echo " $week_name " Generally, $() spawns a new shell instance, so it's advisable to avoid it where possible, for latency reasons. Arithmetic expansion : just from the name, this allows evaluation of arithmetic expressions and substitution of the result. It starts with $(( expression )) . There are some rules — bash doesn't support floating point arithmetic natively, so you'd reach for bc if you need it. I won't go deep into that here since this isn't a full bash tutorial. Here's a simple BMI calculator I wrote while practicing this: #!/usr/bin/env bash # script calculates user's BMI and gives a recommendation set -euo pipefail #

2026-08-04 原文 →
AI 资讯

How to Learn Linux in 2026 (Hands-On, Free, No Experience Needed)

Here is the whole method: get access to a real Linux machine, type commands on it for 30 to 60 minutes every day, and follow a plan that builds from navigating the filesystem up to running your own web server. Do that and you will be comfortable in four weeks and genuinely fluent in about eight. No experience required, no money required. The rest of this article is the specific plan: what to type each week, where to get a free machine you can safely break, what the three scariest errors mean, and how to tell you are actually improving. Why most people fail at Linux The pattern is nearly universal. Someone decides to learn Linux, finds a nine-hour video course, watches it at 1.5x speed, takes beautiful notes, and three weeks later cannot list the contents of a directory without checking those notes. Watching someone else type is not practice. It feels like learning because the explanation makes sense while you hear it. But command line skill is muscle memory wrapped around a mental model, and both are built one way: typing, failing, reading the error, trying again. An hour of reading about ls teaches you less than typing ls twenty times in twenty directories. Videos are fine as a preview. They are just not the workout. So flip the ratio: for every minute reading or watching, spend five with your hands on a keyboard. This article included. Read a section, then go type it. Two smaller failure modes show up almost as often. Trying to memorize everything Linux has thousands of commands. Working engineers lean hard on a core of about 25 and look up the rest without shame. The plan below teaches that core and nothing else. Fear of breaking things On a practice machine, breaking things is the goal, not the risk. A system you broke and fixed teaches more than ten flawless tutorials. Every option in the practice section makes the worst case "start over," which costs a minute. The four-week plan First, get a machine from the free options below (one minute to one afternoon, dep

2026-08-01 原文 →
AI 资讯

My favourite zsh/bash shortcuts (functions and aliases)

Introduction My zsh profile is over 1000 lines at this point. A lot of that is functions I asked AI to generate for me, since it's fast, portable, and saves me a ton of typing. Here's the thing though: the shortcuts that save me the most time aren't the clever ones. They're the dumb ones. Things like clone instead of git clone && cd , or dir instead of mkdir -p && cd . Each one only saves a second or two, but I run them so often that it adds up fast. These are in no particular order, just the ones I reach for constantly. Git aliases for common commands A few one-liners I have set up as plain aliases: alias gcp = "git cherry-pick" alias git-append = "git commit --amend --no-edit -a" gcp is self-explanatory. git-append amends the last commit with your currently staged (and unstaged, thanks to -a ) changes without touching the commit message. Great for fixing up a commit you just made before you push. Create a branch or switch to it if it already exists One of my most-used functions. Normally you have to remember whether a branch exists before deciding between git checkout <branch> and git checkout -b <branch> . This just does the right thing either way: gb () { if git rev-parse --verify --quiet " $1 " > /dev/null ; then git checkout " $1 " else git checkout -b " $1 " fi } Nuke all local changes to reset the working tree When an experiment goes sideways or I just want to throw everything away and start clean, I run nah : nah () { git reset --hard git clean -df if [ -d ".git/rebase-apply" ] || [ -d ".git/rebase-merge" ] ; then git rebase --abort fi } This resets tracked changes, removes untracked files and directories. No confirmation prompt, so use it carefully. Print recent commits as ready-to-paste cherry-pick commands Useful when you need to cherry-pick a batch of commits from one branch onto another in order: logs () { if [[ -z " $1 " || " $1 " = ~ [ ^0-9] ]] ; then echo "Usage: logs <number_of_commits>" return 1 fi git log -n " $1 " --reverse --pretty = format: "g

2026-07-09 原文 →
科技前沿

How to align columnar output in the terminal

In bioinformatics we are handling a lot of tabular data. Be it VCF files, tabular Blast output, or just creating a CSV or TSV samplesheet. Actually, one of my favorite tabular formats is by using SeqKit to convert Fasta or FastQ files to tabular format, as this allows to do various filtering operations by row , using standard unix tools if so wished. Scrolling through this type of data in the terminal can be messy to say the least though. Although CSVs can of course be imported into a spreadsheet software for viewing, it would be very powerful to be able to view them comfortably right from the terminal, isn't it? To take one example that fits within the code window of a blog post, let's take a selected set of columns from the CSV output from the Mykrobe tool. And to make it emulate another common problem with many csv formats, let's also use tr to convert the _ :s in the headers into real spaces (Mykrobe does not do this, but many other tools do): $ cat SOME_SAMPLE.csv | cut -d , -f 2,3,10,14,15,17,18 | tr '_' ' ' > selection.csv $ cat selection.csv "drug" , "susceptibility" , "kmer size" , "phylo group per covg" , "species per covg" , "phylo group depth" , "species depth" "Amikacin" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Capreomycin" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Ciprofloxacin" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Delamanid" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Ethambutol" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Ethionamide" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Isoniazid" , "R" , "21" , "99.672" , "98.428" , "372" , "347" "Kanamycin" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Levofloxacin" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Linezolid" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Moxifloxacin" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Ofloxacin" , "S" , "21" , "99.672" , "98.428" , "372" , "347" "Pyrazinamide" , "S" , "21" , "99.672"

2026-07-07 原文 →
AI 资讯

I Spent 40 Minutes at 11pm Debugging a Deploy That Wasn't Broken

I once spent forty minutes at eleven at night debugging a deploy that wasn't broken. The release script ran the database migration, the migration threw connection refused , the script exited non-zero, the deploy rolled itself back, and I got paged. So I did the things you do. I read the migration. I read the logs. I checked the database — it was up, it was healthy, it accepted my connection instantly. I re-ran the deploy and it worked. I chalked it up to gremlins and went to bed, which is the part I'm not proud of, because it happened again two days later. That time I watched the timing: the script brought up a fresh database container and started the migration about six seconds before Postgres finished initializing and began accepting connections. The migration was racing the database's boot. Most of the time it won. The times it lost, I lost forty minutes. The script wasn't wrong about anything except one assumption: that a dependency is ready the instant you ask for it. In production, dependencies are eventually ready That's the mental model shift. Networks blip. A service you call returns a 503 for the two seconds it takes to finish a rolling restart. An API rate-limits you with a 429 it fully expects you to retry. A fresh container's database isn't accepting connections for its first few seconds. Treating the first failure as fatal turns every one of these normal, transient conditions into a paged engineer — and the script that handles them isn't smarter — it declines to give up on the first try. But retrying naively is its own trap. Retry instantly and you hammer a recovering service into staying down. Retry forever and a genuinely dead dependency hangs your script indefinitely. Retry a 404 and you wait a minute to confirm what you already knew. Good retries are bounded, backed off, and selective. A retry function you can reuse anywhere #!/bin/bash # Purpose: survive transient failures instead of dying on the first error set -euo pipefail CHECK = "✓" CROSS = "

2026-07-02 原文 →
AI 资讯

A Cron Job Took Our Server to Load 41 by Attacking Itself

A */1 rsync took our staging box to a load average of 41 one afternoon, and it took me longer than I want to admit to work out why. The sync normally finished in about twenty seconds. That day the backup target's NFS mount went sluggish, the sync started taking ninety seconds, and cron — which does not know or care whether the last run is still going — launched a fresh copy every single minute on top of it. Inside ten minutes there were a half-dozen rsyncs all reading the same tree off the same slow disk, each one making the disk slower, each new minute adding another. The box wasn't under attack. It was attacking itself, one polite copy at a time. The thing that stung was that nothing was broken — every individual rsync was correct, the disk eventually recovered on its own, and the only reason it became an outage is that cron has no concept of "the last one is still running." That's the trap with scheduled jobs: a command that's perfectly fine when you run it by hand can take down a server the first time it runs longer than its interval with nobody watching. The fix everyone reaches for first is the wrong one The instinct is a PID file: write $$ to /var/run/job.pid on start, check whether that file exists on the next run, bail if it does. It almost works. Then one run gets kill -9 'd, or the box reboots mid-job, and the PID file is left behind pointing at a process that died on Tuesday. Now every future run sees a "lock" owned by a PID that no longer exists, and the job never runs again — the opposite failure, just as silent. There's also a race between the check and the write, and the times you most need the lock to be clean are exactly the times cleanup didn't happen, because the process died before it could clean up. flock has none of that. The lock isn't a file you create and delete — it's a lock the kernel holds on an open file descriptor , and the kernel releases it automatically the instant that descriptor closes. The process exiting closes it. So does crash

2026-06-23 原文 →
AI 资讯

Meet mytuis: A Sleek Terminal Application Manager Built with Bash and Gum

Having spent over 25 years in software development and managing countless Linux environments, I've accumulated a vast collection of custom bash scripts, containers, and CLI tools. Remembering their exact paths and managing them efficiently directly from the terminal is a common challenge. To solve this, I built mytuis . mytuis is a small, attractive terminal UI for managing a personal catalogue of applications. It is built with gum and plain bash, with persistent storage in a human-readable YAML file. GITHUB REPO : https://github.com/horaciod/mytuis Why mytuis? I wanted a tool that didn't require heavy dependencies or a complex setup, but still looked great and provided a smooth user experience. Here is what mytuis brings to the terminal: CRUD operations: You can create, read, update, and delete application entries from a single menu. Quick launch: Pick an app from the filterable list and it is launched immediately. It replaces the manager process via exec, meaning no extra shell window is left behind. Smart path handling: It accepts absolute paths (like /usr/bin/firefox), relative paths (./scripts/myscript.sh), tilde paths (~/bin/foo), or plain command names looked up in your $PATH (firefox). Persistent metadata: Every entry stores its name, description, absolute path, creation date, and last-used date. Friendly TUI: You get clear menus, color-coded messages, and clean borders, all powered by gum. Under the Hood: Plain Text and Standard Utils Simplicity and standard compliance were key goals. mytuis requires bash ≥ 4 and standard Unix utilities like awk, sed, grep, date, and tput. Your catalogue is stored at ~/.mytuis.yaml. Because it is a standard YAML file, it can be inspected, edited, or backed up with any text editor. It is also completely safe to sync with a dotfiles repository or version-control. To ensure data integrity, all file operations are performed atomically by rewriting the YAML file from scratch on every change, so there is no risk of leaving the fi

2026-06-21 原文 →
AI 资讯

Securing AI-Generated Bash Scripts Before You Run Them

Bash is the easiest language for AI to write and the easiest language to get devastating output from. A 20-line script that "just cleans up old files" can recursively delete a home directory because the model assumed a variable would always be set. A "simple log shipper" can write your secrets to a remote server because the model used set -x for debugging and forgot to remove it. I have run AI-generated bash that I should not have. Most engineers I know have too. After enough close calls, there's a short checklist that catches the worst of it. This is that checklist. The five things to check before running any AI-generated bash 1. Does it start with a strict pragma? The first lines of any non-trivial bash script should be: #!/usr/bin/env bash set -euo pipefail IFS = $' \n\t ' What each does: set -e — exit on any command failure. Without this, a failure in line 5 doesn't stop the script from happily running lines 6-50. set -u — error on undefined variables. This is the one that saves you from rm -rf $UNDEFINED/ . set -o pipefail — propagate failures through pipes. Without it, failing-command | grep something succeeds because grep succeeds. IFS=$'\n\t' — sane field splitting. Defends against word-splitting bugs in filenames. If the AI-generated script doesn't have these, add them and re-read the script. You'll often discover bugs the pragma now flags. 2. Is every variable expansion quoted? # Wrong rm -rf $TARGET_DIR # Right rm -rf " $TARGET_DIR " The wrong version is what causes the "I deleted the root directory" stories. If $TARGET_DIR is empty or contains a space, the command becomes rm -rf (delete current directory) or rm -rf foo bar (delete two unintended things). Models default to the wrong version about half the time because the right version is harder to write in chat ("escape the quotes!") and the wrong version is what most blogs show. Fix: When reading AI bash, mentally check every $VAR for quotes. Add them if missing. This is the single biggest source of bas

2026-06-18 原文 →
开源项目

How to Automate Azure Resource Group Creation with a Bash Script

If you are just getting started with Azure CLI and Bash scripting, this post is for you. I will walk you through how I automated the creation of Azure resource groups for multiple environments using a single Bash script — something that was taking a cloud admin several manual steps every week. This is Project 2 in my TechRush Cloud Engineering bootcamp series. If you want to see where this journey started, you can read my previous post where I tackled deploying a web app across two Azure regions for the first time . That project involved real blockers — quota limits, CLI version mismatches, and a deep dive into Azure Resource Providers. This one went smoother, and I think that is because the previous project was the hard school. The Problem Imagine a cloud administrator who has to create five resource groups every single week, one for each active project: Project-A-RG Project-B-RG Project-C-RG Project-D-RG Project-E-RG Every week. By hand. Management's response was simple: automate it. But here is where the task gets more interesting. Instead of creating one flat resource group per project, the better approach is to create four resource groups per project — one for each environment: Dev Test UAT Production This matters because each environment needs its own access controls, cost tracking, and lifecycle rules. You do not want your Development environment sharing a resource group with Production. Keeping them separate is a real-world cloud best practice, not just a bootcamp exercise. What You Will Need Before running this script, make sure you have the following set up: Azure CLI installed on your local machine. You can follow the official installation guide . An active Azure account . A free account works fine for this. A terminal that runs Bash — Linux, macOS, or WSL on Windows. Understanding the Design The core idea behind this script is parameterization . Instead of hardcoding project names, the script accepts a project name as input and uses it as a prefix for ev

2026-06-09 原文 →
AI 资讯

Scaling User Management on Linux: Moving Beyond the Manual Script

The Scenario: The Help Desk Bottleneck From 2019 to 2021, while serving as Lead Backend Software Engineer at a fast-growing company, I occasionally support our Linux System Administration tasks. When the DevOps team encountered a critical bottleneck during an initiative to scale dozens of new server deployments, I stepped in to streamline the infrastructure processes. The DevOps team was being hampered by constant, fragmented requests from the help desk to manually create new Linux accounts for recruits testing the latest application. These interruptions were not only time-consuming but were directly preventing the team from focusing on the high-priority infrastructure deployments that define their core responsibilities. I realized that we weren't just struggling with a task; we were struggling with a scaling bottleneck. To regain the team's focus and ensure we hit our project deadlines, I decided to automate this workflow. The First Step: The Interactive Script My first objective was to develop a robust, automated shell script to efficiently create new Linux user accounts. I started with an interactive Bash script (create-user-interactive.sh) that prompted for input. This was a good educational exercise for learning the fundamentals of Bash—like useradd, passwd, and shell variables. However, I quickly learned that while interactive scripts are great for learning, they are rarely used in professional DevOps environments. Why Manual Scripts Don’t Scale As I transitioned into a more infrastructure-focused role, I realized that manual scripts fail for three key reasons: Lack of Automation: DevOps is about "Infrastructure as Code" (IaC). Asking an engineer to sit at a terminal and type prompts is slow, error-prone, and destroys the ability to automate. Lack of Centralization: In a real team, we aren't creating users on individual local machines. We manage identity across hundreds of servers. Security Risks: Hardcoding passwords or piping them through echo is a major red

2026-06-02 原文 →