Probably this is already out there somewhere, but I couldn’t google it up so I wrote it.
This recurses through all scripts in a folder and its subfolders and does find/replace on one or more text strings in all the scripts.
It works on any plain-text .applescript files, compiled .scpt files, and it will find the .scpt files inside applescript .app’s and get them too. It’s using osadecompile and osacompile to get the .scpt files.
It also searches all the scripts for any strings and logs all found instances - it generates a .csv in your downloads folder that shows the file’s modification date, which search text was found, the full path to the script it was found in, the line number in the script the string was found on, and the complete line of script text containing the found string. Note that it’s saving a CSV, but the code lines can contain commas. For this reason, the code lines are put in quotes so they import to spreadsheets in a single column, ignoring any commas in the code.
Comments/improvements welcome.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
set masterScriptFolder to choose folder with prompt "Choose a folder of scripts to process a text change on. All instances will be changed, including all subfolders."
-- globals used in main script and handlers
global originalStrings -- list of text strings you want to replace
global newStrings -- list of text strings you'll replace them with
global logFileData -- to log instances of text you want to search for in the scripts
global logTextInstances -- list of the text strings you want to search for the occurance of
global thisScriptName -- check to see if a script with the name of this script is in the folder you're parsing. If it is, it will be skipped. Don't want to change your "find" value to the "replace" value inside the script you're running...
-- *** SET YOUR SEARCH AND REPLACEMENT STRINGS HERE ***
-- these will be found
set originalStrings to {"/Applications/Adobe Photoshop CS6/Adobe Photoshop CS6.app", "/Applications/Adobe Illustrator CS6/Adobe Illustrator.app"}
-- and replaced with the string in the equivalent positon in this list
set newStrings to {"Adobe Photoshop CC 2019", "Adobe Illustrator"}
-- This script also searches the processed scripts for text. Any strings in this list will be searched for in the script, and a log file will be saved to your downloads folder as a CSV containing these lines, what script they were found in, the line number, and the date modified of the file. If you only want to search your scripts and not perform a find/replace, then leave the above originalStrings and newStrings lists empty. Note, search occurs AFTER find/replace.
set logTextInstances to {"CS6"}
set logFileHeaders to "Date Modified,Search Text,Path,Line Number,Line Text" & return -- column headers for CSV
set logFileData to logFileHeaders
set thisScriptName to name of me
if (count of originalStrings) ≠ (count of newStrings) then display dialog "You don't have a replacement string for each original string. These lists must be the same length."
-- Handles recursion through directory tree and passing off parsable files to main handler
script_text_replace(masterScriptFolder)
if logFileData ≠ logFileHeaders then -- if there's been data added to log
-- generate/save log file
tell application "Finder"
try
set logSaveFile to ((path to downloads folder) & "Log File.csv" as string) as alias
on error
set logSaveFile to (make new file at (path to downloads folder as alias) with properties {name:"Log File.csv", file type:"TEXT", creator type:"ttxt"}) as alias
end try
end tell
try
set logFileRef to open for access logSaveFile with write permission
on error
close access logSaveFile
set logFileRef to open for access logSaveFile with write permission
end try
set eof of logFileRef to 0
write logFileData to logFileRef
close access logFileRef
set dataLogged to true
else
set dataLogged to false
end if
-- make and display a dialog for when the script is finished
set endDialogText to "Complete."
if dataLogged is true then
set endDialogText to endDialogText & return & "Your search string(s) resulted in a logged data CSV file called \"Log File\" in your Downloads folder."
set dialogButtons to {"Done", "Open Log"}
set defaultButton to "Open Log"
else
set dialogButtons to {"Done"}
set defaultButton to "Done"
end if
set dialogData to display dialog endDialogText buttons dialogButtons default button defaultButton
set buttonChoice to the button returned of dialogData
if buttonChoice is "Open Log" then tell application "Finder" to open logSaveFile
on script_text_replace(anItem)
tell application "Finder"
set anItem to anItem as alias
if the kind of anItem is in {"Folder", "Application"} then --applications are actually bundles, and the Applescript code lives inside
set subItems to every item of anItem
repeat with anItem in subItems
my script_text_replace(anItem) -- recurse through all files in all subfolders
end repeat
else
set fileExtension to the name extension of anItem
--ignore files we can't parse. Currently works on .app (the actual code is stored an .rtf) and .applescript, not on .scpt's (yet).
if fileExtension is in {"applescript", "scpt"} then
my change_text_in_file(anItem, fileExtension)
end if
end if
end tell
end script_text_replace
-- Main function, handles the actual text replacement, search, log generation, and saving changes.
on change_text_in_file(theFile, fileExtension)
tell application "Finder" to set fileName to the name of theFile
if fileName ≠ thisScriptName then
tell application "Finder" to set fileSize to the size of theFile
if fileSize ≠ 0 then --skip empty files
if fileExtension is "scpt" then
set scriptPOSIXpath to quoted form of POSIX path of theFile
set fileText to do shell script ("osadecompile " & scriptPOSIXpath)
else if fileExtension is "applescript" then
try
set fileRef to open for access theFile with write permission
on error
close access theFile
set fileRef to open for access theFile with write permission
end try
set fileText to read fileRef
end if
set replacementItemsCount to count of originalStrings
set outputText to fileText
set textChanged to false --only bother saving if we change something
-- go through each string we want to search for
repeat with i from 1 to replacementItemsCount
set anOriginalString to item i of originalStrings
set aNewString to item i of newStrings
if fileText contains anOriginalString then
--do the replace
set outputText to my replace_chars(fileText, anOriginalString, aNewString)
set textChanged to true
end if
end repeat
--search for those strings we want to find and log
set textParagraphs to paragraphs of outputText
tell application "Finder"
set filePath to theFile as text
set modDate to the modification date of theFile
set {year:y, day:d} to modDate
tell modDate to set m to its month as integer
set modDateText to m & "/" & d & "/" & y
end tell
set lineNumber to 0
repeat with aParagraph in textParagraphs
set lineNumber to lineNumber + 1
repeat with someLogText in logTextInstances
if aParagraph contains someLogText then
-- add line to CSV for found match
set logFileData to logFileData & modDateText & "," & someLogText & "," & filePath & "," & lineNumber & "," & "\"" & aParagraph & "\"" & return
end if
end repeat
end repeat
if textChanged is true then
if fileExtension is "scpt" then
set shellCompileCommand to "osacompile -o " & scriptPOSIXpath & " << \"HEREDOC_END_TEXT\"" & linefeed & outputText & linefeed & "HEREDOC_END_TEXT"
do shell script shellCompileCommand
else if fileExtension is "applescript" then
set eof of fileRef to 0 -- writes over existing log files
write outputText to fileRef
close access fileRef
end if
end if
end if
-- don't understand why the previous line occasionally fails, but without this next line here, Script Debugger regularly reports assets left open.
if fileExtension is "applescript" then
try
close access fileRef
end try
end if
end if
end change_text_in_file
-- standard find and replace
on replace_chars(theText, searchString, replacementString)
set AppleScript's text item delimiters to the searchString
set the item_list to every text item of theText
set AppleScript's text item delimiters to the replacementString
set theText to the item_list as string
set AppleScript's text item delimiters to ""
return theText
end replace_chars