Appending text to a file

I occasionally need to append text to a file and expected to be able to do this with a single write command. However, reading the documentation and some Google research yielded nothing.

I’ve included below my temporary solution, which is to read the file, append the text, and then write the original and appended text to the file. This works and is reasonably quick but I wondered if there’s a simpler solution. Right now, the total amount of text almost never exceeds 50 lines but that might change in some future script. Thanks.

use framework "Foundation"
use scripting additions

on appendToFile(theFile, textToAppend)
	set theText to current application's NSString's stringWithContentsOfFile:theFile encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
	if theText is missing value then return false
	set theText to theText's stringByAppendingString:textToAppend
	
	set theResult to (current application's NSString's stringWithString:theText)'s writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end appendToFile

set theFile to POSIX path of ((path to desktop as text) & "Test File.txt")
set textToAppend to "A New Line" & linefeed

appendToFile(theFile, textToAppend)

The usual ObjC way is first to check if the file exists. If not create it with the given text otherwise append the text with the NSFileHandle API. It’s actually the same procedure as AppleScript’s write … starting at eof

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

property |⌘| : a reference to current application

on appendText from textToAppend into theFile
	set fm to |⌘|'s NSFileManager's defaultManager()
	set textData to (|⌘|'s NSString's stringWithString:textToAppend)'s dataUsingEncoding:(|⌘|'s NSUTF8StringEncoding)
	if not (fm's fileExistsAtPath:theFile) as boolean then
		return (fm's createFileAtPath:theFile |contents|:textData attributes:(missing value)) as boolean
	else
		set logFileHandle to |⌘|'s NSFileHandle's fileHandleForWritingAtPath:theFile
		logFileHandle's seekToEndOfFile()
		logFileHandle's writeData:textData
		logFileHandle's closeFile()
		return true
	end if
end appendText


set theFile to POSIX path of (path to desktop) & "Test File.txt"
set textToAppend to "A New Line" & linefeed

appendText from textToAppend into theFile

Thanks Stefan–that’s excellent. NSFileHandle is entirely new to me, so I’ll spend some time with that.

FWIW, I ran some two timing tests with your ASObjC suggestion and with AppleScript’s write command. The first timing test was with no existing text file and the second was with an existing 50-line text file. In every case, the timing result was 5 to 6 milliseconds, which is great. I’ve pretty much switched my scripts to use ASObjC but it’s good to have choices.

I’ve been studying the NSFileHandle class, which raised a question, in that two of the commands in Stefan’s script are deprecated. The writeData command was replaced with writeData:error, which is an easy change. However, closeFile has not been replaced with anything I could ascertain (except possibly closeAndReturnError).

I’ve already put Stefan’s script to work in several of my scripts and everything works great. However, I noticed that the documentation for the fileHandleForWritingAtPath method contains the following and I wondered what happens when closeFile no longer works.

The documentation for the closeFile method is at:

https://developer.apple.com/documentation/foundation/nsfilehandle/1413393-closefile

The term deprecated can have a range of meanings – here it’s probably closer to a nudge.

The method changes are a (belated) effort to ensure that methods that can fail return (hopefully meaningful) errors when they do. However, unless you’re writing code that actually uses these errors, it’s a bit pointless.

Perhaps more to the point, the new methods are only available in macOS 10.15 and later, which is a pretty severe restriction for a lot of people.

It is indeed closeAndReturnError:.

I suspect that’s not going to happen for a long, long time, if ever.

Thanks Shane for the great information. I hadn’t considered the above-quoted point, which is certainly important for anyone participating in this forum. I’ve never given any consideration to the “Availability” column in the documentation, but I’ll pay more attention to that in the future.