Skip to content

Instantly share code, notes, and snippets.

@superkeyor
Last active December 24, 2023 04:16
Show Gist options
  • Save superkeyor/1d431a848426141707ebde736bbfc004 to your computer and use it in GitHub Desktop.
Save superkeyor/1d431a848426141707ebde736bbfc004 to your computer and use it in GitHub Desktop.
Bash script to install app on macOS from command line (can handle various situations)
function installapp {
local path=$1
local quiet=$2
if [ -z "$path" ]; then
echo "Usage:"
echo " installapp <path/to/directory> - Search a directory for dmg, pkg, zip files (in that order) to install."
echo " installapp <path/to/file> - Install a specific dmg, pkg, or zip file."
echo " installapp <URL> - Download and install a (dmg, pkg, zip) file from a URL."
echo " accepts -q or --quiet to auto confirm"
echo ""
echo "Examples:"
echo " installapp Acrobat/Mac"
echo " installapp Python/VSCode*.zip -q"
echo " installapp http://example.com/installer.dmg --quiet"
echo ""
echo "If an installer file is found, you will be asked for confirmation before installation."
return 1
fi
# local for the outer function, but "global" to nested inner functions below
# otherwise, they will "leak" to rest script outside this function
# better, unset them at the end of script
local downloadTempd=$(mktemp -d)
local zipTempd=$(mktemp -d)
local file=""
prompt_for_confirmation() {
local message=$1
if [[ "$quiet" == "-q" || "$quiet" == "--quiet" ]]; then
echo "$message ([y]/n):" # Optionally display the message
return 0 # Automatically confirm
fi
read -p "$message ([y]/n): " response
response=${response:-y}
if [[ $response == [Nn]* ]]; then
echo "User cancelled."
return 1
fi
return 0
}
handle_installation() {
local appfile=$1
local volume=$2
if [[ "$appfile" == *.pkg ]]; then
# sudo installer -pkg "$pkgfile" -target /
open -W "$appfile" # wait for pkg GUI to quit
else
local appfile_lower=$(echo "$appfile" | tr '[:upper:]' '[:lower:]')
if [[ "$appfile_lower" == *"install"* ]]; then
if prompt_for_confirmation "Plausible installer .app found ('$appfile'). Open it (y) or copy to /Applications (n)?"; then
echo "Opening installer app..."
open -W "$appfile"
else
echo "Copying $appfile to /Applications..."
sudo cp -rf "$appfile" /Applications
fi
else
echo "Copying $appfile to /Applications..."
sudo cp -rf "$appfile" /Applications
fi
fi
if [ -n "$volume" ]; then
sudo hdiutil detach "$volume"
fi
}
find_pkg_or_app() {
local search_path=$1
local depth=$2
local pkg_file=$(find "$search_path" -maxdepth "$depth" -type f -name "*.pkg" -print -quit)
if [ -n "$pkg_file" ]; then
echo "$pkg_file"
return
fi
local app_dir=$(find "$search_path" -maxdepth "$depth" -type d -name "*.app" -print -quit)
if [ -n "$app_dir" ]; then
echo "$app_dir"
fi
}
download_file() {
local url=$1
local dest=$downloadTempd/$(basename "$url")
curl -L -o "$dest" "$url"
if [ $? -eq 0 ]; then
echo "$dest"
fi
}
process_path() {
if [[ $path == http* ]]; then
if prompt_for_confirmation "Found URL: $path. Do you want to download and install the file from this URL?"; then
file=$(download_file "$path")
if [ -n "$file" ]; then
return 0
else
echo "Error downloading file."
return 1
fi
fi
else
# Remove trailing slash if it exists for local paths
local search_path="${path%/}"
if [ -d "$search_path" ]; then
for extension in dmg pkg zip; do
for f in "$search_path"/*.$extension; do
if [ -f "$f" ] && prompt_for_confirmation "Found installer: $f. Do you want to install this?"; then
file=$f
return 0
fi
done
done
elif [ -f "$search_path" ] && prompt_for_confirmation "Found file: $search_path. Do you want to install this file?"; then
file=$search_path
return 0
fi
return 1
fi
}
process_path || return 1
case $file in
*.dmg)
local listing=$(sudo hdiutil attach "$file" | grep Volumes)
local volume=$(echo "$listing" | cut -f 3)
# Simulate a breadth-first search
for depth in 1 2; do
local appfile=$(find_pkg_or_app "$volume" $depth)
if [ -n "$appfile" ]; then
handle_installation "$appfile" "$volume"
break
fi
done
;;
*.pkg)
open -W "$file"
;;
*.zip)
unzip "$file" -d "$zipTempd"
for depth in 1 2; do
local appfile=$(find_pkg_or_app "$zipTempd" $depth)
if [ -n "$appfile" ]; then
handle_installation "$appfile" ""
break
fi
done
;;
esac
# echo "download: $downloadTempd"
# echo "zip: $zipTempd"
[ -n "$downloadTempd" ] && rm -rf "$downloadTempd"
[ -n "$zipTempd" ] && rm -rf "$zipTempd"
unset path quiet downloadTempd zipTempd file listing volume depth appfile
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment