Change Sort in a list alphabetically

Hi there,

I have a List, looks like this:

 set the_list to {"Compressed file.7z", "Compressed file 3.7z", "Compressed file 2.7z", "Compressed file 5.7z", "Compressed file 4.7z"}

Now, I’d like to change the sort like this:

 set the_list to {"Compressed file.7z", "Compressed file 2.7z", "Compressed file 3.7z", "Compressed file 4.7z", "Compressed file 5.7z"}

I’ve tried the following:



set old_delims to AppleScript's text item delimiters
set AppleScript's text item delimiters to {ASCII character 10} -- always a linefeed
set list_string to (the_list as string)
set new_string to do shell script "echo " & quoted form of list_string & " | sort -u"
set new_list to (paragraphs of new_string)
set AppleScript's text item delimiters to old_delims
return new_list

But that gives me that:

 {"Compressed file 2.7z", "Compressed file 3.7z", "Compressed file 4.7z", "Compressed file 5.7z", "Compressed file.7z"}

So the last item should be the first. Can somebody help me?

Thank you very much!

Kind regards
Marth

Your naming scheme is not one that is widely used.

The sort routines we use doesn’t handle that well at all. The easiest thing to do would be to rename the .7 file to 0.7, and then maybe renaming it back to .7 afterwards. Though 0.7 seems like a lot more logical name to me.

Or naming the files .7, .7.1 and .7.2 and so on. :slight_smile:

I think the easiest way is to ignore the extensions

set the_list to {"Compressed file.7z", "Compressed file 3.7z", "Compressed file 2.7z", "Compressed file 5.7z", "Compressed file 4.7z"}

set {old_delims, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
set sorted_list to paragraphs of (do shell script "sort -t . -k 1,1 <<< " & quoted form of (the_list as string))
set AppleScript's text item delimiters to old_delims
return sorted_list

Now it actually sorted by file names without the extension ‘7z’.

Nice! :slight_smile:

I didn’t see that one, that I could exclude the extension!

Wow cool! Thank you very much, works great!!!

[strikethrough]A bump because I’ve made a mistake. The field should be 1.1 and not 1,1. I’ve corrected the code above and you’ll see the difference when you sort files from the preference folder. Now it’s working properly.[/strikethrough]

Text above incorrect (missing a strike through on MS)

NOTE: As mentioned in the manual, the sort order depends on the locale settings, which will be LC_ALL=C when using do shell scripts.

You can either set it where it should be set according to OS Version (.MacOsX/Environment.plist for 10.6 and backwards to 10.4 I think, something with launchd later), or specify LC_ALL=en_US or whatever before the command that needs it in the do shell script statement.

Or put it in the ~/.profile where it should be, for persistence.

Edit

I wonder if you mean that the .profile is executed during login?

I have understood that the profile isn’t read when a do shell script command is executed by AppleScript, nor during the regular login of a user onto MacOsX. I might be wrong.

But if enviornment variables are set with one one of those two ways of setting properties, that I mentioned above, (the first method is described in this deprecated document: Technical Q&A QA1067: Technical Q&A QA1067 ) The those variables are set during login, and the variables set are available to all Mac Os X apps.

:slight_smile: As a side-note: I have found that I have to prepen awk with LC_ALL=C in order to execute awk correctly, when I have set LC_ALL=no_NB elsewhere. (The comma separator)

The ~/.profile is read every time a login shell is opened. Do shell script doesn’t use a login shell, as you described, but uses a (sub) shell that is inherited from the system. That means somewhere the ~/.profile has been read; the user has logged in somewhere. To apply the changes in the ~/.profile file you need to re-login.

I think you were right the first time. The 1.1 version gives the exact order Marth didn’t want, whereas 1,1 produces what’s required. 1,1 treats just the first field as the sort key; 1.1 sorts on everything from the first character of the first field to the end of the line.

Maybe the dot and the comma are interpreted with respect to the LOCALE settings?

We are closing up on Chrismas guys, a Merry Christmas to both of you, If I don’t hear from you! :slight_smile:

Thanks Nigel!

I thought that when using 1.1 it was still sorting per column but it seems that sort doesn’t work that way. Because the 1,1 gives problems with files in the preference folder, seems that the fog in my eyes from a hangover haven’t checked this thoroughly enough. What I want is that files containing multiple periods sort still works properly. It seems that we have to make a workaround like this


set theFiles to {"com.apple.QuickTime.plist", "com.apple.iTunes.plist", "org.x.X11.plist", "org.mozilla.firefox.plist", "com.apple.iCal.plist", "com.apple.AddressBook.plist", "QuickTime Preferences", "com.TeamViewer8.Settings.plist", "com.quark.quarkupdate.preferences.plist", "Compressed file.7z", "Compressed file 3.7z", "Compressed file 2.7z", "Compressed file 5.7z", "Compressed file 4.7z"}

set {oldTD, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
set T to every paragraph of (do shell script "FILES=" & quoted form of (theFiles as string) & "
IFS=" & quoted form of linefeed & "
for file in $FILES
do
echo ${file%.*}/$file
done") --this code can be on one line, but better readable this way
set sortedList to every paragraph of (do shell script "sort -f -t / -k 1,1 <<< " & quoted form of (T as string) & " | cut -d/ -f2-")
set AppleScript's text item delimiters to oldTD

return sortedList

edit: here’s the one liner

every paragraph of (do shell script "FILES=" & quoted form of (theFiles as string) & "; IFS=" & quoted form of linefeed & "; (for file in $FILES  ;do echo ${file%.*}/$file; done) | sort -f -t / -k 1,1 | cut -d/ -f2-")

p.s. Changed the code back to 1,1

Sooner or later one would have to do such stunts, and then it is good to know the work arounds! :slight_smile:

@ DJ Bazzie Wazzie
I am going to put your profile thing to a test, but not before I am going to boot anyway., to see if the login window executes a login with the shell, using .profile, or if launchd does the same. I think I tested this some years ago, and settled for the Environment.plist, but I’m going to test it anyway.

The only case in which this will work, if is the login window or launchd executes a login shell.

Just for the the interest, the same result can be obtained with vanilla AS (three to six times as quickly for this particular list) like this:

set the_list to {"com.apple.QuickTime.plist", "com.apple.iTunes.plist", "org.x.X11.plist", "org.mozilla.firefox.plist", "com.apple.iCal.plist", "com.apple.AddressBook.plist", "QuickTime Preferences", "com.TeamViewer8.Settings.plist", "com.quark.quarkupdate.preferences.plist", "Compressed file.7z", "Compressed file 3.7z", "Compressed file 2.7z", "Compressed file 5.7z", "Compressed file 4.7z"}

set srt to (load script file ((path to scripts folder as text) & "Libraries:Sorts:Custom Insertion Sort.scpt"))

script ignoringExtensions
	property delim : "."
	
	on isGreater(a, b)
		set astid to AppleScript's text item delimiters
		set AppleScript's text item delimiters to delim
		if (((count a each text item) > 1) and ((count b each text item) > 1)) then
			set iG to (text 1 thru text item -2 of a > text 1 thru text item -2 of b)
		else
			set iG to (a > b)
		end if
		set AppleScript's text item delimiters to astid
		return iG
	end isGreater
end script

tell srt to sort(the_list, 1, -1, {comparer:ignoringExtensions})
the_list

The insertion sort loaded in the ‘set srt .’ line is this:

(* Insertion sort ” customisable version.
Algorithm: unknown author.
AppleScript implementation: Arthur J. Knapp and Nigel Garvey, 2003.
Modified by Nigel Garvey 2010.

Parameters: (list, range index 1, range index 2, record with optional 'comparer' and 'slave' properties). The 'comparer' value is a script object containing an isGreater(a, b) handler which determines if object a is "greater" than object b. The 'slave' value is a script object containing a shift(a,b) handler which is called when  item b of the list being sorted has been moved to position a, the intervening items being shifted up one position in the process.
Where the 'comparer' or 'slave' properties are omitted, or the customisation parameter isn't a record, the sort has default handlers which respectively compare items directly and do nothing.
*)

on CustomInsertionSort(theList, l, r, customiser)
	script o
		property comparer : me
		property slave : me
		property lst : theList
		
		on isrt(l, r)
			set u to item l of o's lst -- The highest value sorted so far!
			repeat with j from (l + 1) to r
				-- Get the next unsorted value.
				set v to item j of o's lst
				if (comparer's isGreater(u, v)) then
					-- If it's less than highest sorted value, initialise the insertion location index to the beginning of the range.
					set here to l
					-- Work back through the sorted items, shifting up those with greater values than the unsorted one, until a lesser or equal value or the beginning of the range is reached.
					set item j of o's lst to u
					repeat with i from (j - 2) to l by -1
						tell item i of o's lst
							if (comparer's isGreater(it, v)) then
								-- Greater value. Move it up one position.
								set item (i + 1) of o's lst to it
							else
								-- Lesser or equal value. Set the vacated slot after it as the insertion location.
								set here to i + 1
								exit repeat
							end if
						end tell
					end repeat
					-- Insert the value for insertion at the appropriate location.
					set item here of o's lst to v
					slave's shift(here, j)
				else
					-- The value's greater than or equal to the highest sorted one. It's now the highest itself.
					set u to v
				end if
			end repeat
		end isrt
		
		-- Default comparison and slave handlers for an ordinary, qSort-style sort.
		on isGreater(a, b)
			(a > b)
		end isGreater
		
		on shift(a, b)
		end shift
	end script
	
	-- Process the input parmeters.
	set listLen to (count theList)
	if (listLen > 1) then
		-- Negative and/or transposed range indices.
		if (l < 0) then set l to listLen + l + 1
		if (r < 0) then set r to listLen + r + 1
		if (l > r) then set {l, r} to {r, l}
		
		-- Supplied or default customisation scripts.
		if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
		
		-- Do the sort.
		o's isrt(l, r)
	end if
	
	return -- nothing.
end CustomInsertionSort

property sort : CustomInsertionSort

Note: While testing the script, I came across a bug in Snow Leopard whereby negatively indexing more text items than there actually are returns the first text item in the text!

set AppleScript's text item delimiters to "."
text item -2 of "QuickTime Preferences"
-->"QuickTime Preferences"

set AppleScript's text item delimiters to "e"
text item -12357 of "QuickTime Preferences"
--> "QuickTim"

The isGreater() handler I originally posted relied on this bug not to error with “QuickTime Preferences”, but I’ve now modified it to make an alternative comparison if either or both strings don’t have enough text items.

I would have called that a feature if it did it consistently, but alas, it doesn’t, because it errs some of the time. And it should really have returned all of the text, not just the first text item.

I don’t see the fun in doing this, what you do, and I’d keep the compressed file out of the preference folder. And if I was to sort the contents of the preference folder, then I’d just remove the extension, and then sort it by the first field.
We are mixing two different nameing schemes here, kind of like mingling birds with fish.


set theFiles to {"com.apple.QuickTime.plist", "com.apple.iTunes.plist", "org.x.X11.plist", "org.mozilla.firefox.plist", "com.apple.iCal.plist", "com.apple.AddressBook.plist", "QuickTime Preferences", "com.TeamViewer8.Settings.plist", "com.quark.quarkupdate.preferences.plist", "Compressed file.7z", "Compressed file 3.7z", "Compressed file 2.7z", "Compressed file 5.7z", "Compressed file 4.7z"}
set {oldTD, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
set theFiles to theFiles as text
set AppleScript's text item delimiters to oldTD

set theRes to (do shell script "( sed -e '/[\\.]/d'  <<<" & quoted form of theFiles & " ; sed -ne 's/\\(^.*[^\\.]\\)\\([.].*$\\)/\\1/p' <<<" & quoted form of theFiles & ") |sort -fd -k1,1")

Here the compressed files turns up in the middle of the list in the com - section of the preference files. Quicktime preferences ends up as the last item. The extension is also gone, but that may easily be fixed by a grep through the list, rebuilding it, it is costly, but should work in all cases.

That is interesting to know. But it really doesn’t matter that much if when we are talking about it milliseconds. Once! :slight_smile:

Edit

I wonder about the reason for deviating from the naming scheme for Quick Time Preferences.

Nothing surprises me anymore, having read about C++ code in the Finder in Bjarne Stroustrups blog. (Hopefully that C++ Code is pre Mac Os X, secondly, that Bjarne Stroustrup has gotten it all wrong. The one single Reason Mac Os X is what it is today, is Objective-C) If they start deviating, we’ll loose scriptability.

I’ve just amended my post and changed the isGreater() handler so that it doesn’t rely on this bug/feature.

Obviously DJ wasn’t talking about putting files in the Preferences folder, but trying to make his solution more robust and using names from the Preferences folder as examples for testing. :slight_smile:

That was not such a bad idea, after having had a look into my preferences folder, I actually found files that doesn’t keep up with the naming convention.

I am not all grumpy! :smiley:

Sometimes, I think adaptability is a good synonym for fit. (Survival of the fittest, adapt or disappear! )

When you can, you choose a name convention, that works well for you with the existing tools, the system pose problems for you (general you) anyway. If things gets hard to do with some naming scheme, then maybe it would be smart to revise it.

Now, the OP’s naming scheme was a catch 22 in this particular case, and nothing the OP really couldn’t control, so for those (rare cases) we certainly need some work arounds. :slight_smile:

Hello all.
The man of sort state:

   -k, --key=POS1[,POS2]
          start a key at POS1, end it at POS2 (origin 1)

   POS is F[.C][OPTS], where F is the field number  and  C  the  character
   position  in  the  field.   OPTS  is one or more single-letter ordering
   options, which override global ordering options for that  key.   If  no
   key is given, use the entire line as the key.

So, I assumed that :


set the_list to {"Compressed file.7z", "Cbmpressed file 3.7z", "Campressed file 2.7z", "Compressed file 5.7z", "Codpressed file 4.7z"}

set {old_delims, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
set sorted_list to paragraphs of (do shell script "sort -t . -k 1.1,1.2 <<< " & quoted form of (the_list as string))
set AppleScript's text item delimiters to old_delims
return sorted_list

would sort using the two first characters of the items returning:

{“Campressed file 2.7z”, “Cbmpressed file 3.7z”, “Compressed file.7z”, “Compressed file 5.7z”, “Codpressed file 4.7z”}

but it returns :

{“Campressed file 2.7z”, “Cbmpressed file 3.7z”, “Codpressed file 4.7z”, “Compressed file 5.7z”, “Compressed file.7z”}

which is what we get without the « extended parameters ».

What’s wrong, the way I understood ther man or what is written in the man ?

Yvan KOENIG (VALLAURIS, France) mercredi 19 décembre 2012 16:08:20

I haven’t tested it thoroughly, but the scheme seems to be to sort on the main “key” (if one is defined) and then to subsort on whatever’s left.