Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swapping two lines

Tags:

sed

How can I make use of the sed H, h, x, g, G etc. commands to swap two lines?

For example in the file

START
this is a dog
this is a cat
this is something else
END

say I want to swap "this is a dog" with "this is something else".

This is what I have so far:

/this is a dog/{
 h # put to hold space 
}
/this is something else/{
 # now i am stuck on what to do.
}
like image 551
ghostdog74 Avatar asked Dec 02 '10 05:12

ghostdog74


People also ask

Can I swap 2 rows in Excel?

To do this, click on the first cell in the row or column that you'd like to swap, and then hold the mouse down while you drag the cursor across or down the row or column to select a range of cells. Doing this causes a green box to appear around the cells you've selected.

How do you swap cells?

Say you have values in cells A1 and B1 and want to swap them. Press and hold SHIFT on the keyboard, move cell A1 to the right border of cell B1, and when the cursor turns into 工, release the mouse. Cells A1 and B1 are now swapped.


1 Answers

If you know a pattern on each of the two lines you want to swap, but not the full contents of the lines, you can do something like this:

sed -n '                     # turn off default printing
    /dog/{                   # if the line matches "dog"
           h                 # put it in hold space
           :a                # label "a" - the top of a loop
           n                 # fetch the next line
           /something/{      # if it matches "something"
                       p     # print it
                       x     # swap hold and pattern space
                       bb    # branch out of the loop to label "b"
           }                 # done with "something"
                             # if we're here, the line doesn't match "something"
           H                 # append pattern space to hold space
           x                 # swap hold and pattern space
           s/\([^\n]*\)\n\([^\n]*\)$/\2\n\1/    # see below
           x                 # swap hold and pattern space
           ba                # branch to the top of the loop to label "a"
    }                        # done with "dog"
    :b                       # label "b" - outside the loop
                             # print lines that don't match and are outside the pair
    p                        # also prints what had been accumulating in hold space
    ' inputfile

The substitution pattern keeps "dog" at the end of the accumulated lines. It keeps swapping the last two lines that we're keeping in hold space so that "dog" "bubbles" to the bottom.

For example, let's put another line after the "cat" line so the process is a little clearer. We'll ignore the lines before "dog" and after "something". And I'll continue to refer to the lines using my nicknames

this is a dog
this is a cat
there's a bear here, too
this is something else

"Dog" is read, then "cat" is fetched. Some appending and swapping is done. Now pattern space looks like this (\N represents a newline, I'm using an upper case "N" so it stands out, the ^ is the beginning of the pattern space and $ is the end):

^this is a dog\Nthis is a cat$

The substitution command looks for any number of characters that are not newlines (and captures them) followed by a newline followed by any number of characters that are not newlines (and captures them) that are at the end of the line ($) and replaces all that with the two captured strings in the reverse order separated by a newline. Now pattern space looks like this:

^this is a cat\Nthis is a dog$

Now we swap and read a new line. It's not "something" so we do some appending and swapping and now we have:

^this is a cat\Nthis is a dog\Nthere's a bear here, too$

We do the substitution again and get:

^this is a cat\Nthere's a bear here, too\Nthis is a dog$

Why didn't we get "bear/dog/cat" instead? Because the regex pattern consisting of two lines (which each, as usual, consist of non-newlines followed by a newline) is anchored at the end of the line using the $ so we're ignoring anything that comes before it. Note that the last newline is implied and doesn't actually exist in pattern or hold space. That's why I'm not showing it here.

Now we read "something" and print it. We swap. Hey! there's that stuff that we've been "bubbling". Branch and print. Since "dog" is at the bottom of the lines (that had been accumulated in hold space) and we printed "something" right before that bunch, the effect is that we swapped the two lines.

This script will work regardless of how many lines appear before, between or after the two lines to be swapped. In fact, if there are multiple pairs of matching lines, the members of each pair will be swapped throughout the file.

As you can see, I'm keying on just one word in the lines of interest, but any suitable regular expression would do.

like image 186
Dennis Williamson Avatar answered Sep 20 '22 15:09

Dennis Williamson