Silverfrost Forums

Welcome to our forums

BACKSPACE

1 May 2013 1:09 #12129

Imagine the following trivial dataset:

A A 1 BBB 2

I only want to read the numbers, but I need to read them in unformatted, and the leading text is of a consistent length, but is not a consistent number of words. I may not always want to read the first line.

Consider the following program to read the data. It skips line 1, and then reads the entire line to work out how long the leading string is (not shown). It then attempts to backspace to read line 2 again, but this time to skip over the text and then read in the data.

PROGRAM p
  REAL :: d
  CHARACTER(LEN=64) :: c
!
  OPEN (UNIT=11,FILE='external_file.txt',ACTION='read',FORM='formatted',STATUS='old')
  READ (UNIT=11,FMT=*)
  READ (UNIT=11,FMT='(A)') c
! ... some calculations to determine that '(A3)' will be adequate for skipping the leading string
  BACKSPACE (UNIT=11)
  READ (UNIT=11,FMT='(A3)',ADVANCE='no') c
  READ (UNIT=11,FMT=     *) d
  PRINT *, d
END PROGRAM p

This all works great, except if the line used to diagnose the length of the string is the last line in the file. If it is, using BACKSPACE works as long as the last line ends with a carriage return, but if it does not then BACKSPACE takes you a line earlier. Try reading the data at the top of this post in these two ways. Without a carriage return at the end of line 2, the program reads in line 1; with a carriage return it reads in line 2.

As far as I can tell, that's how it should be (FTN95 is doing what the standard says), but it would still be nice to know where I am in the file after invoking BACKSPACE. IOSTAT returns a zero in both cases. There are of course work-arounds such as reading the data from c rather than backspacing at all, but I don't even know whether c has been long enough to read in the entire line of data. I could rewind and get back to this point again, but it may have taken a long time to find this location.

Anybody have any good ideas?[/code]

1 May 2013 9:55 #12137

This looks like a bug in FTN95.

When the last line of the file doesn't have a carriage return, this record can still be read. After this read, the 'current record' should then be the End-of-file record.

A subsequent use of BACKSPACE should then position to the last line again.

On the NAG compiler your program gives 2.0 whether or not there is a carriage return or not on the last line.

With FTN95 I get 2.0 if there is a carriage return and 1.0 if there isn't.

1 May 2013 9:57 #12138

Quoted from brucebowler Well to start, I'd read each line into a string and then work on that string with internal reads (and friends) and eliminate the use of backspace all together.

Are list-directed reads of internal files (characters) allowed?

2 May 2013 3:40 #12139

Well to start, I'd read each line into a string and then work on that string with internal reads (and friends) and eliminate the use of backspace all together.

I normally would, but the files may be massive. I only have to do one backspace, essentially to identify in what column the data start, and then I can read the rest of the file. If I had to read in every line as a string and then read the string, I would assume (but have not tested) that that would be less efficient. The other reason for avoiding reading in each line as a string is that I would have to impose a limit on the width of the file. In practice the limit may not be very restrictive if I define a large enough string, but in general I try to avoid imposing any limits other than those of the compiler.

Am interested to find out if David's assessment that this is a compiler bug is true ...

3 May 2013 1:54 #12146

I would expect that doing a double read would have a minimal performance cost. Try it and you will be pleasently surprised. The double read approach could be:

PROGRAM p 
  REAL    :: d 
  CHARACTER(LEN=64) :: c 
  integer :: n, i, iostat, l
! 
  OPEN (UNIT=11,FILE='external_file.txt',ACTION='read',FORM='formatted',STATUS='old') 
!
!
  do i = 1, huge(i)
    READ (UNIT=11,FMT='(A)', iostat=iostat) c 
     if (iostat/= 0) then
       write (*,*) 'file read error : iostat=',iostat
       exit
     end if
! ... some calculations to determine that '(A3)' will be adequate for skipping the leading string 
    n = 4
    l = len_trim (c)  
    if (l < n) then
       write (*,*) 'no data in line',i
       cycle
     end if
!
    READ (c(n:), FMT=*, iostat=iostat) d 
    PRINT *, i, d, l, iostat
  end do 
!
end

! external_file.txt
aaa  1
abc 2
xxx 3

zz  4

Not sure of what happens if FMT=* and c(4:) is blank, ie l<4 You might want to enhance your test for n and l

If you need to get n from the last line only, then you could read the file, storing the last valid line for each read, then rewind and read all the file ? Faster would be to store all lines in a character array, ie character c(num_lines)64 read (c(line_number)(n:), fmt=, iostat=iostat) d ! should work

Not sure if you are saying you need all lines of data or only the last line ?

Using an internal read of 'c' is definately better than a backspace, especially for a large file. The array c can have nearly 33,554,432 (2^25) lines if allocatable.

John

3 May 2013 2:40 #12147

This example demonstrates storing the file as 1 pass read. Alternatively you could do a 2-pass read to determine the number of lines, or a single pass read to store only the last line

John

PROGRAM p 
  REAL    :: d 
  CHARACTER (LEN=64), allocatable, dimension(:) :: c
  CHARACTER (LEN=64) :: last_c
  integer :: n, i, iostat, l, last_line, max_lines
! 
  OPEN (UNIT=11,FILE='external_file.txt',ACTION='read',FORM='formatted',STATUS='old') 
!
  max_lines =30000000
  allocate ( c(max_lines) )
  write (*,*) 'c has',size (c) * 64,' bytes'
!
  do i = 1, max_lines
    READ (UNIT=11,FMT='(A)', iostat=iostat) c(i) 
     if (iostat/= 0) then
       write (*,*) 'file read error at line',i,' : iostat=',iostat
       exit
     end if
    l = len_trim (c(i))  
    if (l > 3) then
      last_line = i
      last_c = c(i)
    else
      write (*,*) 'no data in line',i
    end if
  end do
!
! ... some calculations to determine that '(A3)' will be adequate for skipping the leading string 
    n = 4
    l = len_trim (c(last_line))  
!
    READ (c(last_line)(n:), FMT=*, iostat=iostat) d 
    PRINT *, last_line, d, l, i, iostat
!
end
Please login to reply.