I have often said that my least-favorite feature of the Fortran language is “OPEN on a connected unit”. In most cases, programmers invoke this accidentally and are confused by its behavior. But it does provide a way to do something useful for which the language doesn’t provide an alternative. Let’s explore.
Assume you have a program that has opened file
foo.dat on unit 10 for formatted I/O. You call a subroutine that needs to do some I/O on its own, so it has:
open (unit=10, file='bar.dat', form='formatted')
What happens? Unit 10 is silently closed and the new file is opened on unit 10! This happens if the file identified by the new OPEN is different from the file currently connected on unit 10. As someone who implemented this for VAX FORTRAN, I can tell you that it is not as simple as it may appear. You can’t just compare the values of the FILE= specifier, you have to do some diligence to determine that it is (or is not) the same file. If you’re lucky, (as I was for VAX/VMS), the file system provides a means to get the fully-specified path to each file and you can compare those. Even on VMS, though, it was possible for two different paths to represent the same file, and this is even more common on today’s popular operating systems. You do the best you can…
Now what happens if in fact the file specified by the new OPEN is the same as the current connection? This is not an error (as I have had users insist it should be), but the effect depends on what other specifiers you give in the second OPEN:
- If STATUS= is specified, its value must be ‘OLD’
- If POSITION= is specified, its value must agree with the current file position
- If the file is connected for formatted I/O, values of the specifiers for “changeable modes” may differ – and I’ll explain that below (files connected for unformatted I/O have no changeable modes)
- Other than ERR=, IOMSG= and IOSTAT=, no additional specifiers may appear
If the rules above are not followed, that’s an error. If you omit FILE=, then you are referring to the currently connected file.
Now, about those “changeable modes”… These are the formatted I/O modes that have defaults but that can be overridden in an I/O statement or a format specifier:
- BLANK= – whether blanks in numeric input are treated as zeroes or are ignored
- DECIMAL= – whether the radix point in real number output is a comma or a point
- DELIM= – whether character values in list-directed or namelist-directed output are delimited with quotes, apostrophes or nothing
- PAD= – whether short records on input are padded with blanks or not
- ROUND= – which rounding mode is used for formatted input and output
- SIGN= – whether positive values are output with a plus sign or not
There is one additional changeable mode, the default scale factor, but this doesn’t have an OPEN specifier – it can be changed only by a P format edit descriptor.
If you OPEN a unit connected for formatted I/O and a specifier for a changeable mode is included, its value becomes the new default for that connection. Note that all of these modes can be specified in an individual READ or WRITE statement, being effective for that statement only, and all except DELIM and PAD can also be changed during processing of a format with a control edit descriptor (SP, BZ, RU, etc.)
Here’s a little example:
integer, parameter :: fortytwo = 42
real, parameter :: pi = 3.14159
character(*), parameter :: doctor = 'Fortran'
open (unit=10,file='foo.dat',delim="QUOTE",decimal="POINT",sign="PLUS") write (10, *) fortytwo, pi, doctor open (unit=10,file='foo.dat',delim="APOSTROPHE",decimal="COMMA",sign="SUPPRESS") write (10, *) fortytwo, pi, doctor end program modes
When built and run, file foo.dat contains:
+42 +3.141590 "Fortran"
42 3,141590 'Fortran'
(Spacing between values in list-directed output will vary depending on implementation – I collapsed the spaces here for clarity.)
When you read above, where I presented a subroutine that wanted to use the same unit number that was already open, you may have thought to yourself, “Self? What if the subroutine doesn’t know which unit numbers are in use? How can I determine a free unit number?” You could put INQUIRE (UNIT=n, OPENED=logvar) in a loop and try to determine an available number, but that’s ugly and you don’t know which unit numbers might be valid for the implementation.
This issue has a special meaning to me, as a limited solution to it was the very first set of routines I designed and coded for the VMS Run-Time Library back in 1979. I added the routines LIB$GET_LUN and LIB$FREE_LUN, which would reserve and free Fortran unit numbers from a fixed set that, it was hoped, the program wasn’t already using. It was a hack, to be honest, but it was adequate at the time.
Fortran 2008 came to the rescue with the NEWUNIT= specifier in OPEN. This would pick a guaranteed-available unit number, open the file, and return the number chosen to you. The unit number was negative (less than zero), and as that had never been allowed before it was guaranteed to not be in use by an existing program. So if you’re writing a library routine that needs to do its own I/O but you don’t know a unit to use, use NEWUNIT= to get one.
That’s all for today’s installment! Feel free to leave a comment below, or suggest a topic for a future Doctor Fortran post!
Hi Steve, thank you for posting the article. I hadn’t appreciated that the NEWUNIT specifier will return a negative unit number. A negative unit number “feels strange”, but I’ll get used to that!
What am I to make of the fact that I OPEN a file (originally specified as FORM=’BINARY’ in 1995 or so), and with the following code, and an INQUIRE stmt, I am returned a *negative* 2 for the RECL?
icltap=2 ! unit to open the existing binary file CLTAPE
OPEN (UNIT= icltap, FILE= ‘CLTAPE’, STATUS=’OLD’,
INQUIRE (UNIT=icltap, RECL=ioRECLEN)
WRITE (*,'(2X, A28, I2)’)
+ ‘RECORD LENGTH From INQUIRE= ‘, ioRECLEN
— output to screen:
RECORD LENGTH From INQUIRE= -2
Why a Negative 2? Is the file only reading 2 bytes at a time? Can I change it to read 4 bytes at a time?
I tried the ‘LEN=’ modifier in OPEN (to make it read 4 bytes at a time), but that gives a syntax error.
-2 is returned for RECL in INQUIRE if the file is connected for stream access. Stream files don’t have a fixed record length. You’ll find that Fortran “inquiry” intrinsics and other features return negative numbers when a valid value is unavailable. For example, SELECTED_REAL_KIND(999) will return -1 (on most implementations) as there is no real kind that can represent 999 decimal digits. Some of these can return -2 or -3, if there is more than one “invalid” case.
When you are reading a stream unformatted file, it’s the size of your I/O list items that determines how many bytes are transferred.