Working with Lists

I’m used to scripting in PHP which has an easy function for every possible thing you’d want to do with an array. Even javascript has caught up a lot.

But now I’m trying to make some tools in applescript and i need suggestions.

First task, whats the simplest way to select two different random items from a list?. Potentially a very long list too.

Second, is there an easy way to filter through long lists? For example, say I’m making a list of the files in a folder. And there are a thousand .txt files, and a few .log files. I want to make a list, but only of the .txt files.

First: AppleScript provides the some keyword which can get a random element from a list.
To get two different random elements you can write something like this

set theList to {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

set randomElements to {}
repeat while (count randomElements) < 2
	set something to some item of theList
	if randomElements does not contain something then set end of randomElements to something
end repeat

Second: You can filter items on disk in the Finder with the whose clause

tell application "Finder"
	set allTextFiles to files of desktop whose name extension is "txt"
end tell

However from experience this can be quite slow. AppleScriptObjC with help of the Foundation framework can do it too, but much faster.

1 Like

Gotcha, I can work with both of those. I figured there was a way to filter folder listing but i didn’t see anything in the docs :confused:

So I came up with this way of picking two random items from the list, basically the same concept you posted but slightly different implementation:

	set PayloadDelete to some item from PayloadFileList
	set PayloadCopy to ""
	repeat while (PayloadCopy = "") or (PayloadCopy = PayloadDelete)
		set PayloadCopy to some item of PayloadFileList
	end

But THEN I realized, I don’t have to even do the loop. I can just re-make the file list after the first file is picked and deleted. That will potentially slow the script down a bit though so I may still use the loop, I’m not sure. I’ll do some speed tests of making the file list with a folder of 10,000ish items and see how much time it takes.

So this is unexpected. Its taking so long for the finder to list the items of a folder (with 10,000 files in it) that the script keeps timing out even as I keep increasing the timeout.

MEANWHILE, I can use `do shell script “ls /path/to/folder” to get results instantly.

However those results are a multi-line string. Can I explode that string into a list? And also use the same filtering where I only add rows that end in “.txt”? If the answer to the last part is no, I can do the old trick I just found for another script, where I set items i don’t want to “missing value” then convert the old list to a new list.

Yes

set fileNames to paragraphs of (do shell script “ls /path/to/folder)

And in your script the empty-string-thing is not needed. You can use an unspecified repeat loop and exit repeat.

set PayloadDelete to some item of PayloadFileList

repeat
	set PayloadCopy to some item of PayloadFileList
	if PayloadCopy ≠ PayloadDelete then exit repeat
end repeat

And it’s some item of PayloadFileList

Maybe you could describe more in detail what you are going to accomplish.

1 Like

LOL i was getting so close to the answer you just gave me. I was trying to do “words of do shell script” and that split on the newline and the periods. Then I tried “lines” and that just failed as if lines aren’t a thing. And right then I saw this post and turns out “paragraphs” was the magic word.

So using ‘ls’ and converting to an array takes about 1 to 2 seconds.
Using set PayloadFileList to name of files of alias "bloop:stress_test:" whose name extension is "txt" still timed out even after 20 minutes.

So is there another magic phrase I can do to say something like:
paragraphs of filelist that end with “.txt” ?

Update: Actually turns out I don’t know how long the Finder method of listing this huge folder will take because its not that the script was timing out, turns out it fully locked up my Finder.

Yes, there is. Use the find command from the shell

set sourceFolder to quoted form of POSIX path of "bloop:stress_test:"
set file_list to paragraphs of (do shell script "/usr/bin/find" & space & sourceFolder & space & "-maxdepth 1 -type f -name '*.txt'")

However this returns full (POSIX) paths.
If you omit the -maxdepth 1 parameter find performs a deep search including subfolders

Ok everything is working and working well. The only problem is that it takes about a second to run ls but then another 5 seconds or so to loop through the results and remove the non-.txt results (in a folder of 10,000 items).
Because this is running in a loop of it’s own, that time is going to add up. I can probably grep up a filter that will be super fast, ill look in to that next time. Thats all for tonight, thanks for the help.

Ok, here is my take.
I would never use “Finder” for file work. I use “System Events”, it is much faster
I also never use a whose clause. It slows things down drastically.
Best to get all files first, then parse them out in your script like so…

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property fileList : {}
set myFolder to "Extra HD:Folder Test"
set fileList to findFileExt(myFolder, "txt")

on findFileExt(aFolder, ext)
	script F
		property fileList : missing value
		property extList : missing value
		property txtList : {}
	end script
	local i
	tell application "System Events"
		tell files of folder aFolder
			set {F's extList, F's fileList} to {name extension, path}
		end tell
	end tell
	repeat with i from 1 to count F's fileList
		if item i of F's extList = ext then set end of F's txtList to item i of F's fileList
	end repeat
	return F's txtList
end findFileExt

In a folder with 10,000 files, it finishes in under 1 second.

Do not use Finder for filesystem operations. Use System Events, which operates almost as quickly as the shell.

tell application id ("com.apple.SystemEvents") ¬
        to tell the folder ("/path/to/folder") ¬
        to tell the files whose name extension ¬
        is "txt" and visible is true to set fs ¬
        to the name 

That’ll get you a list of text files within a given folder, returning a list of basenames (file names with extensions only). The folder path can be provided in HFS (colon) format or in Posix (slash) format (for which you’re free to use the tilde (~) as an abbreviation for your home folder, just as you’d normally do in the shell).

Then grab a random item from the list as you’ve been doing.

There are very, very few reasons one would ever need to use Finder to operate on or enumerate files. For when it is genuinely necessary or advantageous, using the traditional techniques to do so will be extremely slow but there are implementations that can allow one to make the most efficient use of Finder.