user1966709
user1966709

Reputation: 41

using tac to read a file in reverse

I am fairly new to UNIX coding and I have a file that I need to read in reverse line by line. The file has sections of code within {}. Then I need to run an awk script with this reversed file as the input. I am having our support staff install tac, but before they do I want to know if it will output what I need or if anyone can suggest an alternative.

The file has data in the form:

page 0 0 0 none
{
   layer 0 0 0 "1line" 0 8 8 3
   {
      sector 0 -1 0 32 
      {
        point_name 0 0 34543 34 44 1 1 0
        status 1 0 1 0 1 431232 "transformer" 23 12
        analog 1 0 1 0 1 321234 11.5 43 1 1 0
        ...
        ...
        ...
        device 1 0 1 "ground" 32 56 1 1 0
      }
    }
}

and I want to preserve the {} but reverse the lines between the {} so the out put looks like:

page 0 0 0 none
{
   layer 0 0 0 "1line" 0 8 8 3
   {
      sector 0 -1 0 32 
      {
        device 1 0 1 "ground" 32 56 1 1 0
        ...
        ...
        ...
        analog 1 0 1 0 1 321234 11.5 43 1 1 0
        status 1 0 1 0 1 431232 "transformer" 23 12
        point_name 0 0 34543 34 44 1 1 0
      }
    }
}

Also, does tac overwrite the input file or does it save it as a different file?

Upvotes: 4

Views: 1313

Answers (2)

Steve
Steve

Reputation: 54402

tac simply reverses the line order of a file, thus you'll need something more powerful to accomplish what you want. Also, all shell tools do not include means of overwriting an input file, without the use of 'temp' files. Some tools (e.g. sed, Perl, etc) have what's called an 'in-place editing' option, that, when a backup file suffix is not supplied, will give the net result of a file overwrite.

To accomplish what you want, I'd recommend using awk. Run like:

awk -f script.awk file > newfile

To overwrite your input file, using a temporary file, run:

awk -f script.awk file > temp && mv temp file

Contents of script.awk:

/{/ {
    print (a ? a ORS : "") $0
    f=1; a=b=""
    next
}

f && !/}/ {
    a = (a ? a ORS : "") $0
    b = $0 (b ? ORS b : "")
    next
}

/}/ && b {
    print b ORS $0
    a=b=f=""
    next
}1

Results:

page 0 0 0 none
{
   layer 0 0 0 "1line" 0 8 8 3
   {
      sector 0 -1 0 32 
      {
        device 1 0 1 "ground" 32 56 1 1 0
        ...
        ...
        ...
        analog 1 0 1 0 1 321234 11.5 43 1 1 0
        status 1 0 1 0 1 431232 "transformer" 23 12
        point_name 0 0 34543 34 44 1 1 0
      }
    }
}

Alternatively, here's the one liner:

awk '/{/ { print (a ? a ORS : "") $0; f=1; a=b=""; next } f && !/}/ { a = (a ? a ORS : "") $0; b = $0 (b ? ORS b : ""); next } /}/ && b { print b ORS $0; a=b=f=""; next }1' file

Explanation:

The first code block asks if the line contains an opening brace. If so, print anything in record 'a' followed by the current record (the line containing the opening brace). Set a flag, delete the records 'a' and 'b' and skip processing the rest of the code.

The second block will be executed if and only if the flag has been set and the line doesn't contain a closing brace. Now we build up two records 'a' and 'b'. Recall that record 'a' will be printed if a line contains an opening brace. So it must be built in the 'start to end' orientation. We then build record 'b' in the 'end to start' orientation. 'next' then skips processing the rest of the code.

The third block will be executed if the line contains a closing brace and the record 'b' (recall that this is the record build in the reverse orientation). We then print record 'b' followed by the current line (the line with the closing brace). Delete records 'a', 'b' and reset the flag. 'next' to skip processing the rest of the code. The '1' on the end enables default printing.

Upvotes: 1

Chris Seymour
Chris Seymour

Reputation: 85795

Like most UNIX (style) tools tac neither overwrites the orignal file or writes a new one, it prints to stdout, to write a new file with tac you would use redirection tac file > newfile this would save the reversed file into newfile. You cannot do what you require with tac alone, you require a script.

Here is one in awk:

{
    # store each line in the array line
    line[n++] = $0
} 

/{/ {
    # get the line number of the last line containing {
    first=NR
}

!/}/ {
    # get the line number of the last line not containing }
    last=NR
} 

END {
    # print first section in order
    for (i=0;i<first;i++) 
        print line[i]
    # print second section in reverse order
    for (i=last-1;i>=first;i--)    
        print line[i]
    # print last section in order
    for (i=last;i<NR;i++) 
        print line[i]
}

Save this to a file say reverse then run awk -f reverse file:

$ awk -f reverse file
page 0 0 0 none
{
   layer 0 0 0 "1line" 0 8 8 3
   {
      sector 0 -1 0 32 
      {
        device 1 0 1 "ground" 32 56 1 1 0
        ...
        ...
        ...
        analog 1 0 1 0 1 321234 11.5 43 1 1 0
        status 1 0 1 0 1 431232 "transformer" 23 12
        point_name 0 0 34543 34 44 1 1 0
      }
    }
}

Use redirection to create a new file awk -f reverse file > newfile.

Upvotes: 1

Related Questions