>man grep
February 18, 2015 4:16 PM   Subscribe

I’m trying to write a short shell script for Mac OS X that will disable CoreStorage. To do this I need to capture the first Logical Volume UUID (lvUUID) from the output of "diskutil cs list" and use it in an ensuing command. I am having trouble though and it seems to be a simple issue with grep. Obviously still pretty new to the command line.

Sample output from diskutil cs list looks like this: http://pastebin.com/aNbXFdVE

Here's what I've got (NOTE the 8 underscores are actually spaces, but strings of spaces get stripped in post):

LVU=$(diskutil cs list | grep "________+-> Logical Volume" | awk '{print $5}')
diskutil corestorage revert $LVU

Seems like my problem is with how I'm trying to grep the lvUUID from the Logical Volume line specifically. For example, if I try...

LVU=$(diskutil cs list | grep "Logical Volume" | awk '{print $5}')
echo $LVU

...I do get a UUID. Problem is, it's from the "Logical Volume Family" line and not the "Logical Volume" line. I assume because grep hits that first.

I tried escaping the + - > characters with backslashes but that didn't seem to do the trick.

What's the stupid obvious thing I'm missing (or failing to note)? Thanks in advance!
posted by churl to Computers & Internet (14 answers total) 4 users marked this as a favorite
You could

grep -v Family

somewhere, to exclude that line.
posted by pompomtom at 4:23 PM on February 18, 2015 [1 favorite]

Best answer: You should totally test this out extensively before using it, but here's something I quickly wrote that seems to reliably get the right ID on my Mac:

diskutil cs list | grep 'Logical Volume' | awk '{print $4}' | grep "^[^Family|Group]"
posted by cvp at 4:25 PM on February 18, 2015 [1 favorite]

I think you want to use a regular expression that takes "Logical Volume" + space + numeral but rejects "Logical Volume Family/Group".

grep 'Logical Volume [0-9]' worked when I tested it on your sample output.

(Edit: that assumes that the ID only starts with a numeral)
posted by no regrets, coyote at 4:29 PM on February 18, 2015 [1 favorite]

A slightly more condensed version:

diskutil cs list | grep 'Logical Volume [^Family|Group]' | awk '{print $4}'

This could probably be made even more reliable with a regex that only captures UUIDs rather than relying on awk. I might write that as [\w-]{36} but grep doesn't seem to be taking that... might be a flag that lets you use other regex features.
posted by cvp at 4:35 PM on February 18, 2015

Best answer: Given your test input:

$ diskutil cs list | grep -wE "^([ ])\1{6}" | grep -oEi "Logical Volume [A-F0-9-]+" | awk -F' ' '{print $3}'

posted by a lungful of dragon at 4:49 PM on February 18, 2015 [1 favorite]


sed -rn 's/^ *\+-> Logical Volume ([0-9A-F-]*).$/\1/p'

The ([0-9A-F-]*) part captures the UUID. The $ says, the UUID must reach all the way to the end of the line. The . in between is to deal with your DOS line endings. Not sure why a MAC OS X utility is giving you DOS line endings, but there you are.

As an alternative, you can convert the line endings first. DOS line endings are \r\n whereas UNIX line endings are \n, so you just strip the \r off:

tr -d '\r' | sed -rn 's/^ *\+-> Logical Volume ([0-9A-F-]*)$/\1/p'

Hope that helps.
posted by d. z. wang at 4:58 PM on February 18, 2015 [1 favorite]

By the way, sed -rn 's/^...$/\1/p' is often a useful pattern for screen-scraping human-readable text input.
posted by d. z. wang at 4:59 PM on February 18, 2015

Best answer: cvp has what I would suggest. Just a general tip, to use extended regex with grep, you can either use egrep or grep -E. So, for example if you knew for sure there were at least 8 spaces at the start of the line, you could try:

diskutil cs list | grep -E "\s{8,}\+\-> Logical Volume" | awk '{ print $4 }'

posted by bluefly at 5:26 PM on February 18, 2015 [2 favorites]

Regular expressions are not a great way to deal with xml or with nested human-readable formats. Have you considered extracting the data with plist tools? That way you could focus on indexing first, and come up with something easy to parse:
$ KEYPATH="CoreStorageLogicalVolumeGroups.0.CoreStorageLogicalVolumeFamilies.0.CoreStorageLogicalVolumes.0"
$ diskutil cs list -plist | plutil -extract $KEYPATH json -r -o - -
  "CoreStorageUUID" : "CFC92FAD-408B-460E-B801-05DBFC340800",
  "CoreStorageRole" : "LV"
Or you could just pass the data into a language that is comfortable with extracting a named key:
$ diskutil cs list -plist | \
plutil -convert json -o - - | \
python -c 'import sys, json; print json.load(sys.stdin)["CoreStorageLogicalVolumeGroups"][0]["CoreStorageLogicalVolumeFamilies"][0]["CoreStorageLogicalVolumes"][0]["CoreStorageUUID"]'


posted by Phssthpok at 5:52 PM on February 18, 2015 [3 favorites]

The diskutil(8) manpage specifies
If -plist is specified, then a property list will be emitted
instead of the normal user-readable output.
So if you're up for parsing some XML, diskutil coreStorage list -plist . Now you just have to learn how to use xmllint . ;-)
posted by books for weapons at 5:54 PM on February 18, 2015 [2 favorites]

Response by poster: Man I feel dumb that I waited so long to ask this! Thanks so much. Got it working.

In fact, all the suggestions worked, except I ran into trouble with a couple (possibly something I was doing wrong, forgive my ignorance):
d.z.wang: sed throws an error when I use -r as an argument, maybe different version of sed? (Also odd linebreaks may be a remnant from the text editor I composed the post in, not sure.)
cvp: I went with your first suggestion, but your second condensed version spit out a blank string, dunno why.
no regrets, coyote: Not positive how lvUUIDs are generated but assuming Hex so I think it has to accommodate the first char being A-F (or numerical).

FWIW here's the script in case it's useful to some future traveler. Worked for me on 2 machines so far. Thanks again!
LVU=$(diskutil cs list | grep 'Logical Volume' | awk '{print $4}' | grep "^[^Family|Group]")
echo "Are you sure you want to disable CoreStorage on lvUUID $LVU? (Y/N):";
read answer
if [ "$answer" == "Yes" ] || [ "$answer" == "yes" ] || [ "$answer" == "Y" ] || [ "$answer" == "y" ]; then
diskutil corestorage revert $LVU
echo "CoreStorage drive $LVU was disabled. Enter admin password to restart now."
sudo shutdown -r now
echo "CoreStorage will not be disabled."
posted by churl at 6:02 PM on February 18, 2015 [1 favorite]

Response by poster: (And yeah point totally taken about how I'm kinda using a hammer to drive in a screw here.)
posted by churl at 6:09 PM on February 18, 2015

From a more general unix perspective, the website explainshell.com can be a great resource for getting a feel for shell command arguments.

Example of a dissected unix command with args
(the first one one d. z. wang posted).
posted by oceanjesse at 6:35 PM on February 18, 2015 [2 favorites]

Late to the party but I'd do it Perl / PCRE-ish to make a stricter match:
... | perl -ne '/ \b Logical \s Volume \s+ ( [[:xdigit:]-]{36} ) $/x && print $1'

Match: word boundry, 'Logical', space, 'Volume', space(s), 36 hex or '-' characters, end of line. Capturing and printing the 36 hex or '-' characters.

You could easily be more explicit in the UUID format if desired:
( [:xdigit:]{8} - [:xdigit:]{4} - [:xdigit:]{4} - [:xdigit:]{4} - [:xdigit:]{12} )

Or check for multiple 'Logical Volume's and croak. I'd probably avoid the whole pipeline and replace the 'print' with:

system qw(diskutil corestorage revert), $1

Then I'd go overboard:
#!/usr/bin/env perl
my @uuid = qx(diskutil cs list) =~ m/ \b Logical \s Volume \s+ ( [[:xdigit:]-]{36} ) $/xm; 
die "Too many Logical Volumes!\n" unless @uuid == 1;
system qw(diskutil corestorage revert), $uuid[0];

But maybe in your case, just the finding the UUID part...
posted by zengargoyle at 9:44 PM on February 18, 2015

« Older YA Novel set in the UK   |   Help me assuage my first world consumer guilt! Newer »
This thread is closed to new comments.