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:

    Shell

  1. 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.
  2. 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.
  3. The major scripting languages (perl/ruby/python) all support some method of firing a one-off command that can perform this edit.

Claude

  1. A one-off prompt of the `claude -p` style.
  2. 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:

  1. tree is obvious,
  2. find . is also pretty obvious but I love find and want to hear that people know it,
  3. 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,
  4. cp -Rl . /tmp is a bit cheaty because it creates hard links that'll need cleaning up, so it only works if your setup is correct,
  5. rsync -rvn . /tmp/nonexistent/ is more in the spirit of things,
  6. and of course you could always just ask ruby or another language: ruby -e 'puts Dir.glob("**/*")'