With the upgrade to the open statement that allows our program to open an existing file in the append mode, we can now continue writing notes to an old file. This is a useful feature, but it assumes that the user would want to continue writing. There are situations where I’d want to start writing from the beginning; that is, overwrite the file. To allow for more general behavior, and to not surprise the user, it may be a good idea to allow them to choose whether to keep writing to an existing file, or start over, as shown in the following listing.
Listing 6.10 Prompting the user if a file exists
./qn daily_todo.txt
Finish chapter draft
Email Jerry
neural-fortran pull request
^C
./qn daily_todo.txt ❶
File daily_todo.txt already exists! ❷
[O]verwrite, [A]ppend, [Q]uit: ❸
❶ Tries to write to an existing file
❸ Prompts and lets user decide
How can we know programmatically whether or not a file exists? This is where the inquire statement comes in, as shown in the following listing.
Listing 6.11 Checking whether a file exists without opening it
logical :: file_exists ❶
...
inquire(file=trim(filename), exist=file_exists) ❷
❶ Declares a logical variable to store the existence status of the file
❷ Inquires by filename whether it exists or not
You can inquire about a file by its name or its I/O unit number (inquire(fileunit, ...)). Of course, the latter is possible only if you’ve already opened the file and its I/O unit number has been assigned. Here, we’re inquiring by filename, which is necessary because the file hasn’t been opened yet. The exist keyword parameter takes a logical variable whose value will be either .true. or .false. on successful completion of the inquire statement. Whenever you open a new file, the file will be present and detectable by inquire, even if you haven’t written to it yet.
The following listing shows the new version of the program that checks for the presence of the file, and prompts the user if the file exists.
Listing 6.12 Updated program that prompts the user if the file exists
program qn
use iso_fortran_env, only: stdin => input_unit, &
stdout => output_unit
implicit none
integer :: fileunit
character(len=9999) :: filename, text
character(len=6) :: pos
logical :: file_exists
if (command_argument_count() < 1) stop 'Usage: qn <filename>'
call get_command_argument(1, filename)
inquire(file=trim(filename), exist=file_exists) ❶
pos = 'rewind' ❷
if (file_exists) then
write(stdout, '(a)') & ❸
'File ' // trim(filename) // ' already exists!' ❸
do
write(*, '(a)', advance='no') & ❹
'[O]verwrite, [A]ppend, [Q]uit: ' ❹
read(stdin, '(a)') text ❹
if (any(trim(text) == ['O', 'o'])) then
write(stdout, '(a)') & ❺
'Overwriting ' // trim(filename) ❺
exit ❺
else if (any(trim(text) == ['A', 'a'])) then
pos = 'append' ❻
write(stdout, '(a)') & ❻
'Appending to ' // trim(filename) ❻
exit ❻
else if (any(trim(text) == ['Q', 'q'])) then
stop ❼
end if
end do
end if
open(newunit=fileunit, file=trim(filename), &
action='write', position=pos)
do
read(stdin, '(a)') text
write(fileunit, '(a)') trim(text)
flush(fileunit)
end do
end program qn
❶ Checks for presence of the file
❷ Assumes writing from scratch
❸ Prints warning message if file exists
❹ Prompts the user and awaits their response
❺ Leaves loop if user chose “overwrite”
❻ Sets the position parameter and leaves loop if user chose “append”
❼ Stops the program if user chose “quit”
The code added to the program starts with the inquire statement and ends immediately before the open statement. Its purpose is to determine whether the position parameter in the open statement should have the value rewind or append. We start by assuming that the file will be opened in rewind mode, which is the case if the file is not present (start a new file) or the user chooses to overwrite an existing file. If the requested file is present, the program will warn the user and prompt them regarding whether to overwrite (“O” or “o”) or append (“A” or “a”) to the file. At this point, the user can also choose to quit (“Q” or “q”) the program without committing any changes to the file. Note that if the user input doesn’t match any of the coded options (overwrite, append, or quit), no if branch is matched, and we’ll prompt the user again for input.
If you look carefully, you’ll notice that the first write statement in the program has advance='no' as a keyword parameter. This makes the position in the file not move to the next record (line) after printing the message. As a result, the user will be entering their choice on the same line:
./qn daily_todo.txt
File daily_todo.txt already exists!
[O]verwrite, [A]ppend, [Q]uit: a ❶
❶ The user’s choice appears on the same line.
If omitted, advance has the value yes by default, so any such write statement will move the position to the next line after executing, and likewise for the read statement.
It’s possible to use the read and write statement without advancing the file position to the next record. Although this may seem like a purely aesthetic feature, there are cases where it can be quite useful. A common example that comes to mind is progress bars in the terminal. If you’re interested in fancy progress bars for your Fortran app, check out the forbear library by Stefano Zaghi on GitHub: https://github.com/ szaghi/forbear. It uses nonadvancing I/O in combination with the Unicode character set to display some impressive, dynamic progress bars.