简体   繁体   中英

How to reverse order of blocks of text

How can I revert the order of some blocks of text using only bash commands like sed and cat? What I want is something like tac, but instead of operating line by line, it would operate block by block. Example:

From

/Section 3/
Rabbits
Dogs
Cats

/Section 2/
Eagles
Mice

/Section 1/
Dogs
Rabbits
Lemmings

To

/Section 1/
Dogs
Rabbits
Lemmings

/Section 2/
Eagles
Mice

/Section 3/
Rabbits
Dogs
Cats

In some files, the beginning of the block is marked by a slash, as in the example above. In others, the blocks are marked only by the existence of one or more blank lines between them.

In emacs , you can use the sort-paragraphs command:

Ctrl-x h Meta-x sort-paragraphs Enter


In vim : https://superuser.com/questions/365094/sort-file-per-paragraph-in-vim


Use the basic unix tools:

awk -F'\n' -vRS='' -vOFS=',' '{$1=$1}1' input.txt |
    sort |
        tr ',' '\n' |
            sed 's@^/@\n/@'

I use awk to transform the data to a csv , then sort the csv , at last I transform the csv back to list style.


result:

/Section 1/
Dogs
Rabbits
Lemmings

/Section 2/
Eagles
Mice

/Section 3/
Rabbits
Dogs
Cats

Edit : Sorry, I didn't look at your question very carefully. You can change the sort command to tac to reverse the order.

如果有空白行分隔所有块,

awk 'BEGIN{ORS=RS RS;RS=""}{a[NR]=$0}END{for(i=NR;i>0;i--)print a[i]}'

使用csplit将它们拆分为单独的文件,将生成的文件名放入另一个文件中,然后使用tac获取要合并的文件名。

What separates blocks in your example? 2 newlines. In Emacs Lisp, if the text is in a string, if you install dash and s , you can use one of these 2 equivalent expressions:

(s-join "\n\n" (nreverse (s-split "\n\n" s))) ; where s is your string
(->> s (s-split "\n\n") nreverse (s-join "\n\n"))

->> is a dash's threading macro, which pulls s through successive function invocations. Think *nix pipes : s | s-split "\\n\\n" | nreverse | s-join "\\n\\n" s | s-split "\\n\\n" | nreverse | s-join "\\n\\n" s | s-split "\\n\\n" | nreverse | s-join "\\n\\n" .

If you want to have an Emacs Lisp function that opens a file, reverses the blocks then saves it back to the same files, you could also install f file manipulation library:

(defun reverse-blocks (f)
  "F is a filename."
  (interactive "fFind file: ") ; letter `f` is filename goes in first arg
  (let ((s (f-read f))) ; read file into a string
    (--> s
         s-chomp ; remove trailing newline
         (s-split "\n\n" it)
         nreverse
         (s-join "\n\n" it)
         (f-write it 'utf-8 f)))) ; write to the same file

Here I use another trailing macro --> , which allows to put the result of the previous computation in argument denoted it of the next computation. Eg if the result of nreverse is X , then the equivalent would be (s-join "\\n\\n" X) . Finally, suppose you not just want to reverse, but to sort your blocks based on the number after word “Section”:

(--sort (< (string-to-number (cadr (s-match "/.*?\\([0-9]\\)/" it)))
           (string-to-number (cadr (s-match "/.*?\\([0-9]\\)/" other))))
        it) ; put it instead of nreverse

Which, using dash-functional is equivalent to:

(--sort (-on '<
             (-compose 'string-to-number
                       'cadr
                       (-partial 's-match "/.*?\\([0-9]+\\)/")))
        it) ; put it instead of nreverse

Read dash documentation to understand, what -on , -compose , -partial do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM