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.