1

I have a list of data, separated into blocks by headlines. The headlines are marked by 6 '=' signs at beginning and end. There is a limited set of headlines that may appear, but not all of them appear every time. They will however appear in the same order every time. The amount of data in each block varies. I would like to reverse the order of two specific textblocks in this set of data. (Clarification: Not the order of the data inside the block, but the order the block get printed in)

Edit: Another Clarification: There is a limited number of different headlines, and I want to reverse two specific ones that I know the content of, but not where in the file they appear. There may even be cases where only one or none of the two appear. If they appear, they will always appear right after each other in the same order.

Sample input:

======abc======
data1
data2
data3
======def======
data4
======ghi======
data5
data6
======jkl======
data7
======mno======
data8

desired output:

======abc======
data1
data2
data3
======def======
data4
======jkl======
data7
======ghi======
data5
data6
======mno======
data8

If I add empty lines before each headline using sed '/======.*======/i\\', then using something like this answer might work, but haven't ever used perl and don't know how to modify it to do what I want. Also I suspect awk might be able to do this.

1
  • Hello, the answer you link to uses sort to rearrange text blocks. But you only want to shift the position of one text-block. Do you have a key to know which one? Something in the header or body, or just moving by position (e.g. blocks 3 and 4 reverse position). Thx. Dec 31, 2023 at 20:47

4 Answers 4

0

Using any awk:

$ cat tst.awk
/^======[^=]+======$/ {
    key = $0
    gsub(/=/,"",key)
    keys[++numKeys] = key
}
{ key2val[key] = key2val[key] $0 ORS }
END {
    if ( (a in key2val) && (b in key2val) ) {
        tmpVal = key2val[a]
        key2val[a] = key2val[b]
        key2val[b] = tmpVal
    }
    for ( i=1; i<=numKeys; i++ ) {
        key = keys[i]
        printf "%s", key2val[key]
    }
}

$ awk -v a='ghi' -v b='jkl' -f tst.awk file
======abc======
data1
data2
data3
======def======
data4
======jkl======
data7
======ghi======
data5
data6
======mno======
data8
3
  • this only work if bot ghi and jkl appear in the file. If only one of them appears it is deleted. Jan 1 at 9:34
  • I edited my answer to handle that case.
    – Ed Morton
    Jan 2 at 12:46
  • 1
    That seems to be working, thanks very much for your help :) Jan 2 at 16:23
0

Might be easier to use both awk and sed for this. Starting with your input file (let's call it blocks) first transfer jkl block to a temporary file (called the_block):

$ awk '/=jkl=/{p=1; print; next} /======/{p=0}; p>0{print}; p==0{print >"reduced"}' blocks > the_block

Examine the two new files:

$ cat the_block 
======jkl======
data7

$ cat reduced 
======abc======
data1
data2
data3
======def======
data4
======ghi======
data5
data6
======mno======
data8

Now use sed to insert the_block above the first line of the intended block in "reduced" file to get the final result:

$ sed '/=ghi=/ {r the_block                                                                           
                  N
                }' reduced 
======abc======
data1
data2
data3
======def======
data4
======jkl======
data7
======ghi======
data5
data6
======mno======
data8

(can now remove the temporary files)

2
  • this only work if bot ghi and jkl appear in the file. If only one of them appears it is deleted. Jan 1 at 10:17
  • You can move any (existing block) and insert it before any (existing) block. The answer that uses ghi or jkl is just an example -- mentioned by OP
    – dhm
    Jan 1 at 11:14
0

With GNU ed:

printf '%s\n' '/^======ghi======$/kx' '/^======jkl======$/;/^======/-1m'"'x-1" 'w output' | 
  ed -s input

Result:

$ pr -Tm input output 
======abc======                     ======abc======
data1                               data1
data2                               data2
data3                               data3
======def======                     ======def======
data4                               data4
======ghi======                     ======jkl======
data5                               data7
data6                               ======ghi======
======jkl======                     data5
data7                               data6
======mno======                     ======mno======
data8                               data8
0

Using Raku (formerly known as Perl_6)

~$ raku -e 'my @a = slurp.comb( / "======" (<alpha>**3) "======" \v [ \V+ \v]+? <?before "======" | $ > /);  \
            .print for flat @a[0..1,(2..3).sort.reverse,4..*];'  file

OR:

~$ raku -e 'my regex H { "======" <alpha>**3 "======" \v };  \
            my @a = slurp.comb( / <H> [ \V+ \v ]+? <?before <H> | $ > /);  \
            .print for flat @a[0..1,(2..3).sort.reverse,4..*]'  file

Above are solutions coded in Raku, a member of the Perl-family of programming languages. Raku features a comb function, which lets you declare a repeating pattern that you're seeking in your file (as opposed to split, which declares and usually destroys a separator between desired elements). Both solutions use Raku's slurp function, which reads the entire file into memory (without breaking on newlines).

NOTE: When re-arranging [ … ] indices within an @-sigiled array, it is very important to flatten the resulting sequence and/or object, since Raku (unlike Perl) does not auto-flatten by default.

  • In the first solution, the header line "======" (<alpha>**3) "======" \v is sought, followed by the body pattern [ \V+ \v ]+?. Here \v is the built-in vertical-whitespace character class, and \V is the built-in non- vertical-whitespace character class. The ? makes the match non-greedy (i.e. frugal). The \V+ means there has to be at least one body line (change to \V* if zero-body lines is acceptable).

  • In the second solution, the header line pattern is abstracted out into its own H regex pattern. So the following comb call can use <H> within the /…/ matcher to search for the header pattern. You can make this pattern as simple as you wish (i.e. "======"), or adapt it to other files, etc. So this code can become quite a general solution.

Sample Input:

======abc======
data1
data2
data3
======def======
data4
======ghi======
data5
data6
======jkl======
data7
======mno======
data8

Sample Output:

======abc======
data1
data2
data3
======def======
data4
======jkl======
data7
======ghi======
data5
data6
======mno======
data8

Raku has some helpful features one of which is being able to use the built-in pairs function. A second useful feature is calling raku on an object, to get back Raku's internal representation of data. Combining with Raku's put call (print using \n newline terminator):

~$ raku -e 'my regex H { "======" <alpha>**3 "======" \v };  \
            my @a = slurp.comb( / <H> [ \V+ \v]+? <?before <H> | $ > /).pairs;  \
            .raku.put for flat @a[0..1,(2..3).sort.reverse,4..*];'  file
0 => "======abc======\ndata1\ndata2\ndata3\n"
1 => "======def======\ndata4\n"
3 => "======jkl======\ndata7\n"
2 => "======ghi======\ndata5\ndata6\n"
4 => "======mno======\ndata8\n"

#OR (flattening by a different method):

~$ raku -e 'my regex H { "======" <alpha>**3 "======" \v };  \
            my @a = slurp.comb( / <H> [ \V+ \v]+? <?before <H> | $ > /).pairs;  \
            .raku.put for @a[flat(0..1;(2..3).sort.reverse;[email protected])];'  file
0 => "======abc======\ndata1\ndata2\ndata3\n"
1 => "======def======\ndata4\n"
3 => "======jkl======\ndata7\n"
2 => "======ghi======\ndata5\ndata6\n"
4 => "======mno======\ndata8\n"

https://docs.raku.org
https://raku.org

You must log in to answer this question.

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