Fixed length records

From Rosetta Code
Task
Fixed length records
You are encouraged to solve this task according to the task description, using any language you may know.

Fixed length read/write

Before terminals, computers commonly used punch card readers or paper tape input.

A common format before these devices were superseded by terminal technology was based on the Hollerith code, Hollerith code.

These input devices handled 80 columns per card and had a limited character set, encoded by punching holes in one or more rows of the card for each column.

These devices assumed/demanded a fixed line width of 80 characters, newlines were not required (and could not even be encoded in some systems).

Task

Write a program to read 80 column fixed length records (no newline terminators (but newline characters allowed in the data)) and then write out the reverse of each line as fixed length 80 column records.

Samples here use printable characters, but that is not a given with fixed length data. Filenames used are sample.txt, infile.dat, outfile.dat.

Note: There are no newlines, inputs and outputs are fixed at 80 columns, no more, no less, space padded. Fixed length data is 8 bit complete. NUL bytes of zero are allowed.

These fixed length formats are still in wide use on mainframes, with JCL and with COBOL (which commonly use EBCDIC encoding and not ASCII). Most of the large players in day to day financial transactions know all about fixed length records and the expression logical record length.

Sample data

To create the sample input file, use an editor that supports fixed length records or use a conversion utility. For instance, most GNU/Linux versions of dd support blocking and unblocking records with a conversion byte size.

Line 1...1.........2.........3.........4.........5.........6.........7.........8
Line 2
Line 3
Line 4

Line 6
Line 7
     Indented line 8............................................................
Line 9                                                                 RT MARGIN


prompt$ dd if=sample.txt of=infile.dat cbs=80 conv=block
will create a fixed length record file of 80 bytes given newline delimited text input.
prompt$ dd if=infile.dat cbs=80 conv=unblock
will display a file with 80 byte logical record lengths to standard out as standard text with newlines.


Bonus round

Forth systems often include BLOCK words. A block is 1024 bytes. Source code is stored as 16 lines of 64 characters each (again, no newline character or sequence to mark the end of a line).

Write a program to convert a block file to text (using newlines). Trailing spaces should be excluded from the output.

Also demonstrate how to convert from a normal text file to block form. All lines either truncated or padded to 64 characters with no newline terminators. The last block filled to be exactly 1024 characters by adding blanks if needed. Assume a full range of 8 bit byte values for each character.

The COBOL example uses forth.txt and forth.blk filenames.

Ada

Ada string type is a fixed-length string.

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   subtype Block is String (1 .. 80);
   Infile_Name  : String := "infile.dat";
   outfile_Name : String := "outfile.dat";
   Infile       : File_Type;
   outfile      : File_Type;

   Line : Block := (Others => ' ');
begin
   Open (File => Infile, Mode => In_File, Name => Infile_Name);
   Create (File => outfile, Mode => Out_File, Name => outfile_Name);

   Put_Line("Input data:");
   New_Line;
   while not End_Of_File (Infile) loop
      Get (File => Infile, Item => Line);
      Put(Line);
      New_Line;
      for I in reverse Line'Range loop
         Put (File => outfile, Item => Line (I));
      end loop;
   end loop;
   
   Close (Infile);
   Close (outfile);

   Open(File => infile,
        Mode => In_File,
        Name => outfile_name);
   New_Line;
   Put_Line("Output data:");
   New_Line;
   while not End_Of_File(Infile) loop
      Get(File => Infile,
          Item => Line);
      Put(Line);
      New_Line;
   end loop;
end Main;
Output:
Input data:

Line 1...1.........2.........3.........4.........5.........6.........7.........8
Line 2                                                                          
Line 3                                                                          
Line 4                                                                          
                                                                                
Line 6                                                                          
Line 7                                                                          
     Indented line 8............................................................
Line 9                                                                 RT MARGIN

Output data:

8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL
                                                                                
                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI     
NIGRAM TR                                                                 9 eniL

AWK

# syntax: GAWK -f FIXED_LENGTH_RECORDS.AWK
BEGIN {
    vlr_fn = "FIXED_LENGTH_RECORDS.TXT"
    flr_fn = "FIXED_LENGTH_RECORDS.TMP"
    print("bef:")
    while (getline rec <vlr_fn > 0) { # read variable length records
      printf("%-80.80s",rec) >flr_fn # write fixed length records without CR/LF
      printf("%s\n",rec)
    }
    close(vlr_fn)
    close(flr_fn)
    print("aft:")
    getline rec <flr_fn # read entire file
    while (length(rec) > 0) {
      printf("%s\n",revstr(substr(rec,1,80),80))
      rec = substr(rec,81)
    }
    exit(0)
}
function revstr(str,start) {
    if (start == 0) {
      return("")
    }
    return( substr(str,start,1) revstr(str,start-1) )
}
Output:
bef:
Line 1...1.........2.........3.........4.........5.........6.........7.........8
Line 2
Line 3
Line 4

Line 6
Line 7
     Indented line 8............................................................
Line 9                                                                 RT MARGIN
aft:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

C++

#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>

void reverse(std::istream& in, std::ostream& out) {
    constexpr size_t record_length = 80;
    char record[record_length];
    while (in.read(record, record_length)) {
        std::reverse(std::begin(record), std::end(record));
        out.write(record, record_length);
    }
    out.flush();
}

int main(int argc, char** argv) {
    std::ifstream in("infile.dat", std::ios_base::binary);
    if (!in) {
        std::cerr << "Cannot open input file\n";
        return EXIT_FAILURE;
    }
    std::ofstream out("outfile.dat", std::ios_base::binary);
    if (!out) {
        std::cerr << "Cannot open output file\n";
        return EXIT_FAILURE;
    }
    try {
        in.exceptions(std::ios_base::badbit);
        out.exceptions(std::ios_base::badbit);
        reverse(in, out);
    } catch (const std::exception& ex) {
        std::cerr << "I/O error: " << ex.what() << '\n';
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
Output:

After converting with dd:

8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

COBOL

      *> Rosetta Code, fixed length records
      *> Tectonics:
      *>   cobc -xj lrecl80.cob
       identification division.
       program-id. lrecl80.

       environment division.
       configuration section.
       repository.
           function all intrinsic.

       input-output section.
       file-control.
           select infile
               assign to infile-name
               organization is sequential
               file status is infile-status
           .
           select outfile
               assign to outfile-name
               organization is sequential
               file status is outfile-status
           .

       data division.
       file section.
       fd infile.
           01 input-text pic x(80).

       fd outfile.
           01 output-text pic x(80).

       working-storage section.
       01 infile-name.
          05 value "infile.dat".
       01 infile-status pic xx.
          88 ok-input value '00'.
          88 eof-input value '10'.

       01 outfile-name.
          05 value "outfile.dat".
       01 outfile-status pic xx.
          88 ok-output value '00'.

       procedure division.

       open input infile
       if not ok-input then
           display "error opening input " infile-name upon syserr
           goback
       end-if

       open output outfile
       if not ok-output
           display "error opening output " outfile-name upon syserr
           goback
       end-if

      *> read lrecl 80 and write the reverse as lrecl 80
       read infile
       perform until not ok-input
           move function reverse(input-text) to output-text

           write output-text
           if not ok-output then
               display "error writing: " output-text upon syserr
           end-if
           read infile
       end-perform

       close infile outfile

      *> from fixed length to normal text, outfile is now the input file
       open input outfile
       if not ok-output then
           display "error opening input " outfile-name upon syserr
           goback
       end-if

       read outfile
       perform until not ok-output
           display function trim(output-text trailing)
           read outfile
       end-perform

       close outfile

       goback.
       end program lrecl80.

Given a starting file sample.txt of

Line 1...1.........2.........3.........4.........5.........6.........7.........8
Line 2
Line 3
Line 4

Line 6
Line 7
     Indented line 8............................................................
Line 9                                                                 RT MARGIN

And a setup pass of

prompt$ dd if=sample.txt of=infile.dat cbs=80 conv=block
Output:
prompt$ cobc -xj fixed-length.cob
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
..............................................................8 enil fo tuO
NIGRAM TR                                                                 9 eniL
prompt$ file outfile.dat
outfile.dat: ASCII text, with very long lines, with no line terminators

blocks

Demonstrate text to Forth source block form.

      *> Rosetta Code fixed length records, text to Forth block
       identification division.
       program-id. blocking.

       environment division.
       configuration section.
       repository.
           function all intrinsic.

       input-output section.
       file-control.
           select infile
               assign to infile-name
               organization is line sequential
               file status is infile-status
           .
           select outfile
               assign to outfile-name
               organization is sequential
               file status is outfile-status
           .

       data division.
       file section.
       fd infile.
           01 input-text pic x(64).

       fd outfile.
           01 output-text pic x(64).

       working-storage section.
       01 infile-name.
          05 value "forth.txt".
       01 infile-status pic xx.
          88 ok-input value '00'.
          88 eof-input value '10'.

       01 outfile-name.
          05 value "forth.blk".
       01 outfile-status pic xx.
          88 ok-output value '00'.

       procedure division.

      *> read a line, padded to or truncated at 64 as defined in FD
       open input infile
       if not ok-input then
           display "error opening input " infile-name upon syserr
           goback
       end-if

       open output outfile
       if not ok-output
           display "error opening output " outfile-name upon syserr
           goback
       end-if

       move 0 to tally
       read infile
       perform until not ok-input
           move input-text to output-text

           write output-text
           if not ok-output then
               display "error writing: " output-text upon syserr
           end-if

           add 1 to tally
           if tally > 15 then move 0 to tally end-if

           read infile
       end-perform

      *> Output up to next 1024 byte boundary
       if tally > 0 then
           compute tally = 16 - tally
           move spaces to output-text

           perform tally times
               write output-text
               if not ok-output then
                   display "error writing: " output-text upon syserr
               end-if
           end-perform
       end-if

       close infile outfile

       goback.
       end program blocking.

Demonstrate Forth source block to text form.

      *> Rosetta Code fixed length records, Forth blocks to text.
       identification division.
       program-id. unblocking.

       environment division.
       configuration section.
       repository.
           function all intrinsic.

       input-output section.
       file-control.
           select infile
               assign to infile-name
               organization is sequential
               file status is infile-status
           .
           select outfile
               assign to outfile-name
               organization is line sequential
               file status is outfile-status
           .

       data division.
       file section.
       fd infile.
           01 input-text pic x(64).

       fd outfile.
           01 output-text pic x(64).

       working-storage section.
       01 infile-name.
          05 value "forth.blk".
       01 infile-status pic xx.
          88 ok-input value '00'.
          88 eof-input value '10'.

       01 outfile-name.
          05 value "forth.txt".
       01 outfile-status pic xx.
          88 ok-output value '00'.

       procedure division.

       open input infile
       if not ok-input then
           display "error opening input " trim(infile-name) upon syserr
           goback
       end-if

       open output outfile
       if not ok-output
           display "error opening write " trim(outfile-name) upon syserr
           goback
       end-if

      *> read a fixed length line, 64 characters
       read infile
       perform until not ok-input
           move trim(input-text) to output-text

           write output-text
           if not ok-output then
               display "error writing: " output-text upon syserr
           end-if
           read infile
       end-perform

       close infile outfile

       goback.
       end program unblocking.

Delphi

See Pascal

Free Pascal

See Pascal


FreeBASIC

Const As Byte longRegistro = 80
Const archivoEntrada As String = "infile.dat"
Const archivoSalida  As String = "outfile.dat"

Dim As String linea

'Abre el archivo origen para lectura
Open archivoEntrada For Input As #1
'Abre el archivo destino para escritura
Open archivoSalida For Output As #2

Print !"Datos de entrada:\n"
Do While Not Eof(1)
    Line Input #1, linea    'lee una linea
    Print linea             'imprime por pantalla esa linea
    For i As Integer = longRegistro To 1 Step -1
        Print #2, Chr(Asc(linea, i));    'escribe el inverso de la linea 
    Next i
    Print #2, Chr(13);
Loop
Close #1, #2

Dim As Integer a
Open archivoSalida For Input As #2

Print !"\nDatos de salida:\n"
Do While Not Eof(2)
    Line Input #2, linea
    For j As Integer = 0 To Len(linea)-1
        Print Chr(linea[j]);
        a += 1: If a = longRegistro Then a = 0 : Print Chr(13) 
    Next j
Loop
Close
Sleep


Go

package main

import (
    "fmt"
    "log"
    "os"
    "os/exec"
)

func reverseBytes(bytes []byte) {
    for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 {
        bytes[i], bytes[j] = bytes[j], bytes[i]
    }
}

func check(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    in, err := os.Open("infile.dat")
    check(err)
    defer in.Close()

    out, err := os.Create("outfile.dat")
    check(err)

    record := make([]byte, 80)
    empty := make([]byte, 80)
    for {
        n, err := in.Read(record)
        if err != nil {
            if n == 0 {
                break // EOF reached
            } else {
                out.Close()
                log.Fatal(err)
            }
        }
        reverseBytes(record)
        out.Write(record)
        copy(record, empty)
    }
    out.Close()

    // Run dd from within program to write output.dat
    // to standard output as normal text with newlines.
    cmd := exec.Command("dd", "if=outfile.dat", "cbs=80", "conv=unblock")
    bytes, err := cmd.Output()
    check(err)
    fmt.Println(string(bytes))
}
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

Bonus round

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "strings"
)

func check(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func block2text(inputFile, outputFile string) {
    in, err := os.Open(inputFile)
    check(err)
    defer in.Close()

    out, err := os.Create(outputFile)
    check(err)
    defer out.Close()

    line := make([]byte, 64)
    empty := make([]byte, 64)
    for {
        n, err := in.Read(line)
        if err != nil {
            if n == 0 {
                break // EOF reached
            } else {
                log.Fatal(err)
            }
        }
        str := string(line)
        str = strings.TrimRight(str, " \000")
        out.WriteString(str + "\n")
        copy(line, empty)
    }
}

func text2block(inputFile, outputFile string) {
    in, err := os.Open(inputFile)
    check(err)
    defer in.Close()

    out, err := os.Create(outputFile)
    check(err)
    defer out.Close()

    scanner := bufio.NewScanner(in)
    count := 0
    for scanner.Scan() {
        str := scanner.Text()
        count++
        le := len(str)
        if le > 64 {
            str = str[0:64]
        } else if le < 64 {
            str = fmt.Sprintf("%-64s", str)
        }
        out.WriteString(str)
    }
    if rem := count % 16; rem > 0 {
        str := strings.Repeat(" ", (16-rem)*64)
        out.WriteString(str)
    }
}

func main() {
    block2text("block.dat", "block.txt")
    text2block("block.txt", "block2.dat")
}

J

Solution:

Using the 720 byte input file linked to in the Raku entry.

   _80 ]\ fread 'flr-infile.dat'   NB. reads the file into a n by 80 array
   _80 |.\ fread 'flr-infile.dat'  NB. as above but reverses each 80 byte chunk
   'flr-outfile.dat' fwrite~ , _80 |.\ fread 'flr-infile.dat'  NB. as above but writes result to file (720 bytes)
   processFixLenFile=: fwrite~ [: , _80 |.\ fread              NB. represent operation as a verb/function

Example Usage:

   'flr-outfile.dat' processFixLenFile 'flr-infile.dat'  NB. returns number of bytes written
720

jq

To read the raw input and write out raw strings, the command-line options -R (raw input), -r (raw output) and -j options are necessary:

   jq -Rrj -f program.jq infile.dat

program.jq:

Currently gojq does not include the `_nwise/1` filter provided by jq, and in any case jq's implementation is suboptimal, so we define `nwise` here so that it can be used for both array and string inputs. Notice that the inner helper function, `n`, has arity 0, allowing jq's tail-call optimization to work.

def cut($n):
def nwise($n):
  def n: if length <= $n then . else .[0:$n] , (.[$n:] | n) end;
  n;
nwise(80) | explode | reverse | implode

To show that this does indeed work, the output of `/bin/dd cbs=80 conv=unblock` is shown:

Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL
1+1 records in
1+1 records out
644 bytes transferred in 0.030319 secs (21241 bytes/sec)

Julia

The program reads from an "infile.dat" file created with dd, as described in the task instructions.

function processfixedlengthrecords(infname, blocksize, outfname)
    inf = open(infname)
    outf = open(outfname, "w")
    filedata = [ read(inf, blocksize) for _ in 1:10 ]
       
    for line in filedata
        s = join([Char(c) for c in line], "")
        @assert(length(s) == blocksize)
        write(outf, s)
    end
end
    
processfixedlengthrecords("infile.dat", 80, "outfile.dat")


Lua

-- prep: convert given sample text to fixed length "infile.dat"
local sample = [[
Line 1...1.........2.........3.........4.........5.........6.........7.........8
Line 2
Line 3
Line 4
 
Line 6
Line 7
     Indented line 8............................................................
Line 9                                                                 RT MARGIN]]
local txtfile = io.open("sample.txt", "w")
txtfile:write(sample)
txtfile:close()
os.execute("dd if=sample.txt of=infile.dat cbs=80 conv=block > /dev/null 2>&1")

-- task: convert fixed length "infile.dat" to fixed length "outfile.dat" (reversed lines)
local infile = io.open("infile.dat", "rb")
local outfile = io.open("outfile.dat", "wb")
while true do
  local line = infile:read(80)
  if not line then break end
  outfile:write(string.reverse(line))
end
infile:close()
outfile:close()

-- output:
os.execute("dd if=outfile.dat cbs=80 conv=unblock")
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
2 eniL
3 eniL
4 eniL

6 eniL
7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL
1+1 records in
1+1 records out

M2000 Interpreter

Fixed length Record

Form Buffer object (hold a memory block) we can read a byte at offset. So Print Eval(Line80,3) return the 4th byte, and Return Line80, 2:=255,5:=0 set two bytes (unsigned, we place any number and interpreter convert it to byte).

Module FixedFile {
      Read fixed$
      OldLocale=Locale
      \\ chr$(string_argument$)
      \\ use Locale to convert from Ansi to Utf-16LE
      \\ Read Ansi form files also use Locale
      Locale 1032
      Try ok {
            \\ Make the file first
            Const Center=2
            Font "Courier New"
            Bold 0
            Italic 0
            Def long m, z=1, f
            Def text2read$,test3write$
            Form 100, 50  ' 100 by 60 characters
            Document txt$={Line 1...1.........2.........3.........4.........5.........6.........7.........8
                  Line 2
                  Line 3
                  Line 4
                  
                  Line 6
                  Line 7
                       Indented line 8............................................................
                  Line 9                                                                 RT MARGIN
                  }
            \\ use Help Open in M2000 console for details
            \\ Method one
            Report Center,  "Make file"
            \\ for WIDE Random \\  for Utf-16
            Open fixed$ for Random Exclusive as #f len=80
            m=Paragraph(txt$, 0)
            z=1
            If forward(txt$, m) then
                  while m, z<10
                        text2write$=Paragraph$(txt$,(m))
                        Print format$("Len:{0}, Data: {1}",Len(text2write$),text2write$)
                        Put #f, text2write$ , z
                        \\ record number from 1
                        \\ if number is total records plus one
                        \\ we append a record
                        z++     
                  End while
            End If
            Print "Press any key"
            Push Key$ : Drop
            Form 80, 40
            Report Center,  "Method1"
            For z=1 to 9
                  Get #f, text2read$, z
                  text2read$=StrRev$(text2read$)
                  Put #f, text2read$, z
                  Print text2read$
            Next z
            Close #f
            Report Center,  "Method2"
            \\ Method2
            \\ Buffer Clear Line80 ... \\ to clear memory
            \\ here we write all bytes so not needed
            Buffer Line80 as byte*80
            m=filelen(fixed$)
            If m mod 80=0 Then
                  m=1
                  \\ now Get/Put read write at byte position
                  \\ we have to use seek to move to byte position
                  \\ This way used for Binary files
                  Open fixed$ for Input as #f1
                  Open fixed$ for Append as #f2
                  while not eof(#f1)
                        seek #f1, m
                        Rem Print seek(#f)
                        Get #f1, Line80
                        Return line80,0:=Str$(StrRev$(Chr$(Eval$(line80,0,80))))
                        seek #f2, m
                        Put #f2, Line80
                        seek #f1, m
                        Get #f1, Line80
                        Print Chr$(Eval$(line80,0,80))
                        m+=80
                  End While
                  Close #f1
                  Close #f2
            End if
      }
      \\ use Close with no parameters for close all files if something happen
      If error then Close: Print Error$
      Locale OldLocale    
}
FixedFile "fixed.random"

Forth Blocks

Form 80,50
Print "Forth's Blocks"
\\ Forth Blocks
Structure Line16 {
      a`Line as byte*64
}
NewBlock=lambda Line16 -> {
      Buffer a`Block as Line16*16
      \\ fill spaces
      Return a`Block, 0:=str$(string$(" ",1024))
      =a`Block
}
\\ Events are value types, but for closures and groups are reference types
Event Doit {
      Read something$
}
Header=Doit
DisplayBlock= Lambda NewBlock, Line16, Doit, Header (Blocks`File$,Block`Number, UseLocale=1033)->{
      Page1=NewBlock()
      Open Blocks`File$ for input as #f
            Seek #f, 1024*(Block`Number-1)+1
            Get #f,Page1
      Close #f
      Document NewDoc$
      \\ need to convert from Ansi
      Call Event Header, "Block:"+Str$(Block`Number)
      oldlocale=locale
      locale UseLocale
      For i=0 to 15
            lineAny$=chr$(Eval$(Page1,i, Len(Line16)))
            Call Event Doit, format$("{0::-2} {1}",i,chr$(Eval$(Page1,i, Len(Line16))))
      Next i
      locale oldlocale
}
Document ForthCode$={( Large letter F) 
      : STAR    [CHAR] * EMIT ;
      : STARS   0 DO  STAR  LOOP ;
      : MARGIN  CR 30 SPACES ;
      : BLIP    MARGIN STAR ;
      : BAR     MARGIN 5 STARS ;
      : F       BAR BLIP BAR BLIP BLIP CR ;
      }
\\ Make Document bigger than 16 lines
\\ doc.par(ForthCode$)  return paragraphs (here we have no wrap)
\\ actuall lines per layer can be found from Report (the renderer)
\\ using Reportlines. layer can be the printer page.
ForthCode$=string$(ForthCode$,5)
Print "Make Block"
Page1=NewBlock()
Locale 1033
Blocks`File$="Forth Blocks"
Block`Number=1
\\ Apppend three times same blocks
For Pass=1 to 3
      If Doc.Len(ForthCode$)>0 then
            For i=1 to Doc.par(ForthCode$)-1
            \\ we give order number but Paragraph$ use unique number for paragraphs
            \\ if we didn't delete or insert lines, then these two are the same
                  Print Paragraph$(ForthCode$, Paragraph(ForthCode$,i))
                  \\ convert to Ansi using Locale
                  \\ offset from 0, so minus 1
                  \\ offset 1 is at len(Line16)
                  \\ Page1(0) is the real address, but here doesn't matter
                  Return Page1, (i-1) mod 16:=Str$(Paragraph$(ForthCode$, Paragraph(ForthCode$,i)))
                  if i mod 16=0 then Gosub SaveBlock
            Next i
            i--
            if Not i mod 16=0 then  Gosub SaveBlock
      End if
Next Pass
\\ now we read from disk
Class DocumentKeeper {
      Document Text$
      Function AppendLine(aline$) {
            \\ right trim$
            .Text$<=Mid$(Trim$("*"+aline$),2)+{
            }
      }
}
Function Disp(aline$) {
      Print aline$
}
DocumentKeeper=DocumentKeeper()
Event Doit New Disp()
Event Header New Disp()
For i=1 to Block`Number-1
      Call DisplayBlock(Blocks`File$, i)
      Print "Press any key"
      Push key$ : Drop
Next i
Event Doit Drop Disp()
Event Doit New DocumentKeeper.AppendLine()
Event Header Hold
For i=1 to Block`Number-1
      Call DisplayBlock(Blocks`File$, i)
Next i
Report DocumentKeeper.Text$

End 
SaveBlock:
      Print "Save as Number ";Block`Number
      If Exist(Blocks`File$) then 
            \\ check if there is space for this block
            If Not filelen(Blocks`File$) div 1024>=Block`Number-1 Then
                  Error "Wrong Block Number"
            End if
      Else
            Print "not exist"
            Open Blocks`File$ for output as #f
            Close #f
            Wait 100
            \\ or Error "Empty File" if we wish only read
            If Block`Number<>1 then Error "Wrong Block Number"
      End if
      Open Blocks`File$ for append as #f
      Seek #f, Block`Number*1024-1023 ' so we seek to first byte
      Put #f, Page1
      Close #f
      Block`Number++
      Page1=NewBlock()
Return

Mathematica / Wolfram Language

FixedRecordReverse[inFile_File, outFile_File, length_ : 80] :=
  Module[{inStream, outStream, line, byte},
    WithCleanup[
      inStream = OpenRead[inFile, BinaryFormat -> True];
      outStream = OpenWrite[outFile, BinaryFormat -> True];
    ,
      While[True,
        line = {};
        Do[
          byte = BinaryRead[inStream, "Byte"];
          AppendTo[line, byte]
        ,
          length
        ];
        If[byte === EndOfFile, Break[]];
        line = Reverse[line];
        BinaryWrite[outStream, line]
      ]
    ,
      Close[outStream];
      Close[inStream];
    ];
    (* Verify the result *)
    RunProcess[{"dd", "if=" <> outFile[[1]], "cbs=" <> ToString[length], "conv=unblock"}, "StandardOutput"]
  ];

This function will both return the reversed string in the notebook AND write the record to outfile.dat:

Input:
In[]:= FixedRecordReverse[File["infile.dat"], File["outfile.dat"]]
Output:

Out[]= "\
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL
"

Neko

/**
 fixed length records, in Neko
*/

var LRECL = 80

var reverse = function(s) {
  var len = $ssize(s)
  if len < 2 return s

  var reverse = $smake(len)
  var pos = 0
  while len > 0 $sset(reverse, pos ++= 1, $sget(s, len -= 1))
  return reverse
}

var file_open = $loader.loadprim("std@file_open", 2)
var file_read = $loader.loadprim("std@file_read", 4)
var file_write = $loader.loadprim("std@file_write", 4)
var file_close = $loader.loadprim("std@file_close", 1)

var input = file_open("infile.dat", "r")
var output = file_open("outfile.dat", "w")

var len
var pos = 0
var record = $smake(LRECL)

while true {
  try {
    len = file_read(input, record, pos, LRECL)
    if len != LRECL $throw("Invalid read")

    len = file_write(output, reverse(record), pos, LRECL)
    if len != LRECL $throw("Invalid write")
  } catch a break;
}
file_close(input)
file_close(output)
Output:
prompt$ dd if=infile.txt cbs=80 conv=block status=none of=infile.dat
prompt$ nekoc fixed-length.neko
prompt$ neko fixed-length
prompt$ dd if=outfile.dat cbs=80 conv=unblock status=none
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

Nim

Task: fixed length records

import algorithm

proc reverse(infile, outfile: string) =

  let input = infile.open(fmRead)
  defer: input.close()
  let output = outfile.open(fmWrite)
  defer: output.close()

  var buffer: array[80, byte]
  while not input.endOfFile:
    let countRead = input.readBytes(buffer, 0, 80)
    if countRead < 80:
      raise newException(IOError, "truncated data when reading")
    buffer.reverse()
    let countWrite = output.writeBytes(buffer, 0, 80)
    if countWrite < 80:
      raise newException(IOError, "truncated data when writing")

reverse("infile.dat", "outfile.dat")
Output:

Contents of file “result.txt" created by command dd if=outfile.dat of=result.txt cbs=80 conv=unblock:

8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

Bonus: Forth blocks

import strutils

const EmptyRecord = repeat(' ', 64)

#---------------------------------------------------------------------------------------------------

proc textToBlock(infile, outfile: string) =
  ## Read lines of a text file and write them as 64 bytes records.

  let input = infile.open(fmRead)
  defer: input.close()
  let output = outfile.open(fmWrite)
  defer: output.close()

  var count = 0
  while not input.endOfFile:
    var record = input.readLine()
    if record.len > 64:
      record.setLen(64)                         # Truncate to 64 bytes.
    elif record.len < 64:
      record &= repeat(' ', 64 - record.len)    # Pad to 64 bytes.
    if output.writeChars(record, 0, 64) != 64:
      raise newException(IOError, "error while writing block file")
    inc count

  # Complete block with empty records.
  let rem = count mod 16
  if rem != 0:
    for _ in 1..(16 - rem):
      if output.writeChars(EmptyRecord, 0, 64) != 64:
        raise newException(IOError, "error while writing block file")

#---------------------------------------------------------------------------------------------------

proc blockToText(infile, outfile: string) =
  ## Read 64 bytes records and write them as lines trimming spaces.

  let input = infile.open(fmRead)
  defer: input.close()
  let output = outfile.open(fmWrite)
  defer: output.close()

  var line: string
  while not input.endOfFile:
    line.setLen(64)   # Allocate space for the 64 bytes to read.
    if input.readChars(line, 0, 64) != 64:
      raise newException(IOError, "error while reading block file")
    line = line.strip(leading = false, trailing = true) & '\n'
    output.write(line)

#———————————————————————————————————————————————————————————————————————————————————————————————————

when isMainModule:
  textToBlock("block1.txt", "block.dat")
  blockToText("block.dat", "block2.txt")

Pascal

program reverseFixedLines(input, output, stdErr);
const
	lineLength = 80;
var
	// in Pascal, `string[n]` is virtually an alias for `array[1..n] of char`
	line: string[lineLength];
	i: integer;
begin
	while not eof() do
	begin
		for i := 1 to lineLength do
		begin
			read(line[i]);
		end;
		
		for i := lineLength downto 1 do
		begin
			write(line[i]);
		end;
		writeLn();
	end;
end.

Perl

open $in,  '<', 'flr-infile.dat';
open $out, '>', 'flr-outfile.dat';

while ($n=sysread($in, $record, 80)) {   # read in fixed sized binary chunks
     syswrite $out, reverse $record;     # write reversed records to file
     print reverse($record)."\n"         # display reversed records, line-by-line
}
close $out;
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL
                                                                                
                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI     
NIGRAM TR                                                                 9 eniL

input file and output file

There Is More Than One Way To Do It

Read with standard <> using $/ set to a ref-to-int to read a fixed block size.

{
local ($/, @ARGV) = (\80, 'infile.dat');
open my $out, '>', 'outfile.dat' or die $!;
print $out scalar reverse while <>; # can read fixed length too :)
close $out;
}

Slurp and reverse each line in place.

use Path::Tiny;
path('outfile.dat')->spew(path('infile.dat')->slurp =~ s/.{80}/reverse $&/gesr);

Double reverse.

use Path::Tiny;
path('outfile.dat')->spew(reverse unpack '(a80)*', reverse path('infile.dat')->slurp);

Bonus round: convert the sample file to Forth Block format.

use Path::Tiny;
path('outfile.dat')->spew(pack '(A64)16', split /\n/, path('sample.txt')->slurp);

Bonus Round: convert Forth Block format to plain text format.

use Path::Tiny;
path('sample2.txt')->spew(map "$_\n", unpack '(A64)16', path('outfile.dat')->slurp);

Phix

You might want to investigate using builtins such as get_text() and read_lines(), but they were never really designed for fixed length record handling and for me, for this task, their limits outweight any advantages. To simplify matters, this creates any files needed on the fly.

without js -- file i/o
constant sample_text = """
Line 1...1.........2.........3.........4.........5.........6.........7.........8
Line 2
Line 3
Line 4
 
Line 6
Line 7
     Indented line 8............................................................
Line 9                                                                 RT MARGIN"""
 
constant indat = "infile.dat",
         outdat = "outfile.dat"
 
if not file_exists(indat) then
    object text = get_text("sample.txt")
    if text=-1 then text = sample_text end if
    sequence s = split(text,'\n')
    integer fn = open(indat,"wb")
    for i=1 to length(s) do
        puts(fn,s[i]&repeat(' ',80-length(s[i])))
    end for
    close(fn)
    printf(1,"%s created (%d bytes)\n",{indat,get_file_size(indat)})
end if
 
function get_block(integer fn, size)
    string res = ""
    for i=1 to size do
        res &= getc(fn)
    end for
    return res
end function
 
integer fn = open(indat,"rb"),
        isize = get_file_size(indat)
puts(1,"reversed:\n")
for i=1 to isize by 80 do
    ?reverse(get_block(fn,80))
end for
close(fn)
 
-- Bonus part, 16*64 (=1024) block handling:
 
procedure put_block(integer fn, sequence lines)
    lines &= repeat("",16-length(lines))
    for i=1 to length(lines) do
        string li = lines[i]
        if length(li)>64 then
            li = li[1..64]
        else
            li &= repeat(' ',64-length(li))
        end if
        puts(fn,li)
    end for
end procedure
 
fn = open(outdat,"wb")
put_block(fn,split(sample_text,'\n'))
close(fn)
integer osize = get_file_size(outdat)
printf(1,"\n%s created (%d bytes):\n",{outdat,osize})
fn = open(outdat,"rb")
sequence lines = {}
for i=1 to osize by 64 do
    string line = get_block(fn,64)
    ?line
    lines = append(lines,trim_tail(line,' '))
end for
lines = trim_tail(lines,{""})
puts(1,"\ntrimmed:\n")
pp(lines,{pp_Nest,1})
{} = delete_file(indat) -- (for consistent 2nd run)
Output:
infile.dat created (720 bytes)
reversed:
"8.........7.........6.........5.........4.........3.........2.........1...1 eniL"
"                                                                          2 eniL"
"                                                                          3 eniL"
"                                                                          4 eniL"
"                                                                                "
"                                                                          6 eniL"
"                                                                          7 eniL"
"............................................................8 enil detnednI     "
"NIGRAM TR                                                                 9 eniL"

outfile.dat created (1024 bytes):
"Line 1...1.........2.........3.........4.........5.........6...."
"Line 2                                                          "
"Line 3                                                          "
"Line 4                                                          "
"                                                                "
"Line 6                                                          "
"Line 7                                                          "
"     Indented line 8............................................"
"Line 9                                                          "
"                                                                "
"                                                                "
"                                                                "
"                                                                "
"                                                                "
"                                                                "
"                                                                "

trimmed:
{"Line 1...1.........2.........3.........4.........5.........6....",
 "Line 2",
 "Line 3",
 "Line 4",
 {},
 "Line 6",
 "Line 7",
 "     Indented line 8............................................",
 "Line 9"}

Python

infile = open('infile.dat', 'rb')
outfile = open('outfile.dat', 'wb')

while True:
    onerecord = infile.read(80)
    if len(onerecord) < 80:
        break
    onerecordreversed = bytes(reversed(onerecord))
    outfile.write(onerecordreversed)

infile.close()
outfile.close()

Output:

 dd if=outfile.dat cbs=80 conv=unblock
 8.........7.........6.........5.........4.........3.........2.........1...1 eniL
 2 eniL
 3 eniL
 4 eniL
 
 6 eniL
 7 eniL
 ............................................................8 enil detnednI
 NIGRAM TR                                                                 9 eniL
 1+1 records in
 1+1 records out

Raku

(formerly Perl 6) Link to a copy of the input file used: flr-infile.dat

Essentially the same as task Selective_File_Copy except more boring.

$*OUT = './flr-outfile.dat'.IO.open(:w, :bin) orelse .die; # open a file in binary mode for writing
while my $record = $*IN.read(80) {                       # read in fixed sized binary chunks
     $*OUT.write: $record.=reverse;                      # write reversed records out to $outfile
     $*ERR.say: $record.decode('ASCII');                 # display decoded records on STDERR
}
close $*OUT;
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL
                                                                                
                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI     
NIGRAM TR                                                                 9 eniL

REBOL

inp: open     %infile.dat
out: open/new %outfile.dat

while [ not empty? line: copy/part inp 80 ][ write out reverse line ]

close inp
close out

REXX

/*REXX pgm reads fixed─length 80 byte records;  reverses each record, displays to term. */
iFID= 'FIXEDLEN.TXT'                             /*the file's filename (used for input).*/
call charin iFID, 1, 0                           /*open the file, point rec pointer to 1*/
                                                 /* [+]  just to be safe, position file.*/
            do j=1  while chars(iFID) >= 80      /*read data records of LRECL ≡ eighty. */
            @.j= charin(iFID, , 80)              /*read a data record of eighty bytes.  */
            end   /*j*/
#= j - 1                                         /*adjust # of records (J is 1 too high)*/
            do k=1  for #                        /* [↓]  process all the records read.  */
            say reverse(@.k)                     /* reverse a record and write to term. */
            end   /*k*/                          /*stick a fork in it,  we're all done. */
output   when using the default input file:
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

Ruby

open("outfile.dat", "w") do |out_f|
  open("infile.dat") do |in_f| 
     while record = in_f.read(80) 
       out_f << record.reverse
     end
  end
end  # both files automatically closed

Rust

use std::fs::File;
use std::io::prelude::*;
use std::io::{BufReader, BufWriter};

fn reverse_file(
    input_filename: &str,
    output_filename: &str,
    record_len: usize,
) -> std::io::Result<()> {
    let mut input = BufReader::new(File::open(input_filename)?);
    let mut output = BufWriter::new(File::create(output_filename)?);
    let mut buffer = vec![0; record_len];
    while input.read(&mut buffer)? == record_len {
        buffer.reverse();
        output.write_all(&buffer)?;
    }
    output.flush()?;
    Ok(())
}

fn main() {
    match reverse_file("infile.dat", "outfile.dat", 80) {
        Ok(()) => {}
        Err(error) => eprintln!("I/O error: {}", error),
    }
}
Output:

After converting with dd:

8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

Tcl

chan configure stdin  -translation binary
chan configure stdout -translation binary

set lines [regexp -inline -all {.{80}} [read stdin]]
puts -nonewline [join [lmap line $lines {string reverse $line}] ""]

# More "traditional" way

# while {[set line [read stdin 80]] ne ""} {
# 	puts -nonewline [string reverse $line]
# }
Output:
$ exec 2>/dev/null
$ dd if=sample.txt cbs=80 conv=block | tclsh flr.tcl | dd cbs=80 conv=unblock
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

TXR

80 Column Task

At the shell prompt:

$ txr -e '(let ((buf (make-buf 80))
                (nread 80))
            (while (eql nread 80)
              (set nread (fill-buf-adjust buf))
              (buf-set-length buf 80)
              (put-buf (nreverse buf))))' < infile80.bin > outfile80.bin
$ dd if=outfile80.bin cbs=80 conv=unblock
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL

                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI
NIGRAM TR                                                                 9 eniL

1+1 records in
1+1 records out
725 bytes copied, 8.658e-05 s, 8.4 MB/s

This handles a final bit that is shorter than 80. When buf-set-length sets the buffer size back to 80, zero padding is applied. The zero-padded record is reversed.

Forth Blocks Task

Encoding

The following program is called forth-enblock.tl:

(typedef forth-line (array 64 bchar))

(let ((lno 0)
      (blanks (make-buf 64 #\space)))
  (whilet ((line (get-line)))
    (put-obj (fmt "~-64,64a" line) (ffi forth-line))
    (inc lno))
  (while (plusp (mod (pinc lno) 16))
    (put-buf blanks)))
Output:
$ txr forth-enblock.tl  < forth-enblock.tl > forth-enblock.blk
$ xxd forth-enblock.blk 
00000000: 2874 7970 6564 6566 2066 6f72 7468 2d6c  (typedef forth-l
00000010: 696e 6520 2861 7272 6179 2036 3420 6263  ine (array 64 bc
00000020: 6861 7229 2920 2020 2020 2020 2020 2020  har))           
00000030: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000040: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000050: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000060: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000070: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000080: 286c 6574 2028 286c 6e6f 2030 2920 2020  (let ((lno 0)   
[... snip ...]
000003e0: 2020 2020 2020 2020 2020 2020 2020 2020                  
000003f0: 2020 2020 2020 2020 2020 2020 2020 2020

Decoding

This is forth-deblock.tl

(typedef forth-block (array 16 (array 64 bchar)))

(defsymacro fbsz (sizeof forth-block))

(let* ((buf (make-buf fbsz))
       (block-view (carray-buf buf (ffi forth-block)))
       (nread fbsz))
  (while (eql fbsz nread)
    (set nread (fill-buf-adjust buf))
    (when (plusp nread)
      (buf-set-length buf fbsz #\space)
      (each ((row [block-view 0]))
        (put-line (trim-right #/ +/ row))))))
Output:
$ txr forth-deblock.tl < forth-enblock.blk
(typedef forth-line (array 64 bchar))

(let ((lno 0)
      (blanks (make-buf 64 #\space)))
  (whilet ((line (get-line)))
    (put-obj (fmt "~-64,64a" line) (ffi forth-line))
    (inc lno))
  (while (plusp (mod (pinc lno) 16))
    (put-buf blanks)))







Wren

Library: Wren-str
import "io" for File
import "./str" for Str

var records = File.read("infile.dat")
File.create("outfile.dat") { |f|
    for (record in Str.chunks(records, 80)) {
        record = record[-1..0]
        f.writeBytes(record)
    }
}
records = File.read("outfile.dat")
for (record in Str.chunks(records, 80)) System.print(record)
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL
                                                                                
                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI     
NIGRAM TR                                                                 9 eniL

Bonus round

Library: Wren-ioutil
import "./ioutil" for File, FileUtil
import "./str" for Str

var blockToText = Fn.new { |blockFileName, textFileName|
    var block = File.read(blockFileName)
    var lb = FileUtil.lineBreak
    File.create(textFileName) { |f|
        for (chunk in Str.chunks(block, 64)) {
            f.writeBytes(chunk.trimEnd() + lb)
        }
    }
}

var textToBlock = Fn.new { |textFileName, blockFileName|
    var lines = FileUtil.readLines(textFileName).where { |l| l != "" }.toList
    var text = lines.map { |l| (l.count < 64) ? l + (" " * (64-l.count)) : l[0..63] }.join()
    var rem = text.count % 1024
    if (rem > 0) text = text + (" " * (1024 - rem))      
    File.create(blockFileName) { |f| f.writeBytes(text) }
}

// create a block file
File.create("data.blk") { |f|
    f.writeBytes("a" * 1024)
    f.writeBytes("b" * 1024)
    f.writeBytes("c" * 1024)
    f.writeBytes("d" * 1024)
}

blockToText.call("data.blk", "data.txt")
textToBlock.call("data.txt", "data2.blk")
System.print(FileUtil.areDuplicates("data.blk", "data2.blk"))
Output:
true

XPL0

include xpllib;
int  N, I;
char A(80);
[OpenInFile("infile.dat");
OpenOutFile("outfile.dat");
for N:= 1 to 9 do
    [for I:= 0 to 80-1 do A(I):= ChIn(3);
     for I:= 80-1 downto 0 do ChOut(3, A(I));
    ];
OpenInFile("outfile.dat");
for N:= 1 to 9 do
    [for I:= 0 to 80-1 do ChOut(0, ChIn(3));  CrLf(0)];
]
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL
                                                                                
                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI     
NIGRAM TR                                                                 9 eniL

zkl

Reading from the dd formatted file, which is 720 characters:

Line 1...1.........2.........3.........4.........5.........6.........7.........8Line 2                                                                          Line 3                                                                          Line 4                                                                                                                                                          Line 6                                                                          Line 7                                                                               Indented line 8............................................................Line 9                                                                 RT MARGIN
File("infile.dat","rb")	// could contain nulls and newlines
   // Since we are writing to a ASCII terminal, ignore nulls
.walker(3).chunk(80,String).pump(Console.println,"reverse"); // 3-->read chars
Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL
                                                                          2 eniL
                                                                          3 eniL
                                                                          4 eniL
                                                                                
                                                                          6 eniL
                                                                          7 eniL
............................................................8 enil detnednI     
NIGRAM TR                                                                 9 eniL

To write to a file (as one big line), preserving nulls:

in,out := File("infile.dat","rb"), File("outfile.dat","wb");
in.walker(0).chunk(80).pump(out,"reverse"); // may contain nulls and newlines
// 0-->read bytes, chunk to list of bytes, reverse and write the bytes
in.close(); out.close();

outfile.dat:

Output:
8.........7.........6.........5.........4.........3.........2.........1...1 eniL                                                                          2 eniL                                                                          3 eniL                                                                          4 eniL                                                                                                                                                          6 eniL                                                                          7 eniL............................................................8 enil detnednI     NIGRAM TR                                                                 9 eniL

Bonus Round

// read block file (as in read a file of blocks) to text
fcn readFourthBlock(inFileNm){
   out,f,buf := Sink(String), File(inFileNm,"rb"), Data(1024);
   while(f.read(1024,buf,False)){
      // read 64 chars from buf, strip ws from right side, repeat
      buf.walker(3).chunk(64,String).pump(out,T("strip",1),'+("\n"));
   }
   f.close(); out.close();
}
// read text file and write as block to file
fcn formatToFourthBlock(inFileNm,outFileNm){
   n,blk,in,out := 0, Data(), File(inFileNm,"r"), File(outFileNm,"wb");
   foreach line in (in.walker(11)){	// right side stripped
      if(not line) blk.write(" "*64);
      else blk.write(line.walker().chunk(64,String).pump(String,"%-64s".fmt));
      if(blk.len()==1024){ out.write(blk); blk.clear(); }
   }
   if(blk) out.write(blk, " "*(1024 - blk.len()));
   f.close(); out.close();
}