Learning Sed or Using Claude?
I have a favourite linux sys-admin job interview question: you're logged in to a system and it turns out that
it doesn't have ls installed. What're five ways to find the contents of the current directory and
everything below it? It forces my interviewee to think flexibly about linux. Answers at the bottom of this
post...
The older I get, the more I admire thinking flexibly. I use the toolchain that unix gave us all as an inheritance: text streams, filters, a programmable shell. So, I thought I'd ask myself what the tradeoffs are of learning those tools vs learning one of the modern LLM tools. Here's the challenge.
andrew.RssInfo{Title: "PlayTechnique", Dir: "does-not-exist", Description: "Learning to play
better."}
This line of code appears a dozen times in my codebase. I changed the Dir parameter from
string to []string.
I have two refactoring options: shell or claude.
Our modern shells are programable environments for one-off commands and simple loops.
Knowing how to tackle a refactoring across a single file means that you have to know the right tools. Here's a few options:
- A filter program. There are many text filter programs due to unix's history as a text editing and layout toolchain: sed is the most appropriate here, as the stream editor allows us to edit the file line by line but without needing to open an interactive editor session.
- An interactive text editor such as vi or emacs that is notorious for a steep learning curve followed by years of admiration for how well it's designed, or something more user friendly like vs code.
- The major scripting languages (perl/ruby/python) all support some method of firing a one-off command that can perform this edit.
Shell
Claude
- A one-off prompt of the `claude -p` style.
- A slash-command defined for either this codebase or defined in your ~/.claude directory.
sed implementation
When I'm implementing sed, I start with a plan, then by getting the matches right. Planning first is a
programming practice: if you start fixing a problem by opening a text editor, you're in the wrong game. Start
by thinking. I need each instance of
Dir: "string" to become Dir: []string{"string"}.
This requires knowing (a) How do I match a line containing the string Dir:, (b) how do I insert a
little text before that matched string and after it? (c) a few sed flags. I refine from less specific to more
specific. Here's the start (I've made it clear where my match begins and ends; I'm also redacting a lot of
output, because I want the example to be clear):
; sed -n 's/Dir:\(.*\)"/BEGINMATCH \1 ENDMATCH/p' rss_feed_test.go
rssInfo := andrew.RssInfo{Title: "PlayTechnique", BEGINMATCH ".", Description: "Learning to play better. ENDMATCH}
From here, it's more or less a game, refining the syntax used and the pattern itself, training my mind a little more how to see patterns in text. The key insight I needed to have is that '.' matches every character, whereas '[^"]' matches every character except a quote.
; sed -n 's/Dir: "\([^"]*"\)/Dir: []string{"\1}/p' rss_feed_test.go
rssInfo := andrew.RssInfo{Title: "PlayTechnique", Dir: []string{"."}, Description: "Learning to play better."}
There's some arcane syntax in there if you've not seen it before. To figure out one detail of capturing
groups, I had to check man regex. Took about six minutes to get right.
The regex syntax needs a little learning and effort. There's no shortcut, but the upside is that the same regex language is implemented in a lot of cli tools and in languages, because the syntax goes all the way back to the original UNIX. Ken Thompson didn't invent regular expressions, but he was one of a few early programmers to actually use them in tools and programs.
claude implementation
claude -p "In the file rss_feed_test.go, please update the argument to Dir in the RssInfo constructor to be an array of strings with the current value as the only member" --allowedTools "Read,Edit,Write,Bash"
Updated all four `RssInfo` literals in `rss_feed_test.go` (lines 37, 92, 113, 128) to pass `Dir` as a `[]string` containing the previous value.
Note: `RssInfo.Dir` is still declared as a `string` in the package, so `rss_feed_test.go` won't compile until that field's type is changed too — let me know if you want me to make that change.
Much simpler, yes! But even in a simple task like this, Claude wanted to be sure it said something whacky, so it told me that RssInfo.Dir is of type string when the type is []string. Oh, Claude, never change.
The Claude implementation is obviously much simpler as it's natural language. It cost a handful of tokens. I didn't learn anything, and I was actually misled by my tool, but it was fast, a few seconds to write and 5-10 to execute.
The cost of getting convenience without needing to learn the tooling is $20/month. There are cheaper ways to have harnesses, but that's what I pay for Claude Code.
The sed implementation is obviously more complex. It took time and thought to get right. It taught me things, reminding me of details in regular expression matching, reminding me of the delightful section in "The C Programming Language" in which I read about how a character doesn't need to be written with one literal character (above, '\(' is a single character).
The sed implementation will cross-pollinate to my other code writing, because the regular expression syntax is common to a lot of tools. The process made me read some documentation, think about how tokens are delineated, the kind of stuff that helps you write code.
If the goal is getting to the result, the claude implementation wins for most people. To some kinds of employer or entrepreneur, Claude's got to seem like a miracle.
To me, the goal is practicing the debugging of small problems, developing mental fortitude. I've spent late nights debugging production and restoring service. I've been the person who diagnosed the problems in a vibe-coded app because the "developer" didn't understand how to be stumped by a problem and investigate it, he'd turned his brain off. So I don't mind the bit of friction, I don't mind practicing my tools, and I love the command line, so it all feels useful and powerful to me.
The goal's also, for me, having fun. Command line editing and learning new things is rewarding; getting better at my craft is rewarding. Being confident that I can get into production and debug it is rewarding.
Oh, and for completeness' sake, here's 5 ways to recurse a tree without using ls:
treeis obvious,find .is also pretty obvious but I love find and want to hear that people know it,-
tar cvf /dev/null .is a bit more devious and establishes a pattern you can reuse with rsync or some other tool that has file recursion as a core of it, -
cp -Rl . /tmpis a bit cheaty because it creates hard links that'll need cleaning up, so it only works if your setup is correct, rsync -rvn . /tmp/nonexistent/is more in the spirit of things,-
and of course you could always just ask ruby or another language:
ruby -e 'puts Dir.glob("**/*")'