24

My question is related to the sed-specific solution given in this answer for this question of reverse grepping. The sed/grep solution that I am unable to decipher is the following one:

sed '1!G;h;$!d' file

Can someone please decipher this command?

I know from VI(M) knowledge that G denotes the last line of the file and that in sed a bang(!) followed by an address work a bit like grep -v that is to say that it will not match that line. But as a whole the inline sed script above is beyond me.

3
  • 3
    ...very slowly...
    – mikeserv
    Commented Oct 1, 2015 at 1:06
  • 1
    As mikeserv hints. One should note that this tricky sed recipe is an extremely inefficient way (O(n^2/2) complexity) to reverse lines in a file. It would be prohibitively slow for files with a large number of lines. For a much more efficient line-order reversal alternative see tac from GNU coreutils.
    – arielf
    Commented Oct 5, 2015 at 21:52
  • and sed -n 'G;h;$p' does the same as noted here
    – ryenus
    Commented Jun 3, 2022 at 14:31

1 Answer 1

37

This reverses the file line by line.

sed '1!G;h;$!d' file

First, sed has a hold space and a pattern space. We have to distinguish between them before concentrating on that specific command.

When sed reads a new line, it is loaded into the pattern space. Therefore, that space is overwritten every time a new line is processed. On the other hand, the hold space is consistent over the whole processing and values can be stored there for later usage.


To the command:

There are 3 commands in this statement: 1!G, h and $!d

  • 1!G means that the G command is executed on every line except the first one (the ! negates the 1). G means to append what is in the hold space into the pattern space.

  • h applies to every line. It copies the pattern space to the hold space (and overwrites it).

  • $!d applies to every line except the last one ($ represents the last line, ! negates it). d is the command to delete the line (pattern space).


  1. Now, when the first line is read, sed executes the h command. The first line is copied into the hold space. Then it is deleted, since it matches the $! condition. sed continues with the second line.
  2. The second line matches the condition 1! (it's not the first line), and so the hold space (which has the first line) is appended to the pattern space (which has the second line). After that, in the pattern space, there is now the second line followed by the first line, delimited by a newline. Now, the h command applies (as in every line); all that is in the pattern space is copied to the hold space. The third statement ($!d) applies: The line is deleted from the pattern space.
  3. Step 2 is now done with all lines. We skip to the last line.
  4. In the last line ($) nearly all of Step 2 is done, but not the delete part (d). sed, when invoked without -n, prints the pattern space automatically at the end of the processing for each input line. So, when not deleted, the pattern space is printed. It contains now all lines in reversed order.
7
  • 1
    @Geek No, the h command copies the pattern space to the hold space, which persist until sed ends. After the end of the script everything is cleared, because the binary exited.
    – chaos
    Commented Sep 30, 2015 at 14:38
  • 2
    Can we think of the hold space like registers for vim? Are they also numbered? Or there is only one of them?
    – Geek
    Commented Sep 30, 2015 at 14:41
  • 2
    @Geek In sed there is only one hold space. It's like a variable which can contain something.
    – chaos
    Commented Sep 30, 2015 at 14:44
  • 1
    @user1717828 If we wouldn't the first line would be printed, when processed. Since sed is not invoked with -n we have to delete every line except the last. At the last line sed appends everything from the hold space to the pattern space. And because the d command will not be executed, the line is printed (this line contains now the whole file reversed).
    – chaos
    Commented Sep 30, 2015 at 19:54
  • 2
    (1) The OP’s sub-question/observation (in the comment) is, “so the h command on the last line is sort-of a no-op.” (with added emphasis, and slightly paraphrased).  He’s right; when sed is processing the last line of input, the G reads the hold space (in order to append it to the pattern space), and then the h copies the pattern space to the hold space, which is never referenced again.  We could just as well say sed 'G;$!h;$!d' or sed 'G;$!{h;d}'.  (2) We could avoid using d by saying sed -n 'G;h;$p'. Commented Oct 1, 2015 at 7:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .