23

I feel like this should be easy, but google is totally failing me at the moment. I want to open a file, or create it if it doesn't exist, and write to it.

The following

AssignFile(logFile, 'Test.txt');
Append(logFile);

throws an error on the second line when the file doesn't exist yet, which I assume is expected. But I'm really failing at finding out how to a) test if the file exists and b) create it when needed.

FYI, working in Delphi XE.

RBA
  • 12,337
  • 16
  • 79
  • 126
Eric G
  • 3,427
  • 5
  • 28
  • 52

5 Answers5

39

You can use the FileExists function and then use Append if exist or Rewrite if not.

    AssignFile(logFile, 'Test.txt');

    if FileExists('test.txt') then
      Append(logFile)
    else
      Rewrite(logFile);

   //do your stuff

    CloseFile(logFile); 
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • 2
    You can make it even more concise and readable by using `System.IOUtils.TFile.AppendAllText( 'test.txt', contents );` - see also [awmross's answer](https://stackoverflow.com/a/7831631/2822719). It is guaranteed to do exactly what you want: "_If the file specified by the Path parameter exists, the text is appended to it; otherwise, the file is created and filled with the given text._" --from the [official documentation](http://docwiki.embarcadero.com/Libraries/Rio/en/System.IOUtils.TFile.AppendAllText) – Marcus Mangelsdorf Feb 28 '20 at 13:15
25

Any solution that uses FileExists to choose how to open the file has a race condition. If the file's existence changes between the time you test it and the time you attempt to open the file, your program will fail. Delphi doesn't provide any way to solve that problem with its native file I/O routines.

If your Delphi version is new enough to offer it, you can use the TFile.Open with the fmOpenOrCreate open mode, which does exactly what you want; it returns a TFileStream.

Otherwise, you can use the Windows API function CreateFile to open your file instead. Set the dwCreationDisposition parameter to OPEN_ALWAYS, which tells it to create the file if it doesn't already exist.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I suppose race condition will be eliminated once we add critical sections. Acquire it before the FileExists call and hold it until it's done writing. – user1651105 Apr 24 '14 at 14:58
  • 6
    TFile.Open relies on FileExist though, so it's not solution to the race condition – Eric Grange Jul 16 '14 at 10:08
  • Critical sections won't help, @User. Critical sections only work when *all* code honors them. You can't guarantee that all other programs will honor your critical section. (Indeed, they can't honor them because critical sections only apply to a single process.) You cannot solve the problem of other processes except by calling `CreateFile`. – Rob Kennedy Apr 21 '15 at 15:25
11

You should be using TFileStream instead. Here's a sample that will create a file if it doesn't exist, or write to it if it does:

var
  FS: TFileStream;
  sOut: string;
  i: Integer;
  Flags: Word;
begin
  Flags := fmOpenReadWrite;
  if not FileExists('D:\Temp\Junkfile.txt') then
    Flags := Flags or fmCreate;
  FS := TFileStream.Create('D:\Temp\Junkfile.txt', Flags);
  try
    FS.Position := FS.Size;  // Will be 0 if file created, end of text if not
    sOut := 'This is test line %d'#13#10;
    for i := 1 to 10 do
    begin
      sOut := Format(sOut, [i]);
      FS.Write(sOut[1], Length(sOut) * SizeOf(Char)); 
    end;

  finally
    FS.Free;
  end;
end;
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • Ken if you include the `fmCreate` flag the file will be always created, even if exist. – RRUZ Oct 20 '11 at 03:14
  • 1
    Instead you can use something like this `FS := TFileStream.Create('test2.txt', IfThen(FileExists('test2.txt'),fmOpenReadWrite,fmCreate));` – RRUZ Oct 20 '11 at 03:27
  • @Ken - Why do you suggest to use a TFileStream instead? It looks much more complicated when I'm just trying to write to a file quick. (Not being critical. Just learning ins and outs of Delphi.) – Eric G Oct 20 '11 at 03:41
  • @RRUZ fmCreate seems fine to me. – David Heffernan Oct 20 '11 at 06:05
  • 2
    @Eric your preferred approach doesn't do Unicode and is highly deprecated. – David Heffernan Oct 20 '11 at 06:06
  • 3
    @DavidHeffernan the Ken answer suggest which if the flags `fmOpenReadWrite or fmCreate` are used the file will be created or opened for append data. (check the line `FS.Position := FS.Size;`) but that nevers occurs because the file is always recreated if the flag `fmCreate` is present. – RRUZ Oct 20 '11 at 06:08
5

If you are just doing something simple, the IOUtils Unit is a lot easier. It has a lot of utilities for writing to files.

e.g.

procedure WriteAllText(const Path: string; const Contents: string); overload; static;

Creates a new file, writes the specified string to the file, and then closes the file. If the target file already exists, it is overwritten.

awmross
  • 3,789
  • 3
  • 38
  • 51
  • 3
    The question specifically says "create if it doesn't exist", and uses `Append`, so I'm suspecting that they want to append if it's already there. Your quote says "If the target file already exists, it is overwritten", therefore preventing appending. :) No downvote - just sayin'.... – Ken White Oct 20 '11 at 11:26
  • The (non working) example used append; the problem given didn't specify anything about appending. "Open a file... and write to it". It's not clear if he wants to append to or overwrite the existing file. – awmross Oct 20 '11 at 22:31
  • Nope. "open a file, or create it if it doesn't exist, and write to it" does *not* imply truncation; opening an existing file typically means to append to it. However, because it wasn't clear, I did **not** downvote your answer; if the question had been more clear about truncation/appending, I would have upvoted or downvoted accordingly. :) – Ken White Oct 20 '11 at 23:03
  • 1
    There is also [`AppendAllText`](http://docwiki.embarcadero.com/VCL/XE/en/IOUtils.TFile.AppendAllText) – user966939 Feb 05 '20 at 18:04
5

You can also use the load/save feature in a TStringList to solve your problem.

This might be a bad solution, because the whole file will be loaded into memory, modified in memory and then saved to back to disk. (As opposed to your solution where you just write directly to the file). It's obviously a bad solution for multiuser situations.

But this approach is OK for smaller files, and it is easy to work with and easy understand.

const
  FileName = 'test.txt';
var
  strList: TStringList;
begin
  strList := TStringList.Create;

  try
    if FileExists(FileName) then
      strList.LoadFromFile(FileName);

    strList.Add('My new line');

    strList.SaveToFile(FileName);
  finally
    strList.Free;
  end;
end;
Jørn E. Angeltveit
  • 3,029
  • 3
  • 22
  • 53