An open community 
of Macintosh users,
for Macintosh users.

FineTunedMac Dashboard widget now available! Download Here

Previous Thread
Next Thread
Print Thread
diskutil challenge!
#15835 06/01/11 09:55 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
Gold star for anyone that can figure out how to use the diskutil command to partition a hard drive from within a script, with partitions whose names contain spaces. No, it's not as easy as you think.

diskutil partitiondisk /dev/disk4 2 APM JHFS+ abc 2G JHFS+ 'def ghi' R

works when typed in directly on the command line. I have been unsuccessful at setting a shell variable by any reasonable or unreasonable means to get it to work within a script. diskutil appears to have a (borken) check to verify you provided the correct number of parameters, that does not adhere to any method of escape I can find, and so bails out crying about incorrect parameter count.

I would like this because my service drives require 12 partitions, and having to go into disk utility to do it manually when I have to repartition a bunch of them at once is a PAIN. At the moment the best solution I have come up with is to have the script build the command and display it to terminal and say "ok now cut and paste this...." but I don't call that a proper solution.

Right now I have to paste this, which is messy to edit:

diskutil partitionDisk /dev/disk4 12 APM JHFS+ "Service Lion" 17G JHFS+ "Service Snow" 15G JHFS+ "Service Leopard" 23G JHFS+ "Service Tiger" 12G JHFS+ "Mac OS 10.3.4" 1G JHFS+ "Mac OS 10.3.4 Disc 2" 1G JHFS+ "Mac OS 10.4.6" 4G JHFS+ "Mac OS 10.4.7" 6G JHFS+ "Mac OS 10.5.6" 8G JHFS+ "Mac OS 10.6.3" 9G JHFS+ "Mac OS 10.7.0" 9G JHFS+ Diags R


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #15842 06/02/11 03:21 AM
Joined: Sep 2009
Offline

Joined: Sep 2009
Don't have a spare disk with which to experiment.

But i'd say this: insisting on having spaces included in the names of our various volumes is simply not worth the potential risk. (if necessary, use underscores or periods or dashes instead... else, just a single word using ordinary alphanums). Remember the "iTunes 2.0 fiasco"? (use teh google). It was Apple's fault of course, but —by putting spaces in a volume's name (especially on a multi-partition disk) —you are putting those volumes at the mercy of whatever program [or script] out there you might run one day which might replicate Apple's error.

Not.worth.it

[edit: having said that, did you try double\\ backslashes\\ yet? (sans quotes)]

Last edited by Hal Itosis; 06/02/11 03:32 AM.
Re: diskutil challenge!
Hal Itosis #15863 06/03/11 03:33 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
Oh I tried escaping, double escaping, alternate ways to express spaces (\x20 etc) and a whole bunch of other things $(echo ...) before finally giving up on it.


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #15866 06/03/11 04:29 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
Oh I tried escaping, double escaping, alternate ways to express spaces (\x20 etc) and a whole bunch of other things $(echo ...) before finally giving up on it.

Well, i located an expendable flash drive... but i don't encounter any such problem:

Code:
$ cat dsr

diskutil partitionDisk /dev/disk1 2 APM JHFS+ abc 480M JHFS+ 'def ghi' R


$ bash -x dsr
+2: diskutil partitionDisk /dev/disk1 2 APM JHFS+ abc 480M JHFS+ 'def ghi' R
Started partitioning on disk1
Unmounting disk
Creating partition map
Waiting for disks to reappear
Formatting disk1s2 as Mac OS Extended (Journaled) with name abc
Formatting disk1s4 as Mac OS Extended (Journaled) with name def ghi
Finished partitioning on disk1
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     Apple_partition_scheme                        *2.0 GB     disk1
   1:        Apple_partition_map                         32.3 KB    disk1s1
   2:                  Apple_HFS abc                     480.0 MB   disk1s2
   3:                  Apple_HFS def ghi                 1.4 GB     disk1s4

$ echo $?
0


Edit and try again...
Code:
$ date; sw_vers
Fri Jun  3 13:17:09 EDT 2011
ProductName:	Mac OS X
ProductVersion:	10.6.7
BuildVersion:	10J869


$ cat dsr
alpha=Service\ Lion
bravo=Service\ Snow
charlie=Service\ Leopard

diskutil partitionDisk /dev/disk1 3 APM JHFS+ "$alpha" 480M \
JHFS+ "$bravo" 480M JHFS+ "$charlie" R


$ bash -x dsr
+1: alpha='Service Lion'
+2: bravo='Service Snow'
+3: charlie='Service Leopard'
+5: diskutil partitionDisk /dev/disk1 3 APM JHFS+ 'Service Lion' 480M \
JHFS+ 'Service Snow' 480M JHFS+ 'Service Leopard' R
Started partitioning on disk1
Unmounting disk
Creating partition map
Waiting for disks to reappear
Formatting disk1s2 as Mac OS Extended (Journaled) with name Service Lion
Formatting disk1s3 as Mac OS Extended (Journaled) with name Service Snow
Formatting disk1s4 as Mac OS Extended (Journaled) with name Service Leopard
Finished partitioning on disk1
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     Apple_partition_scheme                        *2.0 GB     disk1
   1:        Apple_partition_map                         32.3 KB    disk1s1
   2:                  Apple_HFS Service Lion            480.0 MB   disk1s2
   3:                  Apple_HFS Service Snow            480.0 MB   disk1s3
   4:                  Apple_HFS Service Leopard         1.0 GB     disk1s4


$ df -h |sed '1p;/disk1/!d'
Filesystem      Size   Used  Avail Capacity  Mounted on
/dev/disk1s2   458Mi   19Mi  439Mi     5%    /Volumes/Service Lion
/dev/disk1s3   458Mi   19Mi  439Mi     5%    /Volumes/Service Snow
/dev/disk1s4   1000i   32Mi  968Mi     4%    /Volumes/Service Leopard
 


Works fine with no special quoting or escaping tricks. All those volumes look right in Finder too.

::shrug::

With which OS are you running your failing script?

Re: diskutil challenge!
Hal Itosis #15867 06/03/11 04:47 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
PS: this may explain how line numbers appear in my traces...
Code:
$ grep PS4 ~/.bashrc 
declare -x PS4=$'\[\e[37;41m\]+${LINENO}:\[\e[0m\] '

Re: diskutil challenge!
Hal Itosis #15888 06/06/11 04:05 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
[blockquote]diskutil partitionDisk /dev/disk1 2 APM JHFS+ abc 480M JHFS+ 'def ghi' R[/blockquote]

The trick is to use variables to make the process flexible. Put a little of this in the script:

d2="def ghi"

now try to use $d2 in that command, to specify the name of the second volume. My script builds up the command because it's dealing with a dozen partitions, so it looks something like:

diskutil partitionDisk /dev/disk${devnum} APM ${partlist}

Which only works if the partition names do not contain spaces. Quoting ${partlist} doesn't help. Escaping things in partlist didn't work either, but I may not have been trying what was necessary.


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #15889 06/06/11 04:49 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
The trick is to use variables to make the process flexible.

Trick? smirk You mean like i already did?
See $alpha $bravo $charlie above.

After 3 days, that's your reply? I'm worried about you man.
(anyway, reread my post i guess... and answer the question i asked... etc.)

EDIT:

Wish you had posted your exact script there (instead of "something like"), but... one possibility is that you simply need (soft) quotes around your variables.


Originally Posted By: Virtual1
Quoting ${partlist} doesn't help

${partlist} ??? What's that??? Show us precisely where/how you defined/initialized these items. You're being way too abstract. If you don't post your exact full code... how can we possibly pinpoint the error?

Last edited by Hal Itosis; 06/06/11 04:59 PM.
Re: diskutil challenge!
Virtual1 #15901 06/07/11 04:44 AM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
diskutil partitionDisk /dev/disk${devnum} APM ${partlist}

Which only works if the partition names do not contain spaces. Quoting ${partlist} doesn't help. Escaping things in partlist didn't work either, but I may not have been trying what was necessary.

Okay, i think i can deduce what you're trying to do with that list variable (sans reply). You want to stuff several args into it and have diskutil treat that single variable as an arg list. And you're right about it being impossible to get it working (when the volume names contain spaces)... there seems to be no way. Happy?

But still unclear is: where's the big advantage in using that single "list" arg? How/why is that particular approach so necessary, or aspect so beneficial?

Wouldn't a script "something like" this serve just as well?
Code:
dNum=4
pNum=12

p01="Service Lion"
p02="Service Snow"
p03="Service Leopard"
p04="Service Tiger"
p05="Mac OS 10.3.4"
p06="Mac OS 10.3.4 Disc 2"
p07="Mac OS 10.4.6"
p08="Mac OS 10.4.7"
p09="Mac OS 10.5.6"
p10="Mac OS 10.6.3"
p11="Mac OS 10.7.0"
p12=Diags

diskutil partitionDisk /dev/disk$dNum $pNum APM \
JHFS+ "$p01" 17G  JHFS+ "$p02" 15G \
JHFS+ "$p03" 23G  JHFS+ "$p04" 12G \
JHFS+ "$p05"  1G  JHFS+ "$p06"  1G \
JHFS+ "$p07"  4G  JHFS+ "$p08"  6G \
JHFS+ "$p09"  8G  JHFS+ "$p10"  9G \
JHFS+ "$p11"  9G  JHFS+ "$p12"   R


...and then just edit those variables if/when conditions change.
(the $dNum variable could be a parameter passed at runtime)

IOW, even if there was some magic syntax to adorn the script and perform the task (and i can only imagine it would be very messy looking syntax)... you'd still end up having to edit the script somehow/somewhere as the need arises.

N'est-ce pas?

Else, save several 'specialized' versions of it perhaps.

Last edited by Hal Itosis; 06/07/11 04:58 AM.
Re: diskutil challenge!
Hal Itosis #15908 06/07/11 05:20 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
Code:
$ cat dsr
alpha=Service\ Lion
bravo=Service\ Snow
charlie=Service\ Leopard

diskutil partitionDisk /dev/disk1 3 APM JHFS+ "$alpha" 480M JHFS+ "$bravo" 480M JHFS+ "$charlie" R


partlist is the list of partitions. you're doing them explicitly one at a time, using multiple variables.

now assign all of this into a single variable called partlist:
3 APM JHFS+ "$alpha" 480M JHFS+ "$bravo" 480M JHFS+ "$charlie" R

and try to run it this way:

diskutil partitionDisk /dev/disk1 ${partlist}

The issue is, I am BUILDING the partition list, and then partitioning. The script assembles it. The number of partitions, their size, and their names, are all variable. This requires using ONE variable, not three or ten. Put all three partition descriptions into one variable if you can. Normally I'd include the "diskutil partitionDisk /dev/disk1" in the one variable too, and just run it all with a single command such as: $partme

After 3 days, that's your reply? I'm worried about you man.
Yep, life's been busy. Yesterday for example my truck got.... new front brakes. new rear brakes. new front rotors. new front struts. new rear shocks. tuneup with plugs and cables. ac recharge. lockouts changed from auto pneumatic to manual.

Here's one attempt:

apple:~ v1 $ partme="diskutil partitionDisk /dev/disk2 3 APM JHFS+ \"Service Tiger\" 480M JHFS+ \"Service Leopard\" 480M JHFS+ \"Service Snow\" R"
apple:~ v1 $ echo $partme
diskutil partitionDisk /dev/disk2 3 APM JHFS+ "Service Tiger" 480M JHFS+ "Service Leopard" 480M JHFS+ "Service Snow" R
apple:~ v1 $ $partme
There appear to be too many arguments for the number of partitions you specified


Note that if you replace the spaces with underscores, you get partitions with quotes around them for their names, so that wasn't going to work anyway. But it shows just how abnormally diskutil manages command line parameters.


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #15912 06/07/11 06:32 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
partlist is the list of partitions. you're doing them explicitly one at a time, using multiple variables.

now assign all of this into a single variable called partlist:
3 APM JHFS+ "$alpha" 480M JHFS+ "$bravo" 480M JHFS+ "$charlie" R

and try to run it this way:

diskutil partitionDisk /dev/disk1 ${partlist}

Yessir, i eventually managed to divine that 'single variable' intent. (you shoulda/coulda specified those details in post #1 though)



Originally Posted By: Virtual1
The issue is, I am BUILDING the partition list, and then partitioning. The script assembles it. The number of partitions, their size, and their names, are all variable.

Great, show us the "build code" for that script. (or, preferably, the whole thing).

If you stick with that 'Swiss Army Knife' approach, you could write a shell script which prompts the user to enter the data. [ask for the number of partitions first, and use that value to set the repeat loop that collects the name/size info.] You can employ either Bash's select function (where you offer some options and the user selects one), or even an AppleScript dialog with a text box can be used (via osascript) to get text from the user. Alternatively, write the whole thing in AppleScript... and —once the variable data is all gathered —simply:

do shell script ("diskutil partitionDisk " & a & b & c & e & f & etc. )

Either way —including your own current copy/paste technique —it's a tedious process (due to having to support so many variables [as well as a varying number of variables] in such a "universal" script).



Originally Posted By: Virtual1
This requires using ONE variable, not three or ten.

I'm not convinced of that. It's all a matter of approach. I'm positive that a shell script could be designed to put those pieces "together" for diskutil without having to jam them into a single variable.

But as i also suggested: if you have (for example) three service disks, then it's probably smarter to just custom-tailor three separate saved scripts for those typical cases... instead of trying to create some monster script to handle every possible case [which is pretty much like replicating all the work that simply using Disk Utility's GUI already entails in the first place.]



Originally Posted By: Virtual1
Here's one attempt:

apple:~ v1 $ partme="diskutil partitionDisk /dev/disk2 3 APM JHFS+ \"Service Tiger\" 480M JHFS+ \"Service Leopard\" 480M JHFS+ \"Service Snow\" R"
apple:~ v1 $ echo $partme
diskutil partitionDisk /dev/disk2 3 APM JHFS+ "Service Tiger" 480M JHFS+ "Service Leopard" 480M JHFS+ "Service Snow" R
apple:~ v1 $ $partme
There appear to be too many arguments for the number of partitions you specified

Understood... but that's not a “script”. That's just copy/pasting stuff onto the command line.

Why not write and save a real shell script?... you know, to a file... with an actual name... which one could later run as a command:

dsv 4

(where 4 would be a runtime arg denoting the disk number. E.g., /dev/disk4)


The same error will still occur in saved script versions, if you try to employ that 'single variable' business. But there's no need to attempt such tricks anyway. Just assemble the parts as i did in my first post.

I still say you're better off saving several custom scripts, where the only unknown is which number to put for /dev/disk_ (or possibly one script with two unknowns: disk number and number of partitions... depending on how "diverse" those service disks are).

Last edited by Hal Itosis; 06/08/11 05:36 AM. Reason: FINAL CLEANUP (sorry)
Re: diskutil challenge!
Hal Itosis #15932 06/08/11 05:30 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
Have at it:
Code:
#!/bin/bash

tempfile=/var/tmp/ptemp

pl=""

pl="${pl} Service_Lion=17"
pl="${pl} Service_Snow=15"
pl="${pl} Service_Leopard=23"
pl="${pl} Service_Tiger=12"
pl="${pl} Mac_OS_10.3.4=1"
pl="${pl} Mac_OS_10.3.4_Disc_2=1"
pl="${pl} Mac_OS_10.4.6=4"
pl="${pl} Mac_OS_10.4.7=6"
pl="${pl} Mac_OS_10.5.6=8"
pl="${pl} Mac_OS_10.6.3=9"
pl="${pl} Mac_OS_10.7.0=9"
#pl="${pl} Service_Data"  # Service Data will take the remainder

pl=${pl:1}

# count partitions
plc=$(echo "$pl" | sed 's/[^ ]//g')
plc=${#plc}
((plc++))  #  add one for diff in spaces vs entries
((plc++))  #  add one for Data

# calculate space required (in GB)

nsize=20  # set aside at least this many GB for Service data
p="$pl "
while [ -n "$p" ] ; do
  pp=${p%% *}
  p=${p#* }
  pz=${pp#*=}
  ((nsize+=pz))
done

# fyi
clear
echo
echo "PHR partitioner"
echo
echo "This script will FORMAT a hard drive.  All information on"
echo "the flash drive will be lost!  You will need a capacity of"
echo "at least ${nsize} GB."
echo
if [ $EUID != 0 ] ; then
  admins=" $(dscl . -read /Groups/admin | grep GroupMembership | cut -c 23-999) "
  if [ -z "$(echo "$admins" | grep " ${USER} ")" ] ; then
    echo "You must be logged in as an administrator to run this installer."
    exit
  fi
  sudo -k
  echo "Enter your administrator password to continue:"
  sudo -v
  if [ $? != 0 ] ; then
    echo "Cannot continue without password.  If your password is blank,"
    echo "you will need to change it to something non blank."
    exit
  fi
  sudo "$0"
  exit
fi
echo
echo "Authenticated as an administrator."
echo
echo


# build list of eligible destinations, into array
echo "Checking attached volumes..."
echo
diskutil list | grep "^/dev/disk" > $tempfile
dc=0
options=""
while read d ; do
  echo -n "Found device $d... "
  if [ "$d" == "/dev/disk0" ] ; then
    echo "(booted)"
    continue
  elif [ -z "$(diskutil info $d | grep "Ejectable" | grep -i "yes")" ] ; then
    echo "(not ejectable)"
    continue
  elif [ -z "$(diskutil info $d | grep " Read" | grep -i "no")" ] ; then
    echo "(read-only)"
    continue
  elif [ -n "$(diskutil info $d | grep " Protocol" | grep -i "Disk Image")" ] ; then
    echo "(disk image)"
    continue
  else
    ds=$(diskutil info $d | grep "^ \+Total Size" | cut -d "(" -f 2 | cut -d " " -f 1)
    ((ds=ds/1024/1024/1024))  #  capacity in GB
    if [ $ds -lt $nsize ] ; then
      echo "($ds GB is too small to install onto)"
      continue
    fi
  fi
  echo "(eligible)"
  devs[dc]=$d
  ((dc++))
  options="$options $d"
done < $tempfile
rm $tempfile


# verify at least one eligible device
if [ $dc == 0 ] ; then
  echo
  echo "No eligible volumes found."
  echo
  echo "Conncet a hard drive and rerun script to partition for PHR"
  echo
  exit
fi
options=${options:1}
echo
echo


# list partitions on each eligible device
for ((di=0;di<dc;di++)) ; do
  d=${devs[di]}
  echo "Listing partitions on device $d..."
  diskutil list $d | grep " ${d#/dev/}s[0-9]*$"
  echo
done
echo
echo


# select destination device
echo "Select device to repartition:"
select d in $options ; do
  if [ -n "$d" ] ; then
    echo
    echo "Device selected: $d"
    break
  fi
done
echo

# get device size
ds=$(diskutil info $d | grep "^ \+Total Size" | cut -d "(" -f 2 | cut -d " " -f 1)
((ds=ds/1024/1024/1024))  #  convert bytes to gb
echo "$nsize GB is required to install PHR.  The selected device is $ds GB in capacity."

# verify adequate capacity
if [ $ds -lt $nsize ] ; then
  echo
  echo "Sorry, we require at least a $nsize MB flash drive"
  echo
  exit
fi


# last chance
echo
echo "The following partitions will be ERASED:"
diskutil list $d | grep " ${d#/dev/}s[0-9]*$"
echo
echo -n $'\a\a\a'
while true ; do
  read -p "Type \"FORMAT\" to erase and reformat $d: " c
  if [[ ("$c" == "FORMAT") || ("$c" == "format") ]] ; then
    break
  fi
  echo $'\a'
done
echo
echo


# unmount volumes before attempting repartition
echo "Unmounting volumes on $d..."
diskutil list $d | grep " ${d#/dev/}s[0-9]*$" | while read p ; do
 pd="/dev/${p##* }"
  echo -n "  unmounting $pd... "
  hdiutil unmount $pd 2> /dev/null > /dev/null
  if [ $? == 0 ] ; then
    echo "done."
  else
    echo "unable to unmount.  Be sure all files on device are closed."
    exit
  fi
done
echo
echo


# repartition device.  takes awhile
echo "Creating partition map.  This will take a few minutes:"
echo
c="diskutil partitionDisk $d $plc APM"
for p in $pl ; do
  pv=${p%=*}
  pg=${p#*=}
  pv="'"$(echo "$pv" | sed "s/_/ /g")"'"
  c="$c JHFS+ ${pv} ${pg}G"
done
c="$c JHFS+ 'Service Data' R"  #  Data volume takes remainder
echo
echo "Past this command into terminal:"
echo
echo "$c"
echo
exit



I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #15943 06/09/11 06:36 AM
Joined: Sep 2009
Offline

Joined: Sep 2009
This may take a while, but i want to touch on this part first...
Originally Posted By: Virtual1
Code:

if [ $EUID != 0 ] ; then
	admins=" $(dscl . -read /Groups/admin | grep GroupMembership | cut -c 23-999) "
	if [ -z "$(echo "$admins" | grep " ${USER} ")" ] ; then
		echo "You must be logged in as an administrator to run this installer."
		exit
	fi 

The dscl stuff can be omitted and handled more simply this way (using id):
Code:
if [ `id -u` -ne 0 ]
then
	if id |grep -q -s -v '80(admin)'
	then
		echo "You must be logged in as an administrator to run this script."
		exit
	fi

But those lines also raise a question: is this script intended for the public at large... or just for your personal usage?

--

Then the conclusion of your authorization section goes like this...
Originally Posted By: Virtual1
Code:

		exit
	fi
	sudo "$0"
	exit
fi

I totally get what you're doing there, but i've never seen it done anywhere else before. Maybe it's kosher, but my gut tells me there must be some reason why that might be regarded as slightly more than unorthodox. The normal (safer?) route would be to tell the user to re-execute it using sudo by themselves (not do it for them). I realize "it works" but...

One seldom (if ever) sees scripts with 'sudo' in them — let alone using it to call another instance of themselves. Did you devise that method on your own, or dig it up somewhere? Anyway, knowing whether this script is destined for 'jon q public' or seasoned techs will give me some idea on which way i may modify the content.

--

Also, It appears that the version you posted is strictly handling the 12 partition service disk. So then, is 12 partitions written in stone here? Or was your use of the $pl array done so that this version could be quickly adapted for fewer partitions by simply commenting out a few lines? [i'm trying to decide whether to do away with the array approach entirely, or does that aspect need to remain intact?]

OTOH, using 2 simpler arrays (each 1 dimensional) might be even better, because their index value can "tie" them together while also eliminating much of those fancy ${parameter%=*} expansions. (it's only at the very end when it all gets fed to diskutil that both names and sizes actually need to be in "sync"... and having them stuck together at the very start of the script isn't serving any purpose that i can see).


[p.s. - i am embroiled in other "projects" so give me a week or so... but i'm definitely interested in knocking this off before summer vaca starts, if possible.]

Re: diskutil challenge!
Hal Itosis #15955 06/09/11 04:57 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
The dscl stuff can be omitted and handled more simply this way (using id):

DSCL is used to tell if the user is an admin, not root. if they're not an admin (which root incidently is) they can't use sudo so there's no point in asking for their password.


But those lines also raise a question: is this script intended for the public at large... or just for your personal usage?

Most of what I write I write in a way that can be used by others, even if I only intend to use it myself. Months or even years later I can encounter a need to give code to someone else or to reuse it myself, and this makes it ready-to-go. As of late it's also frighteningly common for me to encounter code in my toolkit that I go to use, and look at it and think "well that's a really good idea, whoever wrote that was really clever!" and then I realize it was my code. Such is my memory. So good comments and clear layout / ease of reuse is very important to me even on a personal level. It's a good thing in that it forces me to code this way, which really everyone should.


I totally get what you're doing there, but i've never seen it done anywhere else before. Maybe it's kosher, but my gut tells me there must be some reason why that might be regarded as slightly more than unorthodox.

I'm sure I'm not the first one to think of it but I did think it up independently. It neatly provides the prompt for the user and authexec's the code. Sometimes I also place a "sudo -k" in front of it to insure it asks for the password in the event they have recently auth'd, for consistency's sake. My service drives boot up as root, which just skates right through that code like it wasn't even there, so all-around it's a very elegant solution. Feel free to criticize that too, but even if you work in a prison you don't have to use a key on every single door you go through during the day, such would be excessive even in that setting.

Since this modified script doesn't actually DO the diskutil command it doesn't even need it though. But I was hoping to fix that wink


Or was your use of the $pl array done so that this version could be quickly adapted for fewer partitions by simply commenting out a few lines?

Correct. I like to make my code as general and easily maintained as possible. Heck, the whole thing could be boiled down to one line of diskutil that has the partition names hardcoded inside quotes if I didn't care about maintainability or readability. But I try to be consistent on both counts.


OTOH, using 2 simpler arrays (each 1 dimensional) might be even better, because their index value can "tie" them together

considered it, may modify it for that later. No point in wasting time on it if it can be made to work automatically anyway. The only reason there's so much already fleshed out here is because I wasn't expecting this problem. Talk of code-reuse, this was actually copied almost verbatim from another script that partitions out about 25 partitions on a usb flash drive for service diagnostic images that Apple requires me to use. (beats lugging around 25 CDs!) That code needs to be modified once or twice a month. But in that case all the partitions were named without spaces in them so I didn't encounter this problem until I got sick of manually repartitioning all the service drives around here every time there was something to add, and lifted the code from there to here, only to find my using spaces in my partition names blew it up. I suppose I could rename the partitions, but y'know, I'm stubborn that way. I'd rather win than concede, even when there's no added practical value in winning. wink


i am embroiled in other "projects" so give me a week or so

Thank you for your time, insight, and patience. Reply at your convenience, I'm not in a big hurry.


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #15968 06/10/11 03:15 AM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
DSCL is used to tell if the user is an admin, not root. if they're not an admin (which root incidently is) they can't use sudo so there's no point in asking for their password.

Okay, but the 'id' way is smaller and probably (a skosh) quicker. wink

--

Going for that gold star now (well, a silver at least):

Notes:
  1. Even though my script includes a $tSize variable [initially reserving 20 gigs and then calculating the full total (based on all uncommented partition data)], it never actually makes use of that info. [see 4.]

  2. I have skipped the whole authorization business, but it can be pasted back in easily enough. (i'm not sure if sudo is even needed, since Disk Utility in the GUI doesn't usually ask for a password unless the operator is not an admin). My test runs all worked sans sudo anyway. [see 4.]

  3. Also skipped various sanity checks, as well as the whole disk analysis/selection part (instead offering only a peek at df to let the user glimpse the device numbers). [see 4.]

  4. The reason for all of the above is simply that my focus was to find a way of getting diskutil to accept some sort of data input when the volume names contain spaces.

    I too initially gave up on the "single variable" concept after many failed experiments... but eventually concluded that it would indeed be needed for any practical (useful) script solution. [one alternative would entail a 12-stage case statement, which would be lengthy, ugly, and less flexible in the end.]

    Luckily, i stumbled onto a mechanism to get the "single variable" to work. Quite simply: put hard quotes around soft quotes, and have Bash properly pass the junk to diskutil via its builtin eval command (which exists in the Bourne sh too). It's quite possible that eval might allow some other quoting methods to work, but at this point i'm placing the whole matter into the 'mission accomplished' folder.

Code:
#!/bin/bash -
# psd  :::  Partition Service Disk
# flexible script to facilitate disk partitioning
#(c)EF/-HI-2011.Jun.07 [rev:2011.Jun.10]

IFS=$' \t\n'
declare -x PATH=/bin:/sbin:/usr/bin:/usr/sbin
declare -i pNum=0 dNum=666 # <--hopefully imaginary
PRG=`basename "$0"`

# variable data may be edited or commented out entirely...
# >========================================>
name1='"Service Lion"'		size1=17
name2='"Service Snow"'		size2=15
name3='"Service Leopard"'	size3=23
name4='"Service Tiger"'		size4=12
name5='"Mac OS 10.3.4"'		size5=1
name6='"Mac OS 10.3.4 Disc 2"'	size6=1
name7='"Mac OS 10.4.6"'		size7=4
name8='"Mac OS 10.4.7"'		size8=6
name9='"Mac OS 10.5.6"'		size9=8
name10='"Mac OS 10.6.3"'	size10=9
name11='"Mac OS 10.7.0"'	size11=9
name12='"Service Data"'		size12=R
# <========================================<
# ...BUT, the final partition MUST be given a size of R

# reserve 20 gigs for the final (data) partition:
tSize=20
# count (and list) partitions:
for n in ${!name*}; do ((pNum+=1)); echo "${!n}"; done
echo
echo "$pNum partitions desired."
# calculate total size:
for s in ${!size*}; do ((tSize+=$s)); done # note: Bash math ignores the 'R'
echo "$tSize gigabytes needed."
echo

df -l -h
printf '\nenter the integer of the /dev/disk_ to partition '
read dNum
echo

c=
for i in {1..12}
do
	j=name$i k=size$i
	if [[ -n ${!j} ]]
	then
		[ ${!k} = R ] && g= || g=G
		c="$c JHFS+ ${!j} ${!k}$g"
	fi
done

printf 'This script (%s) is ready to execute the following command:\n\n' "$PRG"
echo diskutil partitionDisk /dev/disk$dNum $pNum APM $c

doScript=n
printf '\nShall we allow this script to erase /dev/disk%d ? (y/n [n]) ' $dNum
read doScript
echo
if [[ $doScript != y ]]
then
	echo "$PRG: task aborted."
	exit 1
else
	eval diskutil partitionDisk /dev/disk$dNum $pNum APM $c
	exit $?
fi
 


-HI-

Last edited by Hal Itosis; 06/10/11 04:24 AM. Reason: oops, i left an 'M' in there from my Flash disk tests. [and subsequent minor tweak]
Re: diskutil challenge!
Hal Itosis #15991 06/11/11 03:18 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
While reading your post for some reason it inspired me to a solution to the single variable problem. I've done this in the past, not sure why it didn't occur to me before. Have the script create a two line script of its own, and run that. That should work around diskutil's defective parameter processing.

I was unaware of the EVAL command, interesting...


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #15994 06/11/11 04:02 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
Have the script create a two line script of its own, and run that.

Please post that syntax when you get a chance.




Here's my latest rev with additional window dressing. The assembling of the command itself remains identical... and it's still the 'geek' version (i.e., sans sanity checks and/or any guided tours):

Code:
#!/bin/bash -
# psd  :::  Partition Service Disk
# flexible script to facilitate disk partitioning
#(c)EF/-HI-2011.Jun.07 [rev:2011.Jun.11]

IFS=$' \t\n'
declare -x PATH=/bin:/sbin:/usr/bin:/usr/sbin
declare -i x=0 pNum=0 dNum=666 # <--Initialize to an improbable disk number.
declare -i tSize=20 # <--Preallocate 20 gigs for the 'remainder' partition.
### OBS/NB: all sizes in gigabytes. ###
PRG=`basename "$0"`

# Variable data values may be edited and/or commented out entirely...
# >========================================>
name1='"Service Lion"'		size1=17
name2='"Service Snow"'		size2=15
name3='"Service Leopard"'	size3=23
name4='"Service Tiger"'		size4=12
name5='"Mac OS 10.3.4"'		size5=1
name6='"Mac OS 10.3.4 Disc 2"'	size6=1
name7='"Mac OS 10.4.6"'		size7=4
name8='"Mac OS 10.4.7"'		size8=6
name9='"Mac OS 10.5.6"'		size9=8
name10='"Mac OS 10.6.3"'	size10=9
name11='"Mac OS 10.7.0"'	size11=9
name12='"Service Data"'		size12=R
# <========================================<
# ...but, exactly one active partition should be assigned a size of R.

#-----------------------------------------------------------------------------#
Message () { printf '%s: %s: %s\n' "$PRG" "$1" "${2:-}" >&2; }
Bailout () { ((x+=1)); Message 'bailing on error' "${*:-}"; exit $x; }
NoError () { ((x+=$1)); [ $1 -eq 0 ] || Message error "${2:-}"; return $1; }

trap -- 'stty echo -igncr; [ $x -eq 0 ] && echo "$PRG: no errors  :-)"' EXIT
trap -- 'printf "\e[0;39m\n"; ((x+=1)); exit $x' INT QUIT ABRT TERM
#-----------------------------------------------------------------------------#

# Count and list active partitions (number, name and size):
for i in {1..12}
do
	j=name$i k=size$i
	if [[ -n ${!j} ]]
	then
		((pNum+=1))
		[ ${!k} = R ] && z=$tSize || z=${!k}
		printf '%2d:  %-32s %7dG' $pNum "${!j}" "$z"
		[ ${!k} = R ] && echo ' (min)' || echo
	fi
done
NoError $? 'had a problem listing partitions.' || Bailout 'whatzup with that?'
echo
printf '%2d partitions planned; ' $pNum
# Calculate total size:
for s in ${!size*}; do ((tSize+=$s)); done # note: Bash math ignores the R.
NoError $? 'had a problem calculating total size of all partitions,'
printf 'total space needed %dG.\n\n' $tSize
read -e -s -p '[type any character to continue] ' -n 1 foo
printf '\r\e[2K\e[0;39m\n'

df -h -l
echo
sleep 1
while [ $dNum -eq 666 ] || [ $dNum -eq 0 ] # Disallow boot volume selection.
do
	read -e -p 'enter the INTEGER of the /dev/disk_ to partition: ' dNum
done
NoError $? 'integer input' || Bailout 'odd read loop activity. (e.g., PEBKAC)'
echo

c=
for i in {1..12}
do
	j=name$i k=size$i
	if [[ -n ${!j} ]]
	then
		[ ${!k} = R ] && g= || g=G
		c="$c JHFS+ ${!j} ${!k}$g"
	fi
done
NoError $? 'had a problem while assembling the command.' ||
	Bailout 'best not to proceed, since the command might be flawed.'

Message 'script is now ready to execute the following command'
echo
echo diskutil partitionDisk /dev/disk$dNum $pNum APM $c
NoError $? 'had a problem echoing the assembled command.' ||
	Bailout 'best not to proceed, since the command might be flawed.'

echo
goAhead=n
read -e -p "permit $PRG to ERASE /dev/disk$dNum ? (y/n [n]): " goAhead
echo
if [[ $goAhead != y ]]
then
	Bailout 'task aborted by user.'
else
	trap '' HUP INT QUIT ABRT TERM # Prevent interruptions.
	eval diskutil partitionDisk /dev/disk$dNum $pNum APM $c
	NoError $? 'um... diskutil had some kinda problem. :-('
fi
exit $x
 

-HI-

Edit: if it wasn't already apparent, then let's just establish the fact that —when using this script —trying to create a volume with hard (') or soft (") quotes in its name is a recipe for fail.

Last edited by Hal Itosis; 06/11/11 04:46 PM.
Re: diskutil challenge!
Hal Itosis #16013 06/12/11 09:18 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Hal Itosis
### OBS/NB: all sizes in gigabytes. ###

<snip>

for s in ${!size*}; do ((tSize+=$s)); done # note: Bash math ignores the R.

Probably should mention that —as written —these scripts only handle integers for gigabyte values. The basic $(( 2 + 2 )) syntax will refuse floating point numbers like 4.56 + 7.89 (yet diskutil would happily accommodate same).

So either change the code which calculates the total, or remember to only use integer values. Perhaps edit that one line to read:
Code:
### OBS/NB: all sizes in "whole number" gigabytes (i.e., integers only). ###

Re: diskutil challenge!
Hal Itosis #16018 06/13/11 11:48 AM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
yes I noticed decimal values can be issues with volume sizes. That being the case, you can just deal with the next smaller unit. 1024M instead of 1G for example. I'll be reworking the partition app to build and call its own subshell today if I have time.


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #16020 06/13/11 03:58 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
Here's the final working script. I have commented out most of the partitions to make your testing go quicker:

Code:

#!/bin/bash

vers="2011.06.13.B"
tempfile=/var/tmp/ptemp

# build list of partitions to create
pl=""
pl="${pl} Service_Lion=17"
#pl="${pl} Service_Snow=15"
#pl="${pl} Service_Leopard=23"
#pl="${pl} Service_Tiger=12"
#pl="${pl} Mac_OS_10.3.4=1"
#pl="${pl} Mac_OS_10.3.4_Disc_2=1"
#pl="${pl} Mac_OS_10.4.6=4"
#pl="${pl} Mac_OS_10.4.7=6"
#pl="${pl} Mac_OS_10.5.6=8"
#pl="${pl} Mac_OS_10.6.3=9"
pl="${pl} Mac_OS_10.7.0=9"
#pl="${pl} Service_Data"  # Service Data will take the remainder
pl=${pl:1}


# count partitions
plc=$(echo "$pl" | sed 's/[^ ]//g')
plc=${#plc}
((plc++))  #  add one for diff in spaces vs entries
((plc++))  #  add one for Data


# calculate space required (in GB)
nsize=20  # set aside at least this many GB for Service data
p="$pl "
while [ -n "$p" ] ; do
  pp=${p%% *}
  p=${p#* }
  pz=${pp#*=}
  ((nsize+=pz))
done


# fyi
clear
echo
echo "PHR partitioner"
echo
echo "This script will FORMAT a hard drive.  All information on"
echo "the hard drive will be lost!  You will need a capacity of"
echo "at least ${nsize} GB."
echo
echo


# authenticate
if [ $EUID != 0 ] ; then
  admins=" $(dscl . -read /Groups/admin | grep GroupMembership | cut -c 23-999) "
  if [ -z "$(echo "$admins" | grep " ${USER} ")" ] ; then
    echo "You must be logged in as an administrator to run this installer."
    exit
  fi
  sudo -k
  echo "Enter your administrator password to continue:"
  sudo -v
  if [ $? != 0 ] ; then
    echo "Cannot continue without password.  If your password is blank,"
    echo "you will need to change it to something non blank."
    exit
  fi
  sudo "$0"
  exit
fi
echo
echo "Authenticated as an administrator."
echo
echo


# build list of eligible destinations, into array
echo "Checking attached volumes..."
echo
diskutil list | grep "^/dev/disk" > $tempfile
dc=0
options=""
while read d ; do
  echo -n "Found device $d... "
  if [ "$d" == "/dev/disk0" ] ; then
    echo "(booted)"
    continue
  elif [ -z "$(diskutil info $d | grep "Ejectable" | grep -i "yes")" ] ; then
    echo "(not ejectable)"
    continue
  elif [ -z "$(diskutil info $d | grep " Read" | grep -i "no")" ] ; then
    echo "(read-only)"
    continue
  elif [ -n "$(diskutil info $d | grep " Protocol" | grep -i "Disk Image")" ] ; then
    echo "(disk image)"
    continue
  else
    ds=$(diskutil info $d | grep "^ \+Total Size" | cut -d "(" -f 2 | cut -d " " -f 1)
    ((ds=ds/1024/1024/1024))  #  capacity in GB
    if [ $ds -lt $nsize ] ; then
      echo "($ds GB is too small to install onto)"
      continue
    fi
  fi
  echo "(eligible)"
  devs[dc]=$d
  ((dc++))
  options="$options $d"
done < $tempfile
rm $tempfile
echo
echo


# verify at least one eligible device
if [ $dc == 0 ] ; then
  echo "No eligible volumes found."
  echo
  echo "Conncet a hard drive and rerun script to partition for PHR"
  echo
  exit
fi
options=${options:1}


# list partitions on each eligible device
for ((di=0;di<dc;di++)) ; do
  d=${devs[di]}
  echo "Listing partitions on device $d..."
  diskutil list $d | grep " ${d#/dev/}s[0-9]*$"
  echo
done
echo
echo


# select destination device
echo "Select device to repartition:"
select d in $options ; do
  if [ -n "$d" ] ; then
    echo
    echo "Device selected: $d"
    break
  fi
done
echo
echo

# get device size
ds=$(diskutil info $d | grep "^ \+Total Size" | cut -d "(" -f 2 | cut -d " " -f 1)
((ds=ds/1024/1024/1024))  #  convert bytes to gb
echo "$nsize GB is required to install PHR.  The selected device is $ds GB in capacity."
echo
echo

# verify adequate capacity
# unnecessary now since only volumes with enough space are selectable above
#if [ $ds -lt $nsize ] ; then
#  echo
#  echo "Sorry, we require at least a $nsize GB drive"
#  echo
#  exit
#fi


# last chance to chicken out
echo "The following partitions will be ERASED:"
diskutil list $d | grep " ${d#/dev/}s[0-9]*$"
echo
echo -n $'\a\a\a'
while true ; do
  read -p "Type \"FORMAT\" to erase and reformat $d: " c
  if [[ ("$c" == "FORMAT") || ("$c" == "format") ]] ; then
    break
  fi
  echo $'\a'
done
echo
echo


# unmount volumes before attempting repartition
clear
echo
echo "Unmounting volumes on $d..."
diskutil list $d | grep " ${d#/dev/}s[0-9]*$" | while read p ; do
 pd="/dev/${p##* }"
  echo -n "  unmounting $pd... "
  hdiutil unmount $pd 2> /dev/null > /dev/null
  if [ $? == 0 ] ; then
    echo "done."
  else
    echo "unable to unmount.  Be sure all files on device are closed."
    exit
  fi
done
echo
echo


# since diskutil cannot parse volume names with spaces in them when
# passed in as individual shell variables, it's necessary to create a
# shell script file with the partitioning command in it


# create partitioning script
touch "$tempfile"
chmod +x "$tempfile"
echo "#!/bin/bash" >> "$tempfile"
c="diskutil partitionDisk $d $plc APM"
for p in $pl ; do
  pv=${p%=*}
  pg=${p#*=}
  pv="'"$(echo "$pv" | sed "s/_/ /g")"'"
  c="$c JHFS+ ${pv} ${pg}G"
done
c="$c JHFS+ 'Service Data' R"  #  Data volume takes remainder
echo -n "$c" >> "$tempfile"
echo ' 1>&2' >> "$tempfile"  # diskutil output sent to stderror to display
echo 'echo "$?"' >> "$tempfile"  # process result echoed to shell var "rc"


# run partition script
echo "Creating partition map.  This will take a few minutes."
echo
echo "running command: $c"
echo
#cat "$tempfile"
rc=$("$tempfile")
rm "$tempfile"
echo
echo


# check result
echo
if [ "$rc" == 0 ] ; then
  echo 'Success!'
else
  echo "Format failed, diskutil returned error code $rc"
fi
echo
exit




I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #16022 06/13/11 05:12 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
<snip>
# since diskutil cannot parse volume names with spaces in them when
# passed in as individual shell variables, it's necessary to create a
# shell script file with the partitioning command in it

<snip>

Well... of all the ingredients in that script which i might regard as unnecessary (and/or overdone), that comment certainly takes the cake. smirk

Re: diskutil challenge!
Hal Itosis #16035 06/14/11 12:20 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
comments such as that are necessary to prevent me from encountering the code a year from now and saying "huh... that's certainly convoluted... why didn't I just ....?" and wasting time trying to "fix" it.

This way I only have to learn hard lessons once.

Try not to think of all that stuff around it as "unnecessary", consider it instead to be "robust" wink Remember, I don't code for myself, I code for everybody.


I work for the Department of Redundancy Department
Re: diskutil challenge!
Virtual1 #16066 06/15/11 03:43 PM
Joined: Sep 2009
Offline

Joined: Sep 2009
Originally Posted By: Virtual1
Try not to think of all that stuff around it as "unnecessary", consider it instead to be "robust" wink Remember, I don't code for myself, I code for everybody.

I get that part. By 'unnecessary/overdone' i meant stuff like (as mentioned earlier) the dscl method of determining if the operator is an admin. The id approach is so much simpler [only two calls to id and one pipe to grep (as opposed to calling dscl, cut, echo and two greps involving three pipes, plus storing some "$admins" variable which never gets used again).] But even (as also mentioned earlier) the whole authorization step itself... it isn't actually needed, is it?

Also (as mentioned earlier) the scheme of employing the pl array that doesn't seem to offer any advantage (which indexed array access normally does), while simultaneously requiring the code [and its author] to spend energy laboriously "dereferencing" the items stuffed into that array throughout the rest of the script. Seems rather superfluous to me.

As for placing an executable script in /var/tmp/ —beyond being unnecessary —it could also be considered risky business (fodder for haxors). Normally such items would be created using mktemp (to randomize its name), and never be given world access (via chmod +x) as was done. Go with chmod u+x at least.

In the end however, that external script isn't needed. That is what this thread was all about in the first place. Thus your comment (and the fact that your script didn't adopt a single suggestion i've made) really floored me.

But hey, other readers can still benefit. So be it.

Re: diskutil challenge!
Hal Itosis #16093 06/16/11 06:17 PM
Joined: Aug 2009
OP Offline

Joined: Aug 2009
Originally Posted By: Hal Itosis
In the end however, that external script isn't needed. That is what this thread was all about in the first place. Thus your comment (and the fact that your script didn't adopt a single suggestion i've made) really floored me.

But hey, other readers can still benefit. So be it.


oh sorry, nothing personal there. it wasn't sufficiently broke for me to find time to "fix" it. Good suggestions but just no time right now, having enough problems fixing stuff that is really broken.


I work for the Department of Redundancy Department

Moderated by  alternaut, cyn 

Link Copied to Clipboard
Powered by UBB.threads™ PHP Forum Software 7.7.4
(Release build 20200307)
Responsive Width:

PHP: 7.4.33 Page Time: 0.038s Queries: 60 (0.027s) Memory: 0.7494 MB (Peak: 0.9630 MB) Data Comp: Zlib Server Time: 2024-03-28 21:50:55 UTC
Valid HTML 5 and Valid CSS