ed(1) semantics
ed(1) is The Standard Editor, for Unix. And I think I’ve been approaching this powerful editor slightly wrong over the years.
Earlier this week, I got snagged on a gotcha with ed(1). Or at least that’s what I thought initially.
When I would fire up vi(1) in a previous life, I would always pass in the file as argument. If the file hadn’t been created yet, it’s at that moment that I would take the time and conjure up a name for her.
Seems innocent enough, but in ed(1), this could be semantically incorrect, or at least semantically obtuse. It’s quite possible that Ken Thompson, the ed(1) creator, thought it more correct to go about naming files after you were through with ed(1), and perhaps after all your code had been written, even!
I like naming things before I start something. It’s a little like the jest that Steven Spielberg creates his movie poster, then works backward from there. There’s a trade-off to doing things this way, and my bias has blinkered me. Heck, maybe ninety per cent of the world likes naming things later in the process?!
Gotcha, bug, or none of the above
When you fire up ed(1) and give it a file argument when the file doesn’t exist, you actually get the ed(1) equivalent of an error. You could see it as a helpful message that, hey, the thing you may have thought exists, isn’t there yet. But I actually read the message as if it’s much more in the tone of ‘whoa! what are you doing!?’
The reason I feel this way: ed(1) is not chatty, to say the least. For something to fire off a message means you’re been alerted about a critical misstep.
Of course, this naunce could be easily missed. You see, in interactive mode, ed(1) exits normally. But in script mode, there is a non-zero exit.
Now, consider this alternate approach…
$ ed <<EOE
$ 0i
... oodles of code goes here
.
$ f new_user.erl
$ wq
$ EOE
… which will yield no such message.
Now, if I play pretend, and put myself in Ken Thompson’s shoes, perhaps that is exactly the intended ed(1) usage pattern. Perhaps I would never know what ‘new file’ best be named until the code is within. And perhaps, I name my new editor ed(1) to reflect this exact usage pattern. (Rather than naming it ‘editor-or-creator.’)
Feature or bug
This all came about when, in a script, I was creating a file using ed(1). I was stunned that my code wouldn’t execute.
You may ask: ‘ed in a script?’
I enjoy leaning on ed(1), versus sed(1), awk(1), or perl(1). It scales well for most my situations, and this way, I won’t stumble into the endless differences between sed(1) and ed(1).
In a nutshell, this is a version of what I was expecting to work, but did not:
#
...
ed -s some_new_file.data <<EOE
H
0i
New data: ${u}
wq
EOE
Looks without issue! But in both GNU ‘ed,’ and BSD ed(1), this exited with a non-zero status, and from my tests on a few OSs, the file didn’t get created, nor written. This is in large part from what I penned above: ed(1) throws an error, not merely a message regarding ‘file does not exist.’
And this error thrown when in a script, or when in non-interactive mode, terminates ed(1) without execution.
So, with this evidence, my conjecture that this usage is in error, is fairly supported. Furthermore, even if this faulty pattern led to the file being created, written to, but had a non-zero exit signal, this conjecture would still hold water.
A decent pattern (that won’t fall over)
I think the best kludge to employ, and the one I initially gravitated to, is
to just sprinkle in a touch file_in_question
right before any ed(1). But
after some thought, I’ve matured my thinking: this is hacky, and could introduce bugs.
My solution, that just shifts my thinking to how this editor may have been conceived, is as follows:
#
ed -s <<EOE
H
f brand_spanking_new.data
0i
...
.
wq
EOE
ed -s <<EOE
H
e file_that_exists.data
;s/pattern/replacement/
wq
EOE
These two example HEREDOCs correctly, in the first case, creates a file with content, and in the second case, edits an existing one. They are without error messages, are semantically clear, and perhaps, even, lean into how Ken Thompson thought ed(1) should be used — and maybe even something deeper about how one should think whilst coding.
I split the difference in the first HEREDOC, and front-loaded the f brand_spanking_new.data
… but nothing is stopping anyone from simply putting that towards the end, or more correctly:
...
w brand_spanking_new.data
q
...
For me, I just wanted to replicate how I would go about working with ed(1) in interactive mode.
Sigh…
I am not sure why I’ve been so taken with this gotcha. I suppose it’s because I have written a ton of scripts the buggy way, but never bumped into this issue till now. It seems impossible that I have never scripted ed(1) to work with a new file (whereby I would have had code that didn’t work, full stop).
Probably more correct, is I have all kinds of scripts with this broken code that magically got by, but will bite someone in the ass down the road.