ed(1) is The Right Tool ============================= You know: ed(1) is the standard text editor [1], and indeed you would find ed(1) installed in any unix-like system. But I guess most Unix users have never actually tried to open it. The reason is probably that ed(1) is considered "hard-to-use" and "user-unfriendly". I really see no reason for such misconceptions and no motivation for their popularity, so I will try to convince you with concrete examples and scientific evidence that ed(1) is, almost always, The Right Tool to use. This tutorial was originally publised as a series of "phlog" posts (a blog over gopher), in the hope to serve as an "ed-primer" for beginners. The eight posts are reported below in the same order as they appeared in the phlog. The only addition is the last paragraph of Section VII, which was not present in the initial series of phlogs. The references of each section are reported at the end of the section. If you are in a rush and would like to go a proper book, look for the great "Ed Mastery" by Michael Lucas [2]. KatolaZ ed(1) is The Right Tool (I) ================================= First things first: ed(1) is a line editor, meaning that it acts on a line at a time. This is something we are not used to anymore, since our glass-terminals have allowed full-screen character-based editors to evolve. But on teletypes line-based editing makes a lot of sense. This means that we need to tell ed(1) which line we would like to edit and what to do on that line. In this tutorial we will use ed(1) to compile and manage simple TODO lists. This will allow us to explore almost all the ed(1) commands. Let's start by creating the file "TODO.txt" with ed(1): $ ed TODO.txt TODO.txt: No such file or directory Well, welcome to ed(1), aka "The Faithful Silent Servant of Unix masters of old". ed(1) is telling you that it could not find any file named TODO.txt in the current directory (and this is something we knew already) and then it prints a new-line to signal that it is ready to receive commands. Yes, ed(1) has no default "prompt". Actually, it does, and the prompt is just "no-character-at-all". Now let's add a few items to our TODO.txt list: 0a TODO LIST - phlog about ed(1) - put rubbish bins outside - make a tea - check postgrey hiccup . The first line "0a" tells ed(1) that I want to "a-ppend" lines after the 0-th line. Now, the 0-th line actually indicates the start of the buffer. ed(1) accepts my command and waits for the lines to be inserted there. It will keep receiving lines until you input a line containing only a "." (dot) [3]. Then ed(1) will "print" its prompt (which is indeed "no-character") and wait for the next command. Let's ask ed(1) to "p-rint" the whole TODO list then: ,p TODO LIST - phlog about ed(1) - put rubbish bins outside - make a tea - check postgrey hiccup (notice the ed(1) prompt on the last line!). The command "p" is used to print lines. It is normally put after the specification of a range of lines, but in this case "," indicates the range "from the first line down to the last one". Now what happens if you type "p" alone? p - check postgrey hiccup (notice again the ed(1) prompt above). Well, any command in ed(1) modifies the "current line address", which is the line any ed(1) command would implicitely work on if you don't provide any line to it. The command "p" moves the current address to the last line it printed. So ",p" moved the current address to the last line of the buffer, and the following "p" just printed the current line (the last one). Another useful command is "n", which prints lines adding line numbers: ,n 1 TODO LIST 2 - phlog about ed(1) 3 - put rubbish bins outside 4 - make a tea 5 - check postgrey hiccup Oh now that's convenient! Guess how you ask ed(1) to print the third line in your buffer: 3p - put rubbish bins outside While you can use the special marker "$" to indicate "the last line in the buffer": $n 5 - check postgrey hiccup Notice that there is no space between line ranges and the command that applies to them. I guess you have had enough ed(1)-ting for today, and I am not in the right mood to start working at this todo-list now. So let's just save our work for later: w 94 ed(1) prints the number of bytes written in the file TODO.txt. We can now exit ed(1): q $ and you are back to the shell prompt. Now you can see your brand-new TODO list with: $ cat TODO.txt TODO LIST - phlog about ed(1) - put rubbish bins outside - make a tea - check postgrey hiccup $ or by using: $ printf ",p\n" | ed TODO.txt 94 TODO LIST - phlog about ed(1) - put rubbish bins outside - make a tea - check postgrey hiccup $ ;-) -+-+-+- ed(1) was included in Unix-V1 (1971) and was used to write the Unix kernel and all the programs distributed with Unix at least until Unix-V7 (1979) -+-+-+- [1] https://www.gnu.org/fun/jokes/ed-msg.html [2] https://www.tiltedwindmillpress.com/product/ed/ [3] I use a dash "-" to indicate tasks that are still outstanding, a plus "+" for tasks I have started working on, and a star "*" for completed tasks. ed(1) is The Right Tool (II) ================================== Let's get on with our mission of learning the basics of ed(1), the standard text editor of the unix environment. In the first installment of this mini-series of phlogs [1] we saw a few basic commands to add some lines ("a"), print lines ("p", "n") and save a file after you are done ("w"). Using those commands we were able to compile a simple TODO list: $ ed TODO.txt 94 ,n 1 TODO LIST 2 - phlog about ed(1) 3 - put rubbish bins outside 4 - make a tea 5 - check postgrey hiccup Now imagine that we want to insert a new item (buy some tea) just before the line that reminds us to make a tea. We will use the command "i" (for "insert"): 4i - buy some pea . ,n 1 TODO LIST 2 - phlog about ed(1) 3 - put rubbish bins outside 4 - buy some pea 5 - make a tea 6 - check postgrey hiccup Easy, right? While "a" is used to "append" lines after a given line, "i" is instead used to "insert" lines before a given line. As with "a", the "i" commands is ended by a line containing only a ".". If you have ever used vi(1) or one of its many clones, you know that "i" is also the command needed to move to "insert" mode in vi(1). Well, this is not a coincidence. Notice that when we inserted the new line we also introduced a typo: we definitely want to buy some "tea" (not a "pea"). We can "change" the content of a line using the command "c": 4c - buy some tea . ,n 1 TODO LIST 2 - phlog about ed(1) 3 - put rubbish bins outside 4 - buy some tea 5 - make a tea 6 - check postgrey hiccup Notice that ed(1) acts consistently: the three commands used to append, insert, or change lines are all terminated by a line containing a single ".". Actually, the command "c" in general allows us to replace one line (or a range of lines) with another line (or with a range of lines). So if you prefer coffee instead of tea you might use: 4,5c - buy some coffee - buy sugar - make a coffee . ,n 1 TODO LIST 2 - phlog about ed(1) 3 - put rubbish bins outside 4 - buy some coffee 5 - buy sugar 6 - make a coffee 7 - check postgrey hiccup Well, it's evident that having to retype a line just to correct a typo is not exactly what one would call an efficient editing session. Indeed, the command "c" is mostly used when the content of a range of lines has to be substantially modified. Luckily, ed(1) has another command ("s", for "substitute") which allows to easily correct typos, as well as to perform complicate substitutions across any range of lines. That will be the focus of the next phlog in this series :) -+-+-+- [1] gopher://republic.circumlunar.space/0/~katolaz/phlog/20190505_ed_lists.txt ed(1) is The Right Tool (III) =================================== A todo-list is useful if you start working on the items and you are eventually able to remove some items from the list, sooner or later... Our current list looks like this: ,n 1 TODO LIST 2 - phlog about ed(1) 3 - put rubbish bins outside 4 - buy some coffee 5 - buy sugar 6 - make a coffee 7 - check postgrey hiccup Now it is clear I have started phlogging about ed(1), so the first item should somehow be updated. I have come up with a simple convention to identify the status of items in a todo-list, by using the first character of the item: a "-" indicates an outstanding item, a "+" indicates something I have started working on, and a "*" marks a completed item. In this case, I would like to change the "-" in the first item with a "+". Let's start searching for the line where I mentioned "phlog": /phlog/ - phlog about ed(1) Yep. An expression like "/expr/" is used in ed(1) to search for a pattern. ed(1) search for the first occurrence of that pattern and prints it. Most versions of ed(1) will also wrap the search around and start from the beginning if they reach the end of the file. Now the current line (the so-called ".") is the one containing the item I want to edit. Let's "substitute" the first "-" in the line with a "+": s/-/+/ p + phlog about ed(1) Yeah, it's that simple. The command "s" (for "substitute") replaces the first occurrence of the pattern between the first pair of "/" following it with the pattern between the second pair of "/" following it. In this case, we simply replaced "-" with "+". The command "s" accepts a line (or a range of lines) to work on. So if we are indeed working on all the items in the todo-list, you can change their state with a single command: 1,$s/-/+/ ,p TODO LIST + phlog about ed(1) + put rubbish bins outside + buy some coffee + buy sugar + make a coffee + check postgrey hiccup Well, that's powerful, right? But in my case, well, it's not true, since I have not put the rubbish outside and some other items are in a different state. Let's revert out last change: u ,p TODO LIST + phlog about ed(1) - put rubbish bins outside - buy some coffee - buy sugar - make a coffee - check postgrey hiccup The command "u" (for "undo") will do that (i.e., revert the effect of the last command). Try to see what happens if you keep pressing "u" :) The cool thing about "s" is that the line it has to act upon can be expressed with a pattern. For instance, I have solved the hiccup in postgrey, so I can change the status of the line containing "postgrey" to "done" by replacing the leading "-" with a "*". But I don't remember which line is it, so I can use the command: /postgrey/s/-/*/ ,p TODO LIST + phlog about ed(1) - put rubbish bins outside - buy some coffee - buy sugar - make a coffee * check postgrey hiccup Remember that the "." is set at the last edited line: . * check postgrey hiccup Now that we have completed an item, we can finally celebrate: hurray! Well, there is still a bunch of stuff to do anyway. How do we jump to the next outstanding item in the list? Easy: we just look for a "-": /-/ - put rubbish bins outside Yeah, I know, but this is not rubbish-day. Let's look for the next outstanding task: // - buy some coffee Yep! The quite quick combination "//" repeats the last search you did! And we can keep going: // - buy sugar // - make a coffee That's handy, especially since "/" is normally placed in a quite comfortable position in many keyboard layouts. The last example: I have to be honest with you: I can't drink any coffee, so I would like to replace any occurrence of "coffee" with "tea": /coffe/s/coffee/tea/ p - buy some tea The command "/coffee/s/coffee/tea/" is to be read as "look for the next line containing 'coffee' and replace 'coffee' with 'tea' on that line. So what happened here? Let's have a look at the whole file: ,n 1 TODO LIST 2 + phlog about ed(1) 3 - put rubbish bins outside 4 - buy some tea 5 - buy sugar 6 - make a coffee 7 * check postgrey hiccup So, the last search we performed ("//") had set the dot to line 6 ("-make a coffee"). When we issued the substitution command, ed(1) had to look for the next occurrence of "coffee". It wrapped the search around (siince there is no line containing "coffee" after line 6), found "coffee" on line 4, and replaced "coffee" with "tea" on that line. We would like to repeat the last "s" command then, to replace the other occurrence of "coffee" /coffe/s/coffee/tea/ . - make a tea OK, that's powerful, but re-entering the command is not fun. Anyway, we can't learn everything in a single phlog, and this one is already quite long. Let's not forget to save our work: w 121 To be honest with you, I really would like to get rid of that line with "sugar", since I never put any sugar in my tea. But that's a quest for another day :) ed(1) is The Right Tool (IV) ================================== Let's have a look at our current TODO list: $ ed TODO.txt 121 ,p TODO LIST + phlog about ed(1) - put rubbish bins outside - buy some tea - buy sugar - make a tea * check postgrey hiccup I need to add a couple more items to the list. Let's do it: a - find the sugar pot - configure Unix V7 outpost - read Shismatrix . First, there is a typo: s/Shi/Schi/ . - read Schismatrix Then, as I said in the last phlog, I really would like to get rid of sugar. Really. I need to "delete" all the lines containing "sugar" (and I have just inserted another one!). If "i" is for "insert", "a" is for "append", "c" is for change, "p" is for "print", "s" is for "substitute", what do you expect to be the command to "delete"? Yes, indeed, the ed(1) command for "delete" is "d", and it requires either an address or a pattern. Let's give it a try: /sugar/d ,n 1 TODO LIST 2 + phlog about ed(1) 3 - put rubbish bins outside 4 - buy some tea 5 - make a tea 6 * check postgrey hiccup 7 - find the sugar pot 8 - configure Unix V7 outpost 9 - read Schismatrix Wow. The line saying "- buy sugar" has disappeared. Let's undo, and try something else: u 5d ,n 1 TODO LIST 2 + phlog about ed(1) 3 - put rubbish bins outside 4 - buy some tea 5 - make a tea 6 * check postgrey hiccup 7 - find the sugar pot 8 - configure Unix V7 outpost 9 - read Schismatrix Same outcome! So you can delete a line by using the command "d" and specifying either a line number or the pattern that the line should match. Cool, but sometimes impractical, especially if you have lots of "sugar" you want to get rid of. There is obviously an efficient ed(1)-way of dealing with that. In order to have something non-trivial to work on (i.e., more than one line containing "sugar"), we first "undo" the last change, so that all the "sugar" reappears: u ,n 1 TODO LIST 2 + phlog about ed(1) 3 - put rubbish bins outside 4 - buy some tea 5 - buy sugar 6 - make a tea 7 * check postgrey hiccup 8 - find the sugar pot 9 - configure Unix V7 outpost 10 - read Schismatrix What if we want to print all and only the lines containing "sugar"? We know that the simple search command "/sugar/" won't work, since it prints only the next matching line and then stops. Let's try someting more powerful: g/sugar/p - buy sugar - find the sugar pot We have just used the "global" ed(1) command, indicated by "g". This command looks for matches of the pattern immediately following it (in this case, the pattern is "sugar") and then executes the command indicated right after the pattern itself (in this case, the command is "p" for "print"). So what we have just done is a specific instance of "g/RE/p", which looks for all the lines matching the regular expression "RE" and prints them. Yes, this is exactly where the Unix command grep(1) comes from! [1] The nice thing about the global command is that it can be followed by literally any other ed(1) command. So if we need to delete all the lines containing "sugar", we just give: g/sugar/d ,n 1 TODO LIST 2 + phlog about ed(1) 3 - put rubbish bins outside 4 - buy some tea 5 - make a tea 6 * check postgrey hiccup 7 - configure Unix V7 outpost 8 - read Schismatrix Now we are talking. This could have become the "gred" command, but the sage dwarves at Murray Hill noticed that it would have been better to have a generic tool to handle generic stream-oriented editing, and the Unix command sed(1) was born instead [2]. Before we save the todo-list, I need to note that I have started working on two items, namely those on line 7 and line 8 (yes, I am indeed putting up an outpost with a working Unix V7 machine for general use, and I need a good name for it, possibly from the Schismatrix universe :P): g/ix/s/-/+/ ,n 1 TODO LIST 2 + phlog about ed(1) 3 - put rubbish bins outside 4 - buy some tea 5 - make a tea 6 * check postgrey hiccup 7 + configure Unix V7 outpost 8 + read Schismatrix Can you see why the global command did exactly what we expected it to do? Let's dissect it. We asked ed(1) to look globally for the pattern "ix" (g/ix/), and to substitute a "-" with a "+" in the matching lines (s/-/+/). Easy, right? Let's save our work now. w 156 q -+-+-+- [1] http://www.catb.org/~esr/jargon/html/G/grep.html [2] http://www.cs.dartmouth.edu/~doug/reader.pdf ed(1) is the right tool (V) ================================= This is a relatively quick one. I recently used ed(1) to write my entry to the ROOPHLOCH 2020 contest [1], and I did so from the ed(1) version available in FUZIX [2], which is somehow close to the original ed(1) available in early Research Unix systems. No need to say that ed(1) did its job as expected (obviously, an editor does not know anything about the amount of nonsense you put in a file, so I refer here to the mechanics of editing, not the content of the post :P). After I finished my post, I piped it through fold(1) to make sure that all the lines were at most 68 chars long. I went for fold(1) because that is the only formatter I had on FUZIX (and I actually had to port it myself to FUZIX from sbase [3], because FUZIX does not have fold(1) yet). And then I remembered that someting like: $ fold -w 68 -s file.txt would indeed make sure that none of the lines is wider than 68 chars, also avoiding to break words (-s), but it would *not* join lines belonging to the same paragraph, as for instance fmt(1) or par(1) would do. This means that my paragraphs were all messed up, with lots of unwanted newlines where the original newlines stood. The solution? Just join all the lines of a paragraph in a single line! The direct way of doing that is to use the ed(1) command "j" (yes, "j" is for join), which will join the current line and the next one, and leave the current address at the resulting line. I did this by repeating the "j" command as many times as needed in order to have each paragraph in a single line (there is indeed a much smarter way of doing that, but we need some more advanced ed-fu). Then saved the file, gave it to fold, and then cat(1) it to a serial port connected to my laptop, ready to be sent over the Internet. We will learn a much quicker way to use fold(1) from within ed(1), but before that we need to have a look at a few more ed(1) commands. -+-+-+- [1] gopher://zaibatsu.circumlunar.space:70/1/~solderpunk/roophloch/2020 [2] http://www.fuzix.org [3] http://git.suckless.org/sbase/ ed(1) is the right tool (VI) ================================== So, let's keep going from where we left it. We have put together a TODO-list with ed(1): $ cat TODO.txt TODO LIST + phlog about ed(1) - put rubbish bins outside - buy some tea - make a tea * check postgrey hiccup + configure Unix V7 outpost + read Schismatrix and we have learned a bunch of simple ed(1) commands so far. Indeed, a lot of time has passed since we put that TODO-list together, and many items have been addressed and can be marked as "done". In particular, I have definitely phlogged (a bit) about ed(1), I have put the rubbish outside (at least a couple of times since we started this series of phlogs), and made myself many teas. We want our TODO-list to reflect this progress: $ ed TODO.txt 156 /phlog/s/^+/*/ 3s/^-/*/ /make/s/^-/*/ 1,$p TODO LIST * phlog about ed(1) * put rubbish bins outside - buy some tea * make a tea * check postgrey hiccup + configure Unix V7 outpost + read Schismatrix OK, that's a bit better. I have marked all the "completed" tasks with a "*", using different ways to address the corresponding lines. The main issue is that I would like to keep all the outstanding tasks at the beginning of the TODO list, for convenience. How to do that? What we would like to do is to "move" line 4 (- buy some tea) at the top of the list, i.e., after the line with "TODO LIST". Easy: 4m1 1,$p TODO LIST - buy some tea * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup + configure Unix V7 outpost + read Schismatrix Wow. That's it, but what's the magic? No magic involved, at all. If you want to "move" a line somewhere else, you use the "m" command. The command "m" can be preceded by an address, a range, or a regular expression that specifies which lines you want to move, and is followed by an address. It will put the lines specified before "m" right after the address specified after "m". So 4m1 means move line 4 after line 1. Which is exactly what "m" did. Now, it would also make sense to put all the completed tasks at the end of the file, since we don't have to work on them any more. Well, since all the completed tasks are lines starting with "*", this is quite easy to achieve: g/^\*/m$ 1,$p TODO LIST - buy some tea + configure Unix V7 outpost + read Schismatrix * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup and was accomplished by telling ed(1) to take all (g) the lines that start with "*" (/^\*/), and move them (m) after the last line ($). Note that we had to "quote" the "*" character, since it is also used as a wildcard in regular expressions, while we are referrin to a literal "*" here. Now what if we would like to copy a line instead of moving it? If you thought that the "copy" command would be "c" then think again, because we have already seen that "c" is the command to "change" an existing line [1]. The command to copy lines is "t", for "transfer", and its syntax is identical to that of "m". I just remembered that I need to buy some tomatoes: /tea/t 1,$p TODO LIST - buy some tea + configure Unix V7 outpost + read Schismatrix * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup - buy some tea what happened here? I gave the command "/tea/t" which means "look for the first line that matches 'tea' and copy it...WHERE?". Well, remember that consistency in ed(1) is key: if an address is missing, then ed(1) will assume you are referring to the "current line". In this case, the current line when we typed the "t" command was the last line in the file (since we had just given the "1,$p" command...). So "t" did the right thing, and copied the matching line after the last line of the file. Now we just replace "tea" with tomatoes: s/ea/omatoes/ and then move all the lines with outstanding items to the top of the file: g/^-/m1 1,$p TODO LIST - buy some tomatoes - buy some tea + configure Unix V7 outpost + read Schismatrix * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup Quite simple, right? As usual, let's save our TODO-list, until next time: w 176 q -+-+-+- [1] gopher://republic.circumlunar.space/0/~katolaz/phlog/20190830_ed_lists_2.txt ed(1) is the right tool (VII) =================================== The nice thing about TODO-lists is that you will eventually be able to get through them and pencil-out all the completed tasks. But sometimes it is good to keep track of the many things you have accomplished by going through a TODO-list. So I normally just save the finised tasks in a "WELL-DONE" file, to give myself a reassuring (although fictitious) pat in the shoulder. Let's have a look at our current TODO-list: $ ed TODO.txt 176 1,$p TODO LIST - buy some tomatoes - buy some tea + configure Unix V7 outpost + read Schismatrix * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup As per our choice, all the lines starting with "*" denote completed tasks. If we want to copy all of them to a file named "WELL-DONE", ed(1) knows a useful trick or two: 6,9w WELL-DONE 84 We should be able to figure out what might have happened here using our current ed-fu. "6,9" specifies a set of lines (all the lines between 6 and 9, both included), while "w" is the command to "write" ed(1) buffers to a file. We have also seen than "w" followed by a file name will actually dump all the current content of ed(1)'s buffer on that file. Well, this is exactly what happened here, only we told ed(1) to dump a range of lines (6-to-9) instead of the whole buffer, and the destination file is called WELL-DONE. We can check if our intuition is correct by exiting ed(1) and using cat(1) on the file WELL-DONE. The good news is that we don't need to exit ed(1) at all: !cat WELL-DONE * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup ! Wait, what was that? Let's give it another shot: !pwd /home/katolaz/tmp ! this can't be what we think it is, right? !ls -l total 8 -rw-r--r-- 1 katolaz katolaz 176 Oct 10 10:39 TODO.txt -rw-r--r-- 1 katolaz katolaz 84 Oct 11 05:35 WELL-DONE ! OK, OK, we got it! ed(1) uses the command "!" to "shell out" another command, i.e., to execute a command of our choice as if we were typing it at the shell's prompt. The "!" command will execute the command you type, show its output on the screed (beware, ONLY on the screen, without affecting your current buffer), and then finish with a single "!" on a line. At that point, we know that the external command has finished, and ed(1) is ready to receive more commands: 1,$p TODO LIST - buy some tomatoes - buy some tea + configure Unix V7 outpost + read Schismatrix * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup Quite handy, right? Notice again that the invocation of external commands through "!" does *not* modify the current buffer. So we have successfully written lines 6-9 to the file called "WELL-DONE" in the current directory. Now we remove them from TODO.txt: g/^\* /d 1,$p TODO LIST - buy some tomatoes - buy some tea + configure Unix V7 outpost + read Schismatrix to keep our TODO-list in order. I also did my shopping yesterday, so: g/buy/s/^-/\*/ 1,$p TODO LIST * buy some tomatoes * buy some tea + configure Unix V7 outpost + read Schismatrix but now I would like to append the newly-completed tasks to the file "WELL-DONE". If we used (don't do it!): 2,3w WELL-DONE ed(1) would silently destroy the previous content of WELL-DONE, and put there the two tasks we just completed. But this is not what we want: remember, keeping track of the stuff you have done is a good boost for yout morale! The ed(1) command to "append" lines at the end of an existing file is instead "W": 2,3W WELL-DONE 35 !cat WELL-DONE * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup * buy some tomatoes * buy some tea ! We can now remove the completed tasks and save our current TODO.txt file: 2,3d w 57 1,$p TODO LIST + configure Unix V7 outpost + read Schismatrix What if we want to re-insert our old (completed) tasks somewhere in our TODO.txt file, e.g., after line 1? Simple: 1r WELL-DONE 119 1,$p TODO LIST * phlog about ed(1) * put rubbish bins outside * make a tea * check postgrey hiccup * buy some tomatoes * buy some tea + configure Unix V7 outpost + read Schismatrix And our old (completed) tasks are back. But no, we really don't need to see them here: u 1,$p TODO LIST + configure Unix V7 outpost + read Schismatrix w 57 q Note that in the versions of ed(1) currently available in most unix operating systems (including Linux, OpenBSD, FreeBSD, NetBSD), the "r" command can also read lines from an external command into an ed(1) buffer. For instance $ ed filelist filelist: No such file or directory r ! ls -l 125 1,$p total 8 -rw-r--r-- 1 katolaz katolaz 57 Oct 11 06:01 TODO.txt -rw-r--r-- 1 katolaz katolaz 119 Oct 11 06:00 WELL-DONE which is sometimes very handy. This option was not available to the original versions of ed(1) up to UNIXv7. In that case, you would use a simple trick: $ ed filelist filelist: No such file or directory ! ls -l > tmp ! r tmp 178 1,$p total 8 -rw-r--r-- 1 katolaz katolaz 57 Oct 11 06:01 TODO.txt -rw-r--r-- 1 katolaz katolaz 119 Oct 11 06:00 WELL-DONE -rw-r--r-- 1 katolaz katolaz 0 Oct 11 06:06 tmp which (almost) does the job. Now it should not be hard for you to figure out how to use the "!" command to fold(1) a buffer. It actually requires only a little bit of ed-fu. Let us assume you have written an email with ed(1) and you now want to fold(1) it to 72 characters: w e ! fold -s -w 72 % You must be able to decypher this command easily. We are writing the current buffer first ("w"), and then we load in the current buffer ("e") the output of the command "fold -s -w 72 %". Obviously, there is some ed(1)-magic involved: indeed, the character "%" is replaced by ed(1) with the name of the current file. So the result of the command is to replace the current buffer with the fold(1)-ed one. Cool, right? -+-+-+- ed(1) is the right tool (VIII) ==================================== If you are reading this tutorial, it means that you had the patience and determination to go through the quite bumpy ride of learning the basics of ed(1). You did well, but I won't congratulate you, though. Indeed, by learning the basics of ed(1) you have not achieved anything special: for almost two decades, learning ed(1) was the very first thing any newbie willing to seriously play with unix had to do. You learned ed(1) first, and then you could mess around with source code and start writing your own programs. Then, glassy terminals made the minimalist terseness of ed(1) unnecessary, and more powerful full-screen visual editors made their appearance in unix systems, such as emacs and vi (yes, vi(1), not vim(1). vim(1) came much later). At that point, ed(1) started being forgotten, since newcomers to the unix world did not need it for their cool projects, and this little useful tool acquired the (unjustified) fame of being hard to use and hard to learn. I hope this tutorial has shown you that ed(1) is a potentially very useful tool, which is not that hard to use or to learn. It is obviously a tool from another era of computing, when minimalism was a necessity and terseness was a virtue. But its influence on the unix ecosystem is indeed deep and wide, and by learning its basics we have had the opportunity to see how the spirit of ed(1) still survives in hundreds of tools that have inherited its syntax, its commands, its idioms, its idiosyncrasies. We have not had the opportunity to talk about all the commands available in ed(1), but at this point RTFM (i.e., reading the famous man-page) should be enough for you to get along. I provide here some pointers to useful stuff: - we haven't talked about regular expressions, which make ed(1) so much more powerful. Go learn them. I will write a post or two about the basics, but in the meanwhile, go learn them. - 'k' is for labelling rows, which makes life easier sometimes - 'G' is like 'g', but for interactively editing lines - 'v' is like 'g', but works on lines NOT matching a pattern - 'V' is for... (read the two previous points and use your brain) - 'h' prints an explanation of the last error - 'H' toggles explation of errors - in some implementations, 'z' provides a print-by-screen function - in some implementations, 'y' allows to transform a set of characters into another, more or less as tr(1) would do, but in some other implementations it 'yanks' lines in a temporary buffer - in some implementations, 'x' allows to encrypt a buffer before writing it to disk, but in other implementations it is used to 'paste' lines previously 'yanked' with 'y'. I would also encourage you to have a look at the following books and articles about ed(1): - Ed Mastery, by Michael W. Lucas (possibly the best book on ed(1) currently available) https://mwl.io/nonfiction/tools#ed - ed(1) man page in the Single Unix Specification https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ed.html - ed(1) man page from UNIXv1, ca. 1971 http://man.cat-v.org/unix-1st/1/ed - ed(1) man page from UNIXv7, 1979 http://man.cat-v.org/unix_7th/1/ed - ed(1) at gopherpedia gopher://gopherpedia.com/0/Ed (text editor) - A history of UNIX before Berkeley, Ian Darwin & Geoffrey Collyer, section 3.1 http://www.darwinsys.com/history/hist.html - ed(1) is Turing-complete https://nixwindows.wordpress.com/2018/03/13/ed1-is-turing-complete/ - ed(1) is the standard text editor https://www.gnu.org/fun/jokes/ed-msg.txt That's all, folks! :) -+-+-+-