1

I have a file with the data like this

BEGIN
hello2
5
world1
END
BEGIN
hello4
2
world5
END
BEGIN
hello6
4
END

I want to sort the lines in the following manner based on the number inside the block. The numbers are isolated and unique.

BEGIN
hello4
2
world5
END
BEGIN
hello6
4
END
BEGIN
hello2
5
world1
END

I know how to print the blocks with sed and awk. Thats about it.

    # Prints the blocks including the BEGIN and END tags
    cat file | sed -n '/^BEGIN$/,/^END$/p'

    # Prints the blocks exluding the BEGIN and END tags
    awk '/^BEGIN$/ {show=1;next} /^END$/{show=0}  { print }' file
  • It would be best if you edited your question to show us what you tried. – dhag Dec 8 '15 at 18:41
  • The numbers are isolated in their lines? Are they unique? – Kira Dec 8 '15 at 19:19
  • The numbers are isolated and unique. – navmad Dec 8 '15 at 19:43
1

Every time a BEGIN line is encountered, separately read the next numeric line from the file using a separate handle via getline. Print each line from the file with two prefixes, the numeric value that was retrieved previously and the file record number of the current record(thus all lines within the same BEGIN .. END block end up with the same value in prefix 1 corresponding to the number embedded within the block). Feed this to external sort and cut utilities to handle the prefix-based sorting following by discarding the prefixes.

awk '/BEGIN/{"awk \\$0+0==\\$0 "FILENAME | getline x}
{print x"~"FNR"~"$0 | "sort -k1,1n -k2,2n -t~ | cut -f3- -d~"}' file
BEGIN
hello4
2
world5
END
BEGIN
hello6
4
END
BEGIN
hello2
5
world1
END
2

Using GNU awk:

gawk '
    BEGIN { RS="\nEND\n"; ORS = RS; FS = "\n" }
    { record[$3] = $0 }
    END {
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (val in record) print record[val]
    }
' file

Based on your data, I'm assuming there's always one line between BEGIN and the number.

The PROCINFO line defines how the "records" array is traversed. See https://www.gnu.org/software/gawk/manual/html_node/Controlling-Scanning.html

  • BEGIN hello4 4 world5 END BEGIN hello6 5 world1 END BEGIN hello2 2 END – navmad Dec 8 '15 at 20:28
  • ^^ is the result. It doesn't work :( – navmad Dec 8 '15 at 20:29
  • did you use gawk? – glenn jackman Dec 8 '15 at 20:35
  • 1
    @navmad PROCINFO["sorted_in"] is a relatively recent GNU awk feature, I think: if your version of gawk doesn't support it, you should be able to achieve the same result using asorti i.e. by calling asorti(record,sorted) then for (val in sorted) print record[sorted[val]] – steeldriver Dec 8 '15 at 22:32
1

The first line aggregates the text block, line by line, and also tries to find a number to use as a sorting critea afterwards. The if-clause if($0+0==$0) evaluates true when it finds a number.

The second block executes when it finds an "END" in the input, so it saves the block in an associative array, indexing it using the number found in the block.

awk '{block=block"\n"$0; if($0+0==$0) num=$0;} 
/^END$/ {blks[num]=block; block=""} 
END {for(key in blks) print blks[key]}' file

The last line is just printing every entry of the array when it reaches the end of the input file. Note that the associative array is already sorted (that is how it works internally), so we just need to iterate over it printing every entry.

For instance, look at the following awk script:

echo | awk '{a[2]="b"; a[1]="a"; a[3]="c"; for(key in a) print a[key];}'

It outputs:

a
b
c

In my answer I'm printing an extra \n before each block, I suppose this is not a problem. The output for your example is:

BEGIN
hello4
2
world5
END

BEGIN
hello6
4
END

BEGIN
hello2
5
world1
END

If you don't want the extra line, replace the first block of my awk script with:

{if(length(block)=="0")block=$0; else{block=block"\n"$0; if($0+0==$0) num=$0}}

Here is the one-liner version:

awk '{if(length(block)=="0")block=$0; else{block=block"\n"$0; if($0+0==$0) num=$0}} /^END$/ {blks[num]=block; block=""} END {for(key in blks) print blks[key]}' file
  • 1
    I tried your script. It doesn't sort the blocks. BEGIN hello4 4 world5 END BEGIN hello6 5 END BEGIN hello2 2 world1 END ^^ is the output I am getting – navmad Dec 8 '15 at 20:01
  • What is the output awk -W version? Also, use the one-liner at the end of my answer to make sure you are not copying it wrong. – Kira Dec 8 '15 at 20:09
  • GNU Awk 3.1.5 . I'll try again. : – navmad Dec 8 '15 at 20:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.