2

I work with a large collection of mostly-text files subdivided by series, periodically mirroring them from an upstream repository and repackaging them using a shell script from a slightly chaotic source organization into more orderly and compact forms. The sources are collected in directories of files for each series numbering from a handful to hundreds per set, with a semi-orderly human-oriented naming convention dating back to the 90's with this general pattern:

multi-word-series-name[-$subsetnumber]-$docnumber[.{txt,html,pdf}]

Where a series may or may not have subsets, subsets may be numbered with roman or arabic numerals, a series may start with numbered docs in no subset and later get a subset labeled "II" or "2", and so on. These names work well for people using them from the Finder, which in most cases orders them in a human-sensible way and detects that name-II-1 comes later than name-6 and that name-2 does not come after name-19. Because my repackaging consists of assembling the latest version of each series into single files in their human-rational order, I use a simple bit of AppleScript that uses the Finder to sort the names of the items in a given directory. This yields correct results, but it is spectacularly inefficient for reasons I don't understand. The AppleScript is:

on run argv
    set op to ""
    set upath to POSIX file argv as string
    tell application "Finder"
        set foo to every item of folder upath
        set foo to sort foo by name
        repeat with curfile in foo
            set thisname to the name of curfile
            set op to op & " " & thisname as string
        end repeat
    end tell
    return op
end run

(And no, I don't recall why I did it that way. I wrote it circa 2008 and I mostly hate AppleScript...) This is compiled into a script named "flist.scpt" and run from my shell script with "osascript flist.scpt /path/to/series/folder/". The result of running that script with a directory that has 26 files in it is to peg the cpu for over 2 minutes. For comparison, 'ls' gives lexically sorted results on the same machine (a G5 iMac running Leopard... don't laugh, it's a utility machine) in 0.008s with the script chewing on the same directory in the background.

I'm looking to get rid of that AppleScript altogether, but I have not found a way to reproduce the Finder sort logic easily, i.e. by setting a locale or giving some complex arguments to 'sort' that will order the names correctly. My fallback solution if I can't find something canned to do this will be to reproduce the Finder logic myself in some evil mix of sort, sed, awk, and shell, but I'd really like to avoid that if possible. If there's something goofy about my AppleScript that is causing the horrid performance, fixing that would be almost as good as a magic incantation to 'ls' to make it sort like the Finder.

UPDATE: SOLVED! I first tried the Perl Sort::Naturally module, but it sorted labeled-subset names ahead of the implied subset "I" members, which isn't what I wanted. So I went ahead and dove into writing my first Ruby script, after hacking a fix into the Leopard Ruby config to make 'gem' work in the modern world and installing the naturalsort gem. The replacement Ruby script (called with a directory name as an argument) is:

#! /usr/bin/env ruby -rubygems -KU
# Largely cargo-culted from stackexchange response. 
# I dunno what exactly the shebang line opts to ruby do. Probably pwn me. 
# My first Ruby script ever. Don't laugh too hard. 

require 'natural_sort' # gem install naturalsort

dname = ARGV[0]
input = Dir.entries(dname)
puts NaturalSort.naturalsort(input)

The minor differences in output are that this includes the . and .. entries and delimits entries by line rather than with spaces, but as this is being called from a shell script that already filters out some special names these are trivial to handle.

3 Answers 3

2

The exact sorting order used by Finder probably depends on the locale, but scripting languages like Ruby and Python have libraries for sorting strings naturally.

#!/usr/bin/env ruby -rubygems -KU

require 'natural_sort' # gem install naturalsort

input = "name-II-1
name-6
name-2
name-19".lines
# input = Dir["#{ENV['HOME']}/Documents/*"]

puts NaturalSort.naturalsort(input)
2
  • This is promising. Ruby & Python are less annoying than AppleScript but also less familiar, but there's a p5-sort-naturally in MacPorts that may be more fit to my tastes
    – Bill Cole
    Commented Oct 9, 2012 at 13:09
  • Sort::Naturally wasn't quite right, but the Ruby naturalsort gem did the trick, after some work to make Ruby functional on Leopard.
    – Bill Cole
    Commented Oct 9, 2012 at 15:13
0

Building a string using

set a to a & b

is not as fast as building a list and coercing it to text at the end.

Try:

on run argv
set op to {}
tell application "Finder" to set foo to sort every item of folder argv by name

repeat with curfile in my foo
tell application "System Events" to set end of op to name of curfile & space
end repeat
return op as text
end run
1
  • Unfortunately, building the string isn't the bottleneck. Running the clumsy script as written only takes ~1% more time than just returning "foo" (the list of files,) letting osascript figure out how to represent it as text.
    – Bill Cole
    Commented Oct 9, 2012 at 13:02
0

Finder Suite of AppleScript got verb "sort by" name/index/date/etc.

  • Note: This is NOT for sorting items in Finder windows but indeed for sorting "items of folder" lists in AppleScript with the same sorting algorithm as the Finder does.
  • Tested myself successfully on macOS 11 Big Sur.
  • Got introduced with MacOS X Tiger AFAIK.
on run
    set choosenFolder to choose folder with prompt "Choose folder whose content will be be batch renamed."
    tell application "Finder" to set choosenItems to sort (items of folder choosenFolder) by name
    my whatever(choosenItems)
end run

on whatever(myItems)
    repeat with i from 1 to count of myItems
        (*
            Do something with myItems, which are now sorted by name exactly as Finder:
        
            • No leading zeros required, sorts like in Finder: 000, 1, 1 copy, 2, 03, 10, 100
        
            • With file extensions visible you get a slightly different order:
              e.g. "1 copy.jpg" and "1.jpg" have switched place as "1 " comes before "1."
        *)
    end repeat
end whatever

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .