For a good long while, sed
has been a great mystery to me. After-all, it is stylized after the infamous ed
editor. Another thing that has eluded me fairly often, is how I can, from a shell prompt, print out a block of code, and only that block of code?
1 | ; sed '/cfg_services/,/)/p;d' /etc/rc.conf |
Great. We’re looking for ‘cfg_services’, then we’re looking for the next ‘)’, printing those, then deleting all other output. But what if we had nested blocks of code? Assuming one has sane indentation, one can rely on that:
1 | int main(void) { |
Given that code, we want to print the foo check:
1 | ; sed '/if .foo/,/\t}/p;d' test.c |
If we want both blocks, we can simplify the first regex:
1 | ; sed '/if (/,/\t}/p;d' test.c |
Though this might not be as useful, since we can’t quite operate on each block individually.
Now, what if we wanted to add something to the block with sed
? How would we do that? sed
has an -i
flag that allows it to modify the file it was given, however, we have to operate on the file differently.
1 | ; sed -i '/if (foo/,/\t}/ { /}/ i \\t\tfoo();'\n'}' test.c |
A few things to note here. First, if you’re wanting to test your arguments to sed
like this, then omit the -i and let it print out the entire file, or pipe that into another sed that is only printing the block you’re wanting to modify. Second thing to note is that -i supresses sed’s behavior to output to stdout, it is printing back into the file it read from instead. Thus to see the results, we have to print the file out again with cat
.
But how does it work? We’re using the same pattern matching as before, but instead of having the address affect the p
command, we’re having affect a command block in sed, which in this example is: '{ /}/ i \\t\tfoo();'\n'}'
. Here, we’re giving a second address to work on, but this address is only within the context of the block we found, we’re looking for the closing brace, and running the i
command on it. The i
and a
commands tell sed
to insert and append respectively. If we did not give these commands a second address, they would run on all of the lines found with the first address range. Both commands are terminated with a newline, which my shell natively supports, but if you are using bash you will need to use $'\n'
instead of the raw \n
. We also have to provide an extra backslash to escape as both a
and i
will consume one.
Nice and simple. Hope everyone has a good week!