Create diskimage without files, Only folderstructure

Hello.

I knew Nigel Garvey had made a very optimized algorithm for this job, I adapted this particular task to that algorithm.

I have tested it, and it works, but I haven’t timed it.
From the timings I read in the post, this should work considerably faster no matter the number of folders, but it should excel proportionally with the number of folders..

Here it is, thanks to Nigel Garvey from the thread Suggestions For Duplicating A Folder Structure


” © Nigel Garvey 2007 
considering case
	main()
end considering

on main()
” T I N K E R E D  B Y  McUsr to fit with the job description, which is to replicate a folder structure onto a new
” disk image. original post: macscripter.net/viewtopic.php?pid=75224#p75224
	set fromDisk to false
	
	if fromDisk is true then
		set sourceFolder to (choose from list (list disks))
		if sourceFolder is false then return
		set rootPath to POSIX path of alias (item 1 of sourceFolder)
	else
		set rootPath to POSIX path of (choose folder)
	end if
	-- Get the POSIX paths of the hierarchy's subfolders. (The double slashes in the shell script result will come out in the wash.)
	set subfolderPaths to rest of paragraphs of (do shell script "find " & quoted form of rootPath & " -name \"*\" -type d" with administrator privileges)
	if (subfolderPaths is {}) then set subfolderPaths to {""} -- No subfolders in the hierarchy.
	
	-- Construct a POSIX path (without a trailing slash) for the root folder of the duplicate hierarchy,
	-- ensuring that the folder name's unique at the destination.
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to "/" as Unicode text
	set rootName to text item -2 of rootPath
	set newRootName to rootName
	set destinationFolder to (path to desktop as Unicode text)
	set namesAtDestination to (list folder destinationFolder)
	
	set n to 0
	repeat while (newRootName is in namesAtDestination)
		set n to n + 1
		set newRootName to rootName & " " & n
	end repeat
	
	
	set newRootPath to (POSIX path of destinationFolder) & newRootName
	set dmgPath to quoted form of (newRootPath & ".dmg")
	set AppleScript's text item delimiters to astid
	
	-- Construct and use a single mkdir command for the entire duplicate hierarchy.
	do shell script getMkdirString(subfolderPaths, newRootPath)
	
	
	do shell script "/usr/bin/hdiutil create -srcfolder " & quoted form of newRootPath & " -megabytes 100 " & dmgPath
	do shell script "/bin/rm -rf " & quoted form of newRootPath
	
end main

on getMkdirString(subfolderPaths, newRootPath)
	script o
		property paths : subfolderPaths
		property outputComponents : {"mkdir -p " & quoted form of newRootPath}
		-- Some pre-defined stuff for speed.
		property slash : "/" as Unicode text
		property slashBrace : "/{" as Unicode text
		property comma : "," as Unicode text
		property braceComma : "}," as Unicode text
		
		-- A recursive subhandler that analyses how the paths relate to each other
		-- and decides how to structure the 'mkdir' string.
		on parsePaths(a, b)
			-- At each recursion, items a thru b of the list represent sibling subfolders and their descendents.
			-- Each sibling is sorted immediately before its own descendents, so item a must be an immediate
			-- child of the previous level. The other items are either its decendents, siblings, or siblings' descendents.
			set currentSibling to item a of my paths
			if (a = b) then
				-- Item a is a childless subfolder with no siblings. Append "/'<name>'," to the output.
				set end of my outputComponents to slash & quoted form of text item -1 of currentSibling & comma
			else
				if (item b of my paths begins with currentSibling) then
					-- Item a has descendents but no siblings. Append "/'<name>'" to the output.
					set end of my outputComponents to slash & quoted form of text item -1 of currentSibling
					set closingBraceRequired to false
				else
					-- Item a has siblings. It may or may not have descendents. Append "/{'<name>'" to the output.
					set end of my outputComponents to slashBrace & quoted form of text item -1 of currentSibling
					set closingBraceRequired to true
				end if
				-- Check the other paths in this recursion.
				set i to a + 1
				repeat with j from i to b
					if (item j of my paths begins with currentSibling) then
						-- This is a descendent of the current sibling. Skip it for now.
					else
						-- This is a new sibling.
						if (j > i) then
							-- If the previous sibling had descendents, recurse to sort them out.
							parsePaths(i, j - 1)
						else
							-- Otherwise, just append a comma to the output.
							set end of my outputComponents to comma
						end if
						-- Append the new sibling's name to the output and continue the repeat.
						set currentSibling to item j of my paths
						set end of my outputComponents to quoted form of text item -1 of currentSibling
						set i to j + 1
					end if
				end repeat
				-- At the end of the repeat, sort out any descendents of the last sibling.
				if (i > b) then
				else
					parsePaths(i, b)
				end if
				-- Append a closing brace and a comma to the output if there were siblings at this level.
				if (closingBraceRequired) then set end of my outputComponents to braceComma
			end if
		end parsePaths
	end script
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to "/"
	
	tell o to parsePaths(1, (count subfolderPaths))
	
	-- Coerce the resulting list to Unicode text and tidy up the result.
	set AppleScript's text item delimiters to ""
	set mkdirString to o's outputComponents as Unicode text
	set mkdirString to text 1 thru -2 of mkdirString
	set AppleScript's text item delimiters to ",}"
	set mkdirString to mkdirString's text items
	set AppleScript's text item delimiters to "}"
	set mkdirString to mkdirString as Unicode text
	
	set AppleScript's text item delimiters to astid
	
	return mkdirString
end getMkdirString


Thanks guys for your help and effort!

I use Stefan his last one. It’s quick and works on almost any drive I tested.

Thanks again!

KML:)

Hello.

As long as the find command can live with the permissions settings in the last routine I posted, it should work very fine. Unfortunately, the permissions in a directory structure is not necessarily according to the find commands likings.
I did notice that myself.

I’ll investigate this matter during the week, and post a fix for that, just because this is an utterly cool tool to have when replicating folder structures of disks as large as you mentioned. The fix may or may not take noticeable more time. Even if such a pre run would quadruple the overall time used by that routine, it will still execute in just the fraction of a time.

A quick solution that might work, is to start up AppleScript with adminstrator rights by starting the editor through pseudo.

For the record:
I’m not insisting on anybody using this or saying that they should; I have just said that I will fix it to be somewhat more robust with regards to the permissions of the folders.

almost?

Whoops. Sorry about that. Don’t want to be rude and script works very nice.

I logged in remotely to someone else’s Xserve and started the script. I see the script running. This disk is huge with a lot of folders and subfolders and after waiting a while I checked the force quit menu and saw that I had to relaunch finder. But this happened only once and could be that I had to wait longer or something like that.

WIll try again later. I need to change also one line because I just noticed that I don’t have write rights and I want that.

But you are very helpfull as always!

KML:)

Don’t use the script on a volume with a hugh amount of folders like a startup volume. The script will timeout after 2 minutes and working with AppleScript alias specifiers on shared volumes is not reliable

Stefan,

Thank again for your post here.

What do yo mean with that it is not reliable ?

KML

In my experience sometimes alias file specifiers are not recognized on shared volumes.
I’m always using Finder file specifiers or, even better, the shell

Hello.

I suspected to have rights issues with the script I nicked from Nigel Garvey, and that was right.

I added with administrative privileges to the line .


 set subfolderPaths to rest of paragraphs of (do shell script "find " & quoted form of rootPath & " -name \"*\" -type d")

Like this:


 set subfolderPaths to rest of paragraphs of (do shell script "find " & quoted form of rootPath & " -name \"*\" -type d" with administrator privileges )

The next thing that happens is that Script Debugger runs out of StackSpace, then I start to check out what I’m actually testing it on. Well it turned out that there was 27248 sub directories!

The directory string created was 2,645,596 bytes ! when saved to disk. Even Text Edit starts choking when getting this amount of text.

I expect the script to run flawlessly under Smile under these conditions and will report back later on.

:D:D:D:D

Edit: Technical Note TN2065 do shell script states that the maximum size of the string given to the do shell script command can only be:

-So I will have to confine my self with a directory structure within that limit. Which should be approximate 1/tenth of what I used for the previous test.