MacPorts - Quick Install Script

I wrote this script to automatically install the relevant version of MacPorts. It's supposed to be used in a fashion similar to the official installer for HomeBrew.

FYI: The real reason that I decided to write this script is; I'm not all that confident in every aspect of my bash scripting abilities and I needed a project to practice on.

While I have a decent working knowledge of bash from an REPL standpoint, I haven't been scripting very long; so keep that in mind.

macports-installer.sh

#!/bin/bash
#
urisvn=https://svn.macports.org/repository/macports/trunk/www/includes/common.inc
uridist=https://distfiles.macports.org/MacPorts
#
mpvers=$(curl -s$urisvn | grep -m 1 -e \'*.*.*\' | cut -d \' -f 2)
osmajor=$(uname -r | cut -d . -f 1) # case$osmajor in
8)
osvers=10.4
osname=Tiger
;;
9)
osvers=10.5
osname=Leopard
;;
10)
osvers=10.6
osname=SnowLeopard
;;
11)
osvers=10.7
osname=Lion
;;
12)
osvers=10.8
osname=MountainLion
;;
13)
osvers=10.9
osname=Mavericks
;;
14)
osvers=10.10
osname=Yosemite
;;
15)
osvers=10.11
osname=ElCapitan
;;
*)
echo Warning: unknown OS version
;;
esac
#
mppkg=MacPorts-${mpvers}-${osvers}-${osname} mpchk=MacPorts-${mpvers}
echo $mppkg.pkg echo$mpchk.chk.txt
curl $uridist/$mppkg.pkg -O
curl $uridist/$mpchk.chk.txt -O
sha256=$(shasum -a 256 ./$mppkg.pkg)
#
if grep $sha256$mpchk.chk.txt;
then sudo installer -verboseR -allowUntrusted -pkg ./$mppkg.pkg -target /; fi # rm ./$mppkg.pkg
rm ./$mpchk.chk.txt # if ! grep -F 'export PATH="/opt/local/bin:/opt/local/sbin:$PATH"' .bash_profile;

Error handling

If an error occurs (e.g. "unknown OS version" or download failure), the script blithely attempts to continue.

If a checksum failure occurs, then it exits silently with status code 0.

OS version switch

Instead of inferring the OS version based on the Darwin kernel release number, it would be simpler to read the version of Mac OS using sw_vers -productVersion.

Note that for versions ≤ 10.5, the MacPorts installer is distributed as a .dmg rather than a .pkg. You probably shouldn't try to support those ancient releases.

I am dissatisfied with the entire strategy for determining the URL from which to download the package:

1. You download common.inc, which contains PHP code.
2. Then, you "parse" the PHP using Bash to extract the MacPorts release number (mpvers).
3. To construct the package URL, you reproduce the logic of common.inc in Bash. In particular, you hard-code strings like uridist=https://distfiles.macports.org/MacPorts and mppkg=MacPorts-${mpvers}-${osvers}-${osname}. In my opinion, this is a fragile approach. Why not use PHP to interpret common.inc? Mac OS comes installed with a PHP interpreter. To be fair, executing common.inc as PHP code also carries a risk: we would be making an assumption that it has no nasty side-effects. By convention, the .inc extension implies that it should be side-effect free. In general, executing network code is a bad idea, but it's mitigated by the fact that it is served over HTTPS, and that you intend to trust the MacPorts developers anyway. Code organization For scripts of this complexity, it is a good idea to define functions. You would be better able to see the big picture of what the code does without wading through the whole thing from top to bottom. I've done it in my PHP solution below, but the same principle would apply to Bash. Suggested solution Consider this alternate implementation in PHP. It's longer than yours, but much of the excess is due to error handling, which your script lacked. #!/usr/bin/php -d allow_url_include=1 -d error_reporting=~EWARNING <?php # Need to set allow_url_include=1 to allow this include over the network. # Need to set error_reporting=~EWARNING to suppress a warning when common.inc # tries to read a non-existant file /var/www/script_data. require 'https://svn.macports.org/repository/macports/trunk/www/includes/common.inc'; function pkgUrl() {$osVersion = sw_vers -productVersion;
switch(preg_replace('/\.\d+\s*$/', '',$osVersion)) {
# Uses globals from common.inc
case '10.6':  return $GLOBALS['snowleopard_pkg']; case '10.7': return$GLOBALS['lion_pkg'];
case '10.9':  return $GLOBALS['mavericks_pkg']; case '10.10': return$GLOBALS['yosemite_pkg'];
case '10.11': return $GLOBALS['elcapitan_pkg']; default: throw new Exception("Unsupported OS version$osVersion");
}
}

function download($url) {$filename = $_ENV['TMPDIR'] . '/' . basename(parse_url($url, PHP_URL_PATH));
if (FALSE === file_put_contents($filename, file_get_contents($url))) {
throw new Exception("Failed to download $url"); } return$filename;
}

function verifyChecksum($filename) { # Uses$checksums from common.inc
$correctChecksums = file_get_contents($GLOBALS['checksums']);
$sha256 = hash_file('sha256',$filename);
if (FALSE === strstr($correctChecksums,$sha256)) {
throw new Exception("Incorrect checksum for $filename"); } } function appendIfNoSuchLine($filename, $line) { if (FALSE !== strstr($line . "\n", file_get_contents($filename))) { return; } if (!($fp = fopen($filename, 'a'))) { throw new Exception("Could not write to$filename");
}
fwrite($fp,$line . "\n");
fclose($fp); } try {$pkg = download(pkgUrl());
verifyChecksum($pkg); system( sprintf('sudo installer -verboseR -allowUntrusted -pkg %s -target /', escapeshellarg($pkg)),
$status ); if (0 !=$status) {
throw new Exception("Installation failure");
}
unlink($filename); appendIfNoSuchLine($_ENV['HOME'] . '/.bash_profile',
'export PATH="/opt/local/bin:/opt/local/sbin:$PATH"' ); } catch (Exception$e) {
echo \$e->getMessage(), "\n";
return 1;
}

• I don't know anything about php. Like I said; the whole idea was to practice bash scripting. Do you really consider this script to be complex, by the way? Also, i don't know what you mean by common.inc. I appreciate your input; some parts are a bit unclear, though. (or presume prior knowledge of certain concepts). Maybe you could demonstrate with actual example snippets of your suggestions? Thanks again. – voices May 12 '16 at 15:04
• What I posted is a complete working solution. You can just save it, make it executable (chmod +x), and run it the same way as you would run your existing script. – 200_success May 12 '16 at 20:10
• I see that. I'm referring to your bash-related comments, only though. That's currently where my interest / focus lies. – voices May 13 '16 at 12:12
• Also, could you please demonstrate how; I could incorporate "error handling" into my script? – voices May 17 '16 at 11:02
• ||exit; ? – voices May 25 '16 at 18:20