Major overhaul

After working for ~6 years in private repositories, bringing my
updated BASH scripting templates back into the world.
This commit is contained in:
Nathaniel Landau
2021-07-13 17:03:27 -04:00
parent e428d2d1fd
commit b227cf6330
38 changed files with 4003 additions and 6188 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Nathaniel Landau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

235
README.md
View File

@@ -1,70 +1,199 @@
# Shell Scripting Templates and Utilities
A collection of shell scripting utilities and templates used to ease the creation of BASH scripts.
**As of 2017, this repository is in archival state.** I refactored much of the code and am now using the scripts and utilities available publicly in this repository: [https://github.com/natelandau/dotfiles](https://github.com/natelandau/dotfiles).
## Bash Script Template Usage
To create a new script, copy `scriptTemplate.sh` to a new file and make it executable `chmod 755 [newscript].sh`. Place your custom script logic within the `_mainScript_` function at the top of the script.
### Script Template Usage
Default flags included in the base template are:
* `-h`: Prints the contents of the `_usage_` function. Edit the text in that function to provide help
* `-l [level]`: Log level of the script. One of: `FATAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `ALL`, `OFF` (Default is '`ERROR`')
* `-n`: Dryrun, sets `$DRYRUN` to `true` allowing you to write functions that will work non-destructively using the `_execute_` function
* `-v`: Sets `$VERBOSE` to `true` and prints all debug messages to stdout
* `-q`: Runs in quiet mode, suppressing all output to stdout. Will still write to log files
* `--force`: If using the `_seekConfirmation_` utility function, this skips all user interaction. Implied `Yes` to all confirmations.
# My Shell Scripts
This is the centralized repository of all the shell scripts which I use for a number of different purposes.
You can add custom script options and flags to the `_parseOptions_` function.
**Important:** *I am a novice programmer and I bear no responsibility whatsoever if any of these scripts that I have written wipes your computer, destroys your data, crashes your car, or otherwise causes mayhem and destruction. USE AT YOUR OWN RISK.*
### Script Template Functions
scriptTemplate.sh includes some helper functions to perform common tasks.
## What's here
* `_alert_` Provides alerting and logging functionality. See notes below.
* `_trapCleanup_` Cleans up files on error
* `_makeTempDir_` Creates a temp directory to house temporary files
* `_acquireScriptLock_` Acquires script lock to avoid race conditions on files
* `_functionStack_` Prints the function stack in use to aid debugging
* `_parseOptions_` Parse options and take user input (`-a`, `--some-flag`, and `--some-file [filename]` supported)
* `_usage_` Prints help text when `-h` passed
* **etc/** - Many of my scripts and shared functions call for configuration files. These configs are saved here.
* **lib/** - Shared functions and libraries that are used throughout the scripts. Note all my scripts require these files and will break if they can not be found.
* **setupScripts/** - Scripts that configure new Mac computers from scratch. These scripts perform such tasks as:
* Insalling [Homebrew][1] & associated packages
* Installing mac applications using [Homebrew Cask][2]
* Configuring OSX to my liking
* Syncing user preferences and files using [Mackup][3]
* Installing [RVM][4] and associated Gems
* Pushing a new SSH key to Github
* **syncScripts/** - Scripts which use [RSYNC][5] and [Unison][6] to keep different directories and computers in sync.
* **scriptTemplate.sh** - A bash script boilerplate template.
### Script Initialization
The bottom of the script template file contains a block which initializes the script. Comment, uncomment, or change the settings here for your needs
## Versioning
```bash
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' \
EXIT INT TERM SIGINT SIGQUIT
set -o errtrace # Trap errors in subshells and functions
set -o errexit # Exit on error. Append '||true' if you expect an error
set -o pipefail # Use last non-zero exit code in a pipeline
# shopt -s nullglob globstar # Make `for f in *.txt` work when `*.txt` matches zero files
IFS=$' \n\t' # Set IFS to preferred implementation
# set -o xtrace # Run in debug mode
set -o nounset # Disallow expansion of unset variables
# [[ $# -eq 0 ]] && _parseOptions_ "-h" # Force arguments when invoking the script
_parseOptions_ "$@" # Parse arguments passed to script
# _makeTempDir_ "$(basename "$0")" # Create a temp directory '$tmpDir'
# _acquireScriptLock_ # Acquire script lock
_mainScript_ # Run the main logic script
_safeExit_ # Exit cleanly
```
This project implements the [Semantic Versioning][7] guidelines.
# Utility Files
The files within `utilities/` contain BASH functions which can be used in your scripts. Each included function includes detailed usage information. Read the code for instructions.
Releases will be numbered with the following format:
## Including Utility Functions
Within the `utilities` folder are many BASH functions meant to ease development of more complicated scripts. These can be included in the template in two ways.
`<major>.<minor>.<patch>`
#### 1. Copy and Paste
You can copy any complete function from the Utilities and place it into your script. Copy it beneath the end of `_mainscript_()`
And constructed with the following guidelines:
#### 2. Source entire utility files
You can source entire utility files by pasting the following snippet into your script beneath `_mainScript_()`. Be sure to replace `[PATH_TO]` with the full path to this repository.
* Breaking backward compatibility bumps the major (and resets the minor and patch)
* New additions without breaking backward compatibility bumps the minor (and resets the patch)
* Bug fixes and misc changes bumps the patch
```bash
_sourceHelperFiles_() {
# DESC: Sources script helper files.
local filesToSource
local sourceFile
filesToSource=(
"[PATH_TO]/shell-scripting-templates/utilities/baseHelpers.bash"
"[PATH_TO]/shell-scripting-templates/utilities/arrays.bash"
"[PATH_TO]/shell-scripting-templates/utilities/files.bash"
"[PATH_TO]/shell-scripting-templates/utilities/macOS.bash"
"[PATH_TO]/shell-scripting-templates/utilities/numbers.bash"
"[PATH_TO]/shell-scripting-templates/utilities/services.bash"
"[PATH_TO]/shell-scripting-templates/utilities/textProcessing.bash"
"[PATH_TO]/shell-scripting-templates/utilities/dates.bash"
)
for sourceFile in "${filesToSource[@]}"; do
[ ! -f "${sourceFile}" ] \
&& {
echo "error: Can not find sourcefile '${sourceFile}'."
echo "exiting..."
exit 1
}
source "${sourceFile}"
done
}
_sourceHelperFiles_
```
[Here's more information on semantic versioning.][7]
## alerts.bash
Basic alerting and setting colors functions (included in scriptTemplate.sh by default). Print messages to stdout and to a user specified logfile using the following functions.
```bash
debug "some text" # Printed only when in Verbose mode
info "some text" # Basic informational messages
notice "some text" # Messages which should be read. Brighter than 'info'
warning "some text" # Non-critical warnings
error "some text" # Error state warnings. (Does not stop the script)
fatal "some text" # Fatal errors. Exits the script
success "some text" # Prints a success message
header "some text" # Prints a header element
```
## arrays.bash
Common functions for working with BASH arrays.
* `_inArray_` Determine if a value is in an array
* `_join_` Joins items together with a user specified separator
* `_setdiff_` Return items that exist in ARRAY1 that are do not exist in ARRAY2
* `_removeDupes_` Removes duplicate array elements
* `_randomArrayElement_` Selects a random item from an array
## baseHelpers.bash
Commonly used functions in many scripts
* `_execute_` Executes commands with safety and logging options. Respects `DRYRUN` and `VERBOSE` flags.
* `_findBaseDir_` Locates the real directory of the script being run. Similar to GNU readlink -n
* `_checkBinary_` Check if a binary exists in the search PATH
* `_haveFunction_` Tests if a function exists
* `_pauseScript_` Pause a script at any point and continue after user input
* `_progressBar_` Prints a progress bar within a for/while loop
* `_seekConfirmation_` Seek user input for yes/no question
* `_setPATH_` Add directories to $PATH so script can find executables
## csv.bash
Functions to write to a CSV file.
* `_makeCSV_` Creates a new CSV file if one does not already exist
* `_writeCSV_` Takes passed arguments and writes them as a comma separated line
## dates.bash
Common utilities for working with dates in BASH scripts.
* `_monthToNumber_` Convert a month name to a number
* `_numberToMonth_` Convert a month number to its name
* `_parseDate_` Takes a string as input and attempts to find a date within it to parse into component parts (day, month, year)
* `_formatDate_` Reformats dates into user specified formats
## files.bash
Common utilities for working with files.
* `_listFiles_` Find files in a directory. Use either glob or regex.
* `_backupFile_` Creates a backup of a specified file with .bak extension or optionally to a specified directory.
* `_cleanFilename_` Cleans a filename of all non-alphanumeric (or user specified) characters and overwrites original
* `_parseFilename_` Break a filename into its component parts which and place them into prefixed variables (dir, basename, extension, full path, etc.)
* `_decryptFile_` Decrypts a file with `openssl`
* `_encryptFile_` Encrypts a file with `openssl`
* `_ext_` Extract the extension from a filename
* `_extract_` Extract a compressed file
* `_json2yaml_` Convert JSON to YAML uses python
* `_makeSymlink_` Creates a symlink and backs up a file which may be overwritten by the new symlink. If the exact same symlink already exists, nothing is done.
* `_parseYAML_` Convert a YANML file into BASH variables for use in a shell script
* `_readFile_` Prints each line of a file
* `_sourceFile_` Source a file into a script
* `_uniqueFileName_` Ensure a file to be created has a unique filename to avoid overwriting other files
* `_yaml2json_` Convert a YAML file to JSON with python
## macOS.bash
Functions useful when writing scripts to be run on macOS
* `_haveScriptableFinder_` Determine whether we can script the Finder or not
* `_guiInput_` Ask for user input using a Mac dialog box
## numbers.bash
Helpers to work with numbers
* `_fromSeconds_` Convert seconds to HH:MM:SS
* `_toSeconds_` Converts HH:MM:SS to seconds
* `_countdown_` Sleep for a specified amount of time
## services.bash
Functions to work with external services
* `_haveInternet_` Tests to see if there is an active Internet connection
* `_httpStatus_` Report the HTTP status of a specified URL
* `_pushover_` Sends a notification via Pushover (Requires API keys)
## testProcessing.bash
Work with strings in your script
* `_cleanString_` Cleans a string of text
* `_stopWords_` Removes common stopwords from a string. Requires a sed stopwords file. Customize to your needs.
* `_escape_` Escapes a string by adding `\` before special chars
* `_htmlDecode_` Decode HTML characters with sed. (Requires sed file)
* `_htmlEncode_` Encode HTML characters with sed (Requires sed file)
* `_lower_` Convert a string to lowercase
* `_upper_` Convert a string to uppercase
* `_ltrim_` Removes all leading whitespace (from the left)
* `_regex_` Use regex to validate and parse strings
* `_rtrim_` Removes all leading whitespace (from the right)
* `_trim_` Removes all leading/trailing whitespace
* `_urlEncode_` URL encode a string
* `_urlDecode_` Decode a URL encoded string
## A Note on Code Reuse
The scripts herein were created by me over many years without ever having the intention to make them public. As a novice programmer, I have Googled, GitHubbed, and StackExchanged a path to solve my own scripting needs. Quite often I would lift a function whole-cloth from a GitHub repo and not keep track of it's original location. I have done my best within the scripts to recreate my footsteps and give credit to the original creators of the code when possible. Unfortunately, I fear that I missed as many as I found. My goal of making these scripts public is not to take credit for the wonderful code written by others. If you recognize or wrote something here that I didn't credit, please let me know.
I compiled these scripting utilities over many years without having an intention to make them public. As a novice programmer, I have Googled, GitHubbed, and StackExchanged a path to solve my own scripting needs. I often lift a function whole-cloth from a GitHub repo don't keep track of its original location. I have done my best within these files to recreate my footsteps and give credit to the original creators of the code when possible. Unfortunately, I fear that I missed as many as I found. My goal in making this repository public is not to take credit for the code written by others. If you recognize something that I didn't credit, please let me know.
# scriptTemplate.sh
This is a bash script boilerplate template that I use to create all my scripts. It takes care of a number of items for you including:
* Sourcing associated libraries and functions
* Gracefully trapping exits
* Creating and cleaning up temporary directories
* Writing logs
* Checking for dependecies (i.e. - ffmpeg or rsync)
* Passing options from the command line
* Printing usage information when `-h` is passed
## Usage
To use the bash boilerplate for your own script follow these steps.
1. Make a copy of `scriptTemplate.sh`.
2. Ensure that the new script can find the utilities located in `lib/utils.sh` by updating the path of variable `utilsLocation` near the top.
3. Ensure your script is executable (i.e. - run `chmod a+x scriptname`)
4. Add your script within the function titled `mainScript`
[1]: http://brew.sh
[2]: http://caskroom.io
[3]: https://github.com/lra/mackup
[4]: https://rvm.io
[5]: http://en.wikipedia.org/wiki/Rsync
[6]: http://www.cis.upenn.edu/~bcpierce/unison/
[7]: http://semver.org
[8]: http://www.controlplaneapp.com/
## License
MIT

5
bin/.gitignore vendored
View File

@@ -1,5 +0,0 @@
*
!.gitignore
!convertMedia
!README.md
!trash

View File

@@ -1,38 +0,0 @@
This directory contains executable scripts which are run on a day to day basis. To invoke these scripts from anywhere be sure to [add this directory to your PATH][1]
# convertMedia Script
convertMedia automates a number media commands by invoking the correct settings from [ffmpeg][2] and (optionally) [XLD][3]. This script can save a ton of time searching for the correct formats, encoders, and options withinn ffmpeg.
For Mac users, if you don't have ffmpeg installed, this script will install it with all necessary options using [Homebrew][4]
Brief overview of features:
* Resizing video
* Converting formats (for example: FLAC to ALAC or WMV to MP4)
* Changing bit rates on audio files
* Performing actions on a single file, an entire directory, or only on files which match a certain format.
### Examples of usage
Search for all *.flac files in a directory and convert them to
Apple Lossless (alac). Once the conversion is complete, original files
will be deleted.
`$ convertMedia -i flac -o alac --delete`
Search for all 1080p files in a directory and downsize them to 720p.
`$ convertMedia --downsize720`
Convert a Windows Media file (file.wmv) to h264 (mp4).
`$ convertMedia -o mp4 file.wmv`
I did my best to write good help documentation so simply run `convertMedia -h` for usage information.
[1]: http://unix.stackexchange.com/questions/26047/how-to-correctly-add-a-path-to-path
[2]: https://ffmpeg.org
[3]: http://tmkk.undo.jp/xld/index_e.html
[4]: http://brew.sh

View File

@@ -1,965 +0,0 @@
#!/usr/bin/env bash
# ##################################################
# My Generic BASH script template
#
version="2.2.0" # Sets version variable for this script
#
scriptTemplateVersion="1.5.0" # Version of scriptTemplate.sh that this script is based on
#
#
# HISTORY:
#
# * 2015-03-31 - v1.0.0 - First creation
# * 2015-04-07 - v1.1.0 - Added support for music files
# * 2015-05-26 - v1.1.1 - Fixed log level on downsize720
# * 2015-08-30 - v1.2.0 - Support for recursive directory conversions
# * 2016-01-03 - v1.2.1 - Fixed XLD audio conversion which had been broken
# * 2016-01-05 - v2.0.0 - Major refactoring of code.
# - Better JSON parsing via JQ
# - MIME type discovery based on file metadata (rather than user input)
# - Better error handling
# - Better renaming of existing files
# * 2016-01-09 - v2.0.1 - Fixed bug on in video preset section where 'videoPreset' was malformed
# * 2016-01-09 - v2.1.0 - Support for audiobooks with .m4b
# - Support for concatenating multiple audio files
# - Support for --probe function to output ffprobe data in JSON
# * 2016-01-13 - v2.1.1 - Fixed bug with concatenating files
# * 2016-01-14 - v2.2.0 - Added 'spoken word' audio profiles
#
# ##################################################
# Provide a variable with the location of this script.
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Source Scripting Utilities
# -----------------------------------
# These shared utilities provide many functions which are needed to provide
# the functionality in this boilerplate. This script will fail if they can
# not be found.
# -----------------------------------
utilsLocation="${scriptPath}/../lib/utils.sh" # Update this path to find the utilities.
if [ -f "${utilsLocation}" ]; then
source "${utilsLocation}"
else
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
exit 1
fi
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
function trapCleanup() {
echo ""
# Delete temp files, if any
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
fi
die "Exit trapped." # Edit this if you like.
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=false
printLog=false
verbose=false
recursive=false
force=false
strict=false
debug=false
safeRun=0
downsize720=0
deleteOriginal=false
newFileFlag=false
videoFoundFlag=false
audioFoundFlag=false
spokenWord=false
probe=false
concat=false
XLD=0
args=()
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.${RANDOM}.${RANDOM}.${RANDOM}.${$}"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="${HOME}/Library/Logs/${scriptBasename}.log"
# Check for Dependencies
# -----------------------------------
# Arrays containing package dependencies needed to execute this script.
# The script will fail if dependencies are not installed. For Mac users,
# most dependencies can be installed automatically using the package
# manager 'Homebrew'. Mac applications will be installed using
# Homebrew Casks. Ruby and gems via RVM.
# -----------------------------------
homebrewDependencies=(ffmpeg jq rename)
caskDependencies=(xld)
gemDependencies=()
function mainScript() {
############## Begin Script Here ###################
####################################################
# File extension mappings - ie - are we working with a video or music file?
videoTypes=(mp4 mov avi mkv wmv flv ogg m4p m4v 3gp divx h264)
audioTypes=(mp3 m4a aiff aac m4p wav wma flac m4b)
function identifyFiles() {
local item
local maxSearchDepth
local fileType
local fileMatch
# Assign user specified files
if [ -n "${userFile}" ]; then
filesToConvert+=("${userFile}")
fi
if [ -n "${args}" ]; then
filesToConvert+=("${args[@]}")
fi
# Respect recursive file searches when requested
if ${recursive}; then
maxSearchDepth=4
else
maxSearchDepth=1
fi
# Assign files based on MEDIATYPE extension
if [ -n "${MEDIATYPE}" ]; then
while read item; do
filesToConvert+=("${item}")
done < <(find . -name "*.${MEDIATYPE}" -type f -maxdepth ${maxSearchDepth})
fi
# Figure out what to do if a user doesn't specify any files or input types
if [ ${#filesToConvert[@]} -eq 0 ]; then
notice "You didn't specify a specific file."
seek_confirmation "Do you want to convert all VIDEO files?"
if is_confirmed; then
for fileType in "${videoTypes[@]}"; do
while read fileMatch; do
filesToConvert+=("${fileMatch}")
done < <(find . -name "*.${fileType}" -type f -maxdepth ${maxSearchDepth})
done
else
seek_confirmation "Do you want to convert all AUDIO files?"
if is_confirmed; then
for fileType in "${audioTypes[@]}"; do
while read fileMatch; do
filesToConvert+=("${fileMatch}")
done < <(find . -name "*.${fileType}" -type f -maxdepth ${maxSearchDepth})
done
fi
fi
fi
# Error handling: Ensure the count of elements in ${filesToConvert[@]} is not zero.
if [ ${#filesToConvert[@]} -eq 0 ]; then
error "We couldn't find any files to act on. Exiting."
safeExit
fi
# debug
verbose "filesToConvert=${filesToConvert[@]}"
}
function testFiles() {
# This function ensures that the specified files exist and can be converted
local extension
# Ensure we have a valid file
if [ ! -f "${fileToTest}" ]; then die "We can't find '${fileToTest}' or it is not valid."; fi
# Ensure the extension is known to this script
extension="${fileToTest##*.}" # Grab file extension of input file
extension="$(echo "${extension}" | tr '[:upper:]' '[:lower:]')" # Lowercase the extension if needed
# See if the extension is in the specified extension mappings
if [[ ! "${videoTypes[*]}" =~ ${extension} ]] && [[ ! "${audioTypes[*]}" =~ ${extension} ]]; then
error "The extension of '${extension} ' was not recognized."
die "We don't know what to do with ${fileToTest}."
fi
}
function convertToFormat() {
# Do things on video files
if [[ "${videoTypes[*]}" =~ ${file##*.} ]]; then
# if you wanted a default target format for a specific input format,
# you would put it here.
# Set the default video conversion format to mp4
case "${format}" in
'Matroska / WebM') outputFormat='mp4' ;;
*) outputFormat='mp4' ;;
esac
fi
# Do things on audio files
if [[ "${audioTypes[*]}" =~ ${file##*.} ]]; then
# Ensure a user sets an output format since we don't have a default.
if [[ -z ${userOutput} ]]; then
warning "Please specify an output audio format using '-o, --output'. Exiting"
safeExit
fi
# Confirm if a user wants to convert audio to it's own format
# if [[ "${file##*.}" == "${userOutput}" ]]; then
# warning "You are attempting to convert a ${file##*.} file to ${userOutput}."
# seek_confirmation "Do you want to proceed?"
# if is_not_confirmed; then
# continue
# fi
# fi
fi
# Reads user input for format (-o, --output)
if [ -n "${userOutput}" ]; then
outputFormat="${userOutput,,}" #&& verbose "outputFormat=${outputFormat}"
fi
}
function concatFiles() {
if ${concat}; then
# Create variable for ffmpeg to concacenate the files
concatConvert="concat:$(join "|" "${filesToConvert[@]}")"
# Create the variable for the deleteOriginalFile function
concatDelete="$(join " " ${filesToConvert[@]})"
# Ask the user for the name of the newly created file
input "Please enter the name of the new file to be created. [ENTER]: "
read concatOutput
fi
}
function parseJSON() {
local ii
local videoFoundFlag
local audioFoundFlag
local informationFile
# Create the temporary JSON file
informationFile="${tmpDir}/${file////}.json"
verbose "Reading audio data and writing JSON to tmp"
verbose "-> ${informationFile}"
# Output a JSON file for each video asset being parsed.
ffprobe -v quiet -print_format json -show_format -show_streams "${file}" >> "${informationFile}"
# Debugging: Uncomment either (or both) of these lines
# ffprobe -v quiet -print_format json -show_format -show_streams "${file}" >> "${file}.json"
# cat "${informationFile}"
# If the '--probe' flag is set, we print the media file information onto the screen.
if "${probe}"; then
cat "${informationFile}"
return 0
fi
# Read the necessary information from the JSON
format="$(jq -r ".format.format_long_name" "${informationFile}")"
formatName="$(jq -r ".format.format_name" "${informationFile}")"
jsonFilename="$(jq -r ".format.filename" "${informationFile}")"
formatBit_Rate="$(jq -r ".format.bit_rate" "${informationFile}")"
numStreams="$(jq -r ".format.nb_streams" "${informationFile}")"
# Iterate over results
ii=0
while [ ${ii} -lt ${numStreams} ]; do
if [[ $(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_type" "${informationFile}") == "video" ]]; then
videoHeight="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].height" "${informationFile}")"
videoWidth="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].width" "${informationFile}")"
videoCodec="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_name" "${informationFile}")"
videoCodecLong="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_long_name" "${informationFile}")"
videoFoundFlag=true # Used for error handling to confirm we found a video stream
fi
if [ $(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_type" "${informationFile}") == "audio" ]; then
audioCodec="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_name" "${informationFile}")"
audioCodecLong="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_long_name" "${informationFile}")"
audioSampleRate="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].sample_rate" "${informationFile}")"
audioBitRate="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].bit_rate" "${informationFile}")"
audioFoundFlag=true # Used for error handling to confirm we found an audio stream
fi
# Increase loop count
ii=$((ii+1))
done
# Error Handling for video files
if [[ "${videoTypes[*]}" =~ ${file##*.} ]]; then
if [ "${videoFoundFlag}" = "false" ]; then
warning "Missing video stream for '${file}'."
seek_confirmation "Inspect the file with ffprobe?"
if is_confirmed; then
ffprobe -v quiet -print_format json -show_format -show_streams "${file}"
safeExit
else
notice "Exiting"
safeExit
fi
fi
fi
# Error Handling for audio files
if [[ "${audioTypes[*]}" =~ ${file##*.} ]]; then
if [ "${audioFoundFlag}" = "false" ]; then
warning "Missing audio stream for '${file}'."
seek_confirmation "Inspect the file with ffprobe?"
if is_confirmed; then
ffprobe -v quiet -print_format json -show_format -show_streams "${file}"
safeExit
else
notice "Exiting"
safeExit
fi
fi
fi
}
function convertVideo() {
verbose "Running 'convertVideo' function"
# SET AUDIO INFORMATION
# Copy audio in compatible formats. Re-encode audio when needed
################################################################
# Pick the best aac audio encoder
if ffmpeg -version | grep enable-libfdk-aac >/dev/null; then
aacEncoder="libfdk_aac"
else
aacEncoder="libfaac"
fi
supportedAudioCodecs=(aac ac3 eac3)
if [[ "${supportedAudioCodecs[*]}" =~ ${audioCodec} ]]; then
videoAudioCommand="-c:a copy"
else
videoAudioCommand="-c:a ${aacEncoder} -b:a 160k"
fi
# SET VIDEO INFORMATION
# Copy video in compatible formats. Re-encode audio when needed.
# Set resizing options
################################################################
# Is input video a known preset size?
if [[ "${videoWidth}" == "1920" && "${videoHeight}" == "1080" ]] || [[ "${videoWidth}" == "1920" && "${videoHeight}" == "816" ]]; then
videoPreset="1080p" && verbose "Input video has preset: 1080p"
fi
if [[ "${videoWidth}" == "1280" && "${videoHeight}" == "720" ]] || [[ "${videoWidth}" == "1280" && "$videoHeight" == "544" ]]; then
videoPreset="720p" && verbose "Input video has preset: 720p"
fi
if [[ "${videoWidth}" == "720" && "${videoHeight}" == "576" ]]; then
videoPreset="DVPAL" && verbose "Input video has preset: DVPAL"
fi
# Enable resizing of videos
# #############################
# Fail if user sets more than one value
if [[ -n "${videoSize}" ]] && [[ -n "${height}" || -n "${width}" ]]; then
die "We can't set a 'height', 'width', and a 'size' at the same time."
fi
# if user sets both a height and width, run that as a videoSize variable
if [[ -n "${width}" ]] && [[ -n "${height}" ]]; then
videoSize="${width}x${height}" && verbose "videoSize=${width}x${height}"
unset width
unset height
fi
# downsize720
# Commonly used function to downsize 1080p to 720p
if [[ "${downsize720}" == "1" ]]; then
if [[ "${videoPreset}" == "1080p" ]]; then
videoSize="hd720" && verbose "videoSize=hd720"
else
notice "Skipping ${file}. It's not 1080p"
continue
fi
fi
# Do something when a user specifies a size
if [[ -n "${videoSize}" ]]; then
# Don't resize videos to their same size
if [[ "${videoSize}" == "hd720" ]] || [[ "${videoSize}" == "720" || "${videoSize}" == "1280x720" ]]; then
if [[ "$videoPreset" == "720p" ]]; then
notice "${file} is already 720p. Skipping...."
continue
fi
elif [[ "${videoSize}" == "hd1080" || "${videoSize}" == "1080" || "${videoSize}" == "1920x1080" ]]; then
if [[ "$videoPreset" == "1080p" ]]; then
notice "${file} is already 1080p. Skipping...."
continue
fi
fi
# Confirm if user wants to upscale their video
if [[ "${videoSize}" == "hd1080" || "${videoSize}" == "1080" ]]; then
userWidth="1920"
userHeight="1080"
elif [[ "${videoSize}" == "hd720" ]] || [[ "${videoSize}" == "720" ]]; then
userWidth="1280"
userHeight="720"
else
# break user's video size into a height and width
userWidth=$(echo ${videoSize} | cut -f1 -dx)
userHeight=$(echo ${videoSize} | cut -f2 -dx)
if [ "${userWidth}" -gt "${videoWidth}" ] || [ "${userHeight}" -gt "${videoHeight}" ]; then
seek_confirmation "Upscale ${file} to ${videoSize}? It is already ${videoWidth}x${videoHeight}."
if is_not_confirmed; then
continue
fi
fi
fi
# Finally, set the resize variable
videoResize="-vf scale=${videoSize}" && verbose "videoResize='-vf scale=${videoSize}'"
fi
# Scaling variables
# ####################################
if is_not_empty "${height}"; then
if [ "${height}" -gt "${videoHeight}" ]; then
seek_confirmation "Upscale ${file} to height ${height}? It is already ${videoWidth}x${videoHeight}."
if is_not_confirmed; then
continue
fi
fi
videoResize="-vf scale=-1:${height}" && verbose "videoResize='-vf scale=-1:${height}'"
fi
if is_not_empty "${width}"; then
if [ "${width}" -gt "${videoWidth}" ]; then
seek_confirmation "Upscale ${file} to width ${width}? It is already ${videoWidth}x${videoHeight}."
if is_not_confirmed; then
continue
fi
fi
videoResize="-vf scale=${width}:-1" && verbose "videoResize='-vf scale=${width}:-1'"
fi
# Copy h264 when possible
# Save precious time by not re-encoding files that are already H264.
# ###########################
if [[ "${videoCodec}" == "h264" ]] && [[ -z "${videoResize}" ]]; then
videoCommand="-c:v copy" && verbose "videoCommand='-c:v copy'"
else
videoCommand="-c:v libx264 -crf 18 -preset slow" && verbose "videoCommand'-c:v libx264 -crf 18 -preset slow'"
fi
# Don't convert to self if no other options set
if [[ -z ${height} && -z ${width} && -z ${videoSize} && ${downsize720} == "0" && "${outputFormat}" == "${file##*.}" ]]; then
error "Can't convert a '${file##*.}' file to itself. Skipping ${file}"
continue
fi
}
function convertAudio() {
verbose "Running 'convertAudio' function"
# note on XLD: If you have XLD installed and configured, lossless audio conversion
# will be run using built-in XLD profiles. You can disable this by
# changing ensuring that XLD=0 in sections below.
#
# #############################################################
# Build the Conversion Command
# ########################################
# Set mono/64k conversion for audiobooks
if ${spokenWord}; then
monoConversion="-ab 96k -ac 1"
fi
if [[ "${userOutput,,}" == "alac" ]]; then
if type_exists "xld"; then
XLD=1
#audioConvertCommand="--profile FLACtoALAC"
audioConvertCommand="-f alac"
outputFormat="m4a"
else
audioConvertCommand="-acodec alac"
fi
elif [[ "${userOutput,,}" == "flac" ]]; then
if type_exists "xld"; then
XLD=1
audioConvertCommand="-f flac"
else
audioConvertCommand="-c:a flac ${monoConversion}"
fi
elif [[ "${userOutput,,}" == "aac" || "${userOutput,,}" == "m4a" ]]; then
outputFormat="m4a"
# Pick the best aac audio encoder
if ffmpeg -version | grep enable-libfdk-aac >/dev/null; then
# set variable bit rate to '5', the highest unless we are doing spoken word
if ${spokenWord}; then
aacEncoder='libfdk_aac'
else
aacEncoder='libfdk_aac -vbr 5'
fi
else
aacEncoder='libfaac -q:a 400'
fi
if type_exists "xlds"; then
XLD=1
audioConvertCommand="-f aac" && verbose "using xld. audioConvertCommand = -f aac "
else
audioConvertCommand="-acodec ${aacEncoder} ${monoConversion}"
fi
elif [[ "${userOutput,,}" == "mp3" ]]; then
# Can we convert to mp3? Do we have an ffmpeg encoder?
if ffmpeg -version | grep enable-libmp3lame >/dev/null; then
mp3Encoder='libmp3lame'
# else
# warning "No workable ffmpeg mp3 encoder. Skipping ${f}..."
# continue
fi
# Take user specified bitrate
if [ -n "$bitrate" ]; then
bitrate="${bitrate%k}k" # Ensure 'k' is at the end of any bitrate sent to ffmpeg
ffmpegBitrate="-ab ${bitrate}"
else
ffmpegBitrate="-qscale:a 0"
fi
# Set mono conversion for audiobooks
if ${spokenWord}; then
ffmpegBitrate="${monoConversion}"
fi
audioConvertCommand="-acodec ${mp3Encoder} ${ffmpegBitrate} -map_metadata 0 -id3v2_version 3"
elif [[ "${userOutput,,}" == "m4b" ]]; then
# m4b is exactly the same as m4a except it tells Apple that the file is an audiobook.
# so we use m4a conversion here and then rename the output file to m4b
# The main difference here is that we make the assumption that audiobooks don't
# need high fidelity or stereo so we make them mono and low bit-rate.
# Pick the best aac audio encoder
if ffmpeg -version | grep enable-libfdk-aac >/dev/null; then
# set variable bit rate to '5', the highest
aacEncoder="libfdk_aac ${monoConversion} -f m4a"
else
aacEncoder="libfaac ${monoConversion} -f m4a"
fi
audioConvertCommand="-acodec ${aacEncoder}"
else
warning "We don't know what to do with audio format: '${outputFormat}'."
warning "Exiting"
safeExit
fi
}
function setOutputDirectory() {
if ${verbose}; then v="-v" ; fi
# Use the user's specified directory to save the new file in if specified.
# default to use the location of the original file.
if [[ -n "${saveDir}" ]]; then
if [[ -e "${saveDir}" && ! -d "${saveDir}" ]]; then
die "${saveDir} exists but is not a directory"
fi
if [[ ! -d "${saveDir}" ]]; then
seek_confirmation "${saveDir} does not exist. Create?"
if is_confirmed; then
mkdir ${v} "${saveDir}"
else
die "Can't run without a place to save the files."
fi
fi
# remove trailing slash if included. Add back to ensure it's always there.
outputDir="${saveDir%/}/"
else
outputDir=$(dirname "${file}")
outputDir="${outputDir#*/}"
outputDir="${outputDir%/}/"
fi
}
function setOutputFile() {
# This function creates the name of new file to be generated by the conversion
# ##################
# Set output filename
output="$(basename "${file%.*}").${outputFormat}"
# Add the output directory
output="${outputDir}${output}"
#Override the output file to the user's input if we are concatenating files
if [ -n "$concatOutput" ]; then
output="${outputDir}${concatOutput%.*}.${outputFormat}"
success "output=${output}"
fi
# Confirm we're not overwriting an existing file
# If we are, append '.new' to the name
fileFlag=0
while [[ fileFlag -eq 0 ]]; do
if [ -e "${output}" ]; then
# output="$(basename "${file%.*}").new."${outputFormat}""
output="$(basename "${output%.*}").new.${outputFormat}"
# Add the directory back to the file
output="${outputDir}${output}"
# Set a flag so that we can rename this file back to the original name if
# a user elects to delete the original file
newFileFlag=true
else
fileFlag=1
fi
done
verbose "new file name will be: ${output}"
}
function doConvert() {
verbose "running doConvert function"
if ${verbose}; then v="-v" ; fi
# Respect the 'Quiet' flag
if "${quiet}"; then
verbose "running in quiet mode"
ffquiet="-loglevel quiet"
fi
# When concatenating files, we need a different $file variable
if ${concat}; then
file="${concatConvert}"
fi
# Respect the 'logfile' flag
if ${printLog}; then ffmpegLog=">> ${logFile}"; fi
# Use XLD for audio file conversion if available
if [[ "${XLD}" == "1" && "${spokenWord}" == "false" ]]; then
verbose "Running XLD commands for audio. No FFMPEG."
# Respect --safe flag.
if [[ "${safeRun}" == "1" ]]; then
notice "xld -o "${output} ${audioConvertCommand} ${file}
else
verbose "xld -o ${output} ${audioConvertCommand} ${file}"
xld -o "${output}" "${audioConvertCommand}" "${file}"
fi
else # Use ffmpeg when XLD is set to 0
# Respect --safe flag.
if [[ "${safeRun}" == "1" ]]; then
notice "ffmpeg -i ${file} ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} ${output} ${ffquiet}"
else
verbose "ffmpeg -i ${file} ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} ${output} ${ffquiet}"
ffmpeg -i "${file}" ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} "${output}" ${ffquiet}
fi
fi
# Unset variables to get ready for the next file
unset jsonFilename
unset formatBit_Rate
unset numStreams
unset videoCodec
unset videoCodecLong
unset format
unset formatName
unset videoHeight
unset videoWidth
unset videoPreset
unset audioCodec
unset audioCodecLong
unset audioSampleRate
unset audioBitRate
}
function deleteOriginalFile() {
if ${verbose}; then v="-v" ; fi
# first, ensure we don't delete the originals if we're in a safe rune
if [[ "${safeRun}" == "0" ]]; then
if ${deleteOriginal}; then
if ${concat}; then
for fileToDelete in "${filesToConvert[@]}"; do
rm -f ${v} "${fileToDelete}"
done
else
rm -f ${v} "${file}"
if [[ "${file##*.}" == "${outputFormat}" ]]; then
mv ${v} "${output}" "${outputDir}${file}"
fi
fi
fi
fi
# break the loop if we're concatenating files.
if ${concat}; then
break
fi
}
## RUN THE SCRIPT ##
####################################
# First we need to identify the files
identifyFiles
# Then we test that all files can be operated upon
for fileToTest in "${filesToConvert[@]}"; do testFiles; done
# Then we respect the '--probe' option if requested
if "${probe}"; then
for file in "${filesToConvert[@]}"; do
info "Probing ${file}"
verbose "ffprobe -v quiet -print_format json -show_format -show_streams ${file}"
parseJSON
done
safeExit
fi
# Then we work on the individual files. This is the fun part.
for file in "${filesToConvert[@]}"; do
if ! ${concat}; then info "Working on ${file}"; fi
# First we grab the metadata of the file and assign it to variables
parseJSON
# Then we set the expected output format
convertToFormat
# Then we set the appropriate conversion commands
if [[ "${videoTypes[*]}" =~ "$(echo ${file##*.} | tr '[:upper:]' '[:lower:]')" ]]; then convertVideo; fi
if [[ "${audioTypes[*]}" =~ "$(echo ${file##*.} | tr '[:upper:]' '[:lower:]')" ]]; then convertAudio; fi
# Then we tell the script where to output the file
setOutputDirectory
# Then we check if we are supposed to concatenate the files
concatFiles
# Then we generate the name for the new file
setOutputFile
# Then we actually do the conversion
doConvert
# Then we delete the original file (if requested)
deleteOriginalFile
done
####################################################
############### End Script Here ####################
}
usage() {
# Print usage
echo -n "${scriptName} ${version} [OPTION]... [ARGUMENT]...
${bold}DESCRIPTION${reset}
This is a media conversion script which converts audio and video into many
different formats. It was written to eliminate the need to remember specific
FFMPEG commands. All conversions can be performed with ffmpeg. XLD on a mac
is used for audio conversions when available.
${BOLD}DEPENDENCIES${reset}
This script makes heavy use of shared functions contained in ${bold}lib/utils.sh${reset} which are
available as part of the same Github repository. It will fail if these are not found.
This script relies on ${bold}ffmpeg${reset} for video and audio conversion as well as ${bold}jq${reset}
for parsing JSON files. These must be installed prior to usage. If run on a
mac, the script will attempt to help you install these packages using ${bold}Homebrew${reset}.
${bold}General Options:${reset}
${bold}-h, --help${reset} Display this help and exit
${bold}-d, --debug${reset} Runs script in BASH debug mode ('set -x')
${bold}--force${reset} Skip all user interaction. Implied 'Yes' to all actions.
${bold}-l, --log${reset} Print log to file
${bold}-q, --quiet${reset} Quiet (no output)
${bold}-v, --verbose${reset} Output more information. (Items echoed to 'verbose')
${bold} --safe${reset} Runs the script without actually invoking FFMPEG. Will simply print
the FFMPEG commands to the terminal
${bold}--version${reset} Output version information and exit
${bold}--recursive${reset} Will search recursively through directories
${bold}--probe${reset} Outputs file metadata via ffprobe in JSON format. Does no coversion.
${bold}--concat${reset} Will concatenate audio files together into a single output. Good for audiobooks.
${bold}--spoken${reset} Will convert audio files to mono, 64k. Good for audiobooks
${bold}File Options:${reset}
${bold}-f, --file${reset} Specify a specific file to take actions on.
${bold}-i, --input${reset} Specify the specific media type to search for and take action
on. (mov', 'mp4', 'mp3')
${bold}-o, --output${reset} Specify the output format for the file(s) to be converted to.
('mkv', 'mp4', 'm4a')
${bold}--delete ${reset} Delete the original file after conversion.
${bold}--saveDir${reset} Specify a folder for the converted files to be saved to. Defaults to
the directory the script is invoked in.
${bold}Video Specific Options:${reset}
For video files, if no output format is specified, the script will attempt to convert every
video file it finds in the directory into h264 .mp4 format.
${bold}--size${reset} Set the size of the target file. Can be one of 'hd1080', 'hd720',
or 'HeightxWidth' (ie. 1920x1080)
${bold}--height${reset} Set a height in pixels to scale the target file.
${bold}--width${reset} Set a width in pixels to scale the target file.
${bold}--downsize720${reset} Searches for 1080p videos and downsizes them to 720p. No need
for other scaling options. Used to reduce the size of video collections
when quality is not the primary need.
${bold}Audio Specific Options:${reset}
${bold}--bitrate${reset} Set a bit rate for audio conversions. Note, this does not
effect video conversions.
${bold}EXAMPLES:${reset}
Search for all *.flac files in a directory and convert them to
Apple Lossless (alac). Once the conversion is complete, original files
will be deleted.
$ convertMedia -i flac -o alac --delete
Search for all 1080p files in a directory and downsize them to 720p.
$ convertMedia --downsize720
Convert a Windows Media file (file.wmv) to h264 (mp4).
$ convertMedia -o mp4 file.wmv
Do a recursive search for all directories beneath the current one. In each
directory, search for .avi files and convert them to .mp4
$ convertMedia -o mp4 -i avi --recursive
Create an Apple audiobook (m4b) from all mp3 files in a directory
$ convertMedia -i mp3 -o m4b --concat
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# [[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ ${1} = -?* ]]; do
case ${1} in
-f|--file) shift; userFile="$1" ;;
-i|--input) shift; MEDIATYPE="$1" ;;
-o|--output) shift; userOutput="$1" ;;
--safe) safeRun=1 ;;
--size) shift; videoSize="$1" ;;
--height) shift; height="$1" ;;
--width) shift; width="$1" ;;
--probe) probe=true; safeRun=1 ;;
--downsize720) downsize720=1 ;;
--recursive) recursive=true ;;
--delete) deleteOriginal=true ;;
--concat) concat=true; ;;
--spoken) spokenWord=true ;;
--saveDir) shift; saveDir="$1" ;;
--bitrate) shift; bitrate="$1" ;;
-h|--help) usage >&2; safeExit ;;
--force) force=true ;;
--version) echo "$(basename $0) $version"; safeExit ;;
-v|--verbose) verbose=true ;;
-l|--log) printLog=true ;;
-q|--quiet) quiet=true ;;
-d|--debug) debug=true;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
# ############# ############# #############
# ## TIME TO RUN THE SCRIPT ##
# ## ##
# ## You shouldn't need to edit anything ##
# ## beneath this line ##
# ## ##
# ############# ############# #############
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$' \n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
# Run in debug mode, if set
if ${debug}; then set -x ; fi
# Exit on empty variable
if ${strict}; then set -o nounset ; fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Invoke the checkDependenices function to test for Bash packages. Uncomment if needed.
checkDependencies
# Run your script
mainScript
# Exit cleanly
safeExit

547
bin/trash
View File

@@ -1,547 +0,0 @@
#!/usr/bin/env bash
function mainScript() {
local user
local uid
local finder_pid
user=$(whoami)
uid=$(id -u "$user")
finder_pid=$(ps -u "$user" | grep /System/Library/CoreServices/Finder.app | grep -v grep | awk '{print $1}')
if ${verbose}; then v="-v"; fi
function have_scriptable_finder() {
# Determine whether we can script the Finder or not
# We must have a valid PID for Finder, plus we cannot be in
# `screen` (another thing that's broken)
if [[ (${finder_pid} -gt 1) && ("$STY" == "") ]]; then
true
else
false
fi
}
function realpath() {
# Convert a relative path to an absolute path.
#
# From http://github.com/morgant/realpath
#
# @param string the string to converted from a relative path to an absolute path
# @returns Outputs the absolute path to STDOUT, returns 0 if successful or 1 if
# an error (esp. path not found).
local successPath=true
local path="$1"
# make sure the string isn't empty as that implies something in further logic
if [ -z "$path" ]; then
successPath=false
else
# start with the file name (sans the trailing slash)
path="${path%/}"
# if we stripped off the trailing slash and were left with nothing, that means we're in the root directory
if [ -z "$path" ]; then
path="/"
fi
# get the basename of the file (ignoring '.' & '..', because they're really part of the path)
local file_basename="${path##*/}"
if [[ ( "$file_basename" = "." ) || ( "$file_basename" = ".." ) ]]; then
file_basename=""
fi
# extracts the directory component of the full path, if it's empty then assume '.' (the current working directory)
local directory="${path%$file_basename}"
if [ -z "$directory" ]; then
directory='.'
fi
# attempt to change to the directory
if ! cd "${directory}" &>/dev/null ; then
successPath=false
fi
if ${success}; then
# does the filename exist?
if [[ ( -n "${file_basename}" ) && ( ! -e "${file_basename}" ) ]]; then
successPath=false
fi
# get the absolute path of the current directory & change back to previous directory
local abs_path="$(pwd -P)"
cd "-" &>/dev/null
# Append base filename to absolute path
if [ "${abs_path}" = "/" ]; then
abs_path="${abs_path}${file_basename}"
else
abs_path="${abs_path}/${file_basename}"
fi
# output the absolute path
echo "${abs_path}"
fi
fi
${successPath}
}
function listTrash() {
local num_volumes
local total_blocks
local blocks
local size
num_volumes=0
total_blocks=0
notice "Listing items in Trash"
# list file contents & calculate size for user's .Trash folder
if find "/Users/${user}/.Trash" -depth 1 ! -depth 0; then
num_volumes=$(( num_volumes + 1 ))
blocks=$(du -cs "/Users/${user}/.Trash" | tail -n 1 | cut -f 1)
total_blocks=$(( total_blocks + blocks ))
fi
# list file contents & calculate size for volume-specific .Trashes folders
for file in /Volumes/*; do
if [ -d "$file" ]; then
folder="${file}/.Trashes/${uid}"
if [ -d "${folder}" ]; then
if find "${folder}" -depth 1 ! -depth 0; then
num_volumes=$(( num_volumes + 1 ))
blocks=$(du -cs "${folder}" | tail -n 1 | cut -f 1)
total_blocks=$(( total_blocks + blocks ))
fi
fi
fi
done
# convert blocks to human readable size
size=0
if (( total_blocks >= 2097152 )); then
size=$(bc <<< "scale=2; ${total_blocks} / 2097152")
size="${size}GB"
elif (( total_blocks >= 2048 )); then
size=$(bc <<< "scale=2; ${total_blocks} / 2048")
size="${size}MB"
else
size=$(bc <<< "scale=2; ${total_blocks} / 2")
size="${size}K"
fi
info "${size} across ${num_volumes} volume(s)."
}
function emptyTheTrash() {
# Determine if we can tell Finder to empty trash via AppleScript
if have_scriptable_finder; then
notice "Telling Finder to empty trash..."
if /usr/bin/osascript -e "tell application \"Finder\" to empty trash" ; then
success "Trash has been emptied"
safeExit
else
die "Unable to empty trash"
fi
# If Finder isn't scriptable, we'll manually empty the trash ourselves
else
# Confirm that the user wants to empty the trash
seek_confirmation "Are you sure you want to empty the trash (this cannot be undone)?"
if is_confirmed; then
notice "Emptying trash..."
# delete the contents of user's .Trash folder
find "/Users/${user}/.Trash" -depth 1 ! -depth 0 -print0 | xargs -0 rm $v -r
# delete the contents of the volume-specific .Trashes folders
for file in /Volumes/*; do
if [ -d "${file}" ]; then
folder="${file}/.Trashes/${uid}"
if [ -d "${folder}" ]; then
find "${folder}" -depth 1 ! -depth 0 -print0 | xargs -0 rm $v -r
fi
fi
done
success "Trash has been emptied"
fi
fi
}
function secureEmptyTheTrash() {
# determine if we can tell Finder to securely empty trash via AppleScript
if have_scriptable_finder; then
notice "Telling Finder to securely empty trash... "
if /usr/bin/osascript -e "tell application \"Finder\" to empty trash with security" ; then
success "Trash has been securely emptied."
safeExit
else
die "Could not empty trash."
fi
# if Finder isn't scriptable, we'll manually empty the trash ourselves
else
# confirm that the user wants to securely empty the trash
seek_confirmation "Are you sure you want to securely empty the trash (this REALLY cannot be undone)?"
if is_confirmed; then
# securely delete the contents of user's .Trash folder
find "/Users/${user}/.Trash" -depth 1 ! -depth 0 -print0 | xargs -0 srm $v -r
# securely delete the contents of the volume-specific .Trashes folders
for file in /Volumes/*; do
if [ -d "$file" ]; then
folder="${file}/.Trashes/${uid}"
if [ -d "${folder}" ]; then
find "${folder}" -depth 1 ! -depth 0 -print0 | xargs -0 srm $v -r
fi
fi
done
success "Trash has been securely emptied."
fi
fi
}
function trashAFile() {
# Iterate over all files passed by user
for userFile in "${args[@]}"; do
if [ ! -e "${userFile}" ]; then
warning "${userFile}: No such file or directory"
continue
fi
# determine if we'll tell Finder to trash the file via AppleScript (very easy, plus free undo
# support, but Finder must be running for the user and is DOES NOT work from within `screen`)
if have_scriptable_finder; then
# determine whether we have an absolute path name to the file or not
if [ "${userFile:0:1}" = "/" ]; then
local file="${userFile}"
else
# expand relative to absolute path
verbose "Determining absolute path for '${userFile}'... "
file="$(realpath "${userFile}")"
if [ $? -ne 0 ]; then
warning "Could not determine absolute path for '${userFile}'!"
fi
fi
verbose "Telling Finder to trash '${file}'..."
if /usr/bin/osascript -e "tell application \"Finder\" to delete POSIX file \"$file\"" &>/dev/null; then
success "'${userFile}' moved to trash"
else
warning "'${userFile}' not moved to trash"
continue
fi
# Finder isn't available for this user, so don't rely on it (we'll do all the dirty work ourselves)
else
local trash="/Users/${user}/.Trash/"
# create the trash folder if necessary
if [ ! -d "${trash}" ]; then
mkdir ${v} "${trash}"
fi
# move the file to the trash
if [ ! -e "${trash}${userFile}" ]; then
mv ${v} "${userFile}" "${trash}"
else
# determine if the filename has an extension
ext=false
case "${ }" in
*.*) ext=true ;;
esac
# keep incrementing a number to append to the filename to mimic Finder
local i=1
if $ext; then
new="${trash}${userFile%%.*} ${i}.${userFile##*.}"
else
new="${trash}${userFile} ${i}"
fi
while [ -e "${new}" ]; do
((i=$i + 1))
if ${ext}; then
new="${trash}${userFile%%.*} ${i}.${userFile##*.}"
else
new="${trash}${userFile} ${i}"
fi
done
#move the file to the trash with the new name
mv ${v} "${userFile}" "${new}"
fi
fi
done
}
if ${list}; then listTrash; safeExit; fi
if ${emptyTrash}; then emptyTheTrash; safeExit; fi
if ${secureEmpty}; then secureEmptyTheTrash; safeExit; fi
# Default behavior without flags is to delete a file
trashAFile
}
function trapCleanup() {
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
echo ""
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
die "Exit trapped."
}
function safeExit() {
# safeExit
# -----------------------------------
# Non destructive exit for when script exits naturally.
# Usage: Add this function at the end of every script.
# -----------------------------------
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
trap - INT TERM EXIT
exit
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
list=false
emptyTrash=false
secureEmpty=false
quiet=false
printLog=false
verbose=false
force=false
strict=false
debug=false
args=()
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="${HOME}/Library/Logs/${scriptBasename}.log"
# Options and Usage
# -----------------------------------
# Print usage
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
${bold}Trash${reset} allows MacOS trashing of files instead of tempting fate with ${bold}rm${reset}.
Anything deleted with Trash will be moved to the native MacOS trash folder.
This script:
- Correctly handles ${bold}trashing files on other volumes${reset}
- Uses the ${bold}same filename renaming scheme as Finder${reset} for duplicate file names
- Can ${bold}list trash contents${reset} w/disk usage summary
- ${bold}Empty trash${reset} (including securely) w/confirmation.
- Does not require Finder to be running.
${bold}Options:${reset}
-l , --list List trash contents
-e, --empty Empty trash contents
-s, --secure Secure empty trash contents
--force Skip all user interaction. Implied 'Yes' to all actions.
--log Print log to file
-q, --quiet Quiet (no output)
-v, --verbose Output more information. (Items echoed to 'verbose')
-d, --debug Runs script in BASH debug mode (set -x)
-h, --help Display this help and exit
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# -------------------------------------
# [[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-l|--list) list=true ;;
-s|--secure) secureEmpty=true ;;
-e|--empty) emptyTrash=true ;;
-v|--verbose) verbose=true ;;
--log) printLog=true ;;
-q|--quiet) quiet=true ;;
-s|--strict) strict=true;;
-d|--debug) debug=true;;
--force) force=true ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
# Logging and Colors
# -----------------------------------------------------
# Here we set the colors for our script feedback.
# Example usage: success "sometext"
#------------------------------------------------------
# Set Colors
bold=$(tput bold)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
function _alert() {
if [ "${1}" = "emergency" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "error" ] || [ "${1}" = "warning" ]; then local color="${red}"; fi
if [ "${1}" = "success" ]; then local color="${green}"; fi
if [ "${1}" = "debug" ]; then local color="${purple}"; fi
if [ "${1}" = "header" ]; then local color="${bold}""${tan}"; fi
if [ "${1}" = "input" ]; then local color="${bold}"; printLog="0"; fi
if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi
# Don't use colors on pipes or non-recognized terminals
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi
# Print to $logFile
if ${printLog}; then
echo -e "$(date +"%m-%d-%Y %r") $(printf "[%9s]" "${1}") ${_message}" >> "${logFile}";
fi
# Print to console when script is not 'quiet'
if ${quiet}; then
return
else
echo -e "$(date +"%r") ${color}$(printf "[%9s]" "${1}") ${_message}${reset}";
fi
}
function die () { local _message="${*} Exiting."; echo "$(_alert emergency)"; safeExit;}
function error () { local _message="${*}"; echo "$(_alert error)"; }
function warning () { local _message="${*}"; echo "$(_alert warning)"; }
function notice () { local _message="${*}"; echo "$(_alert notice)"; }
function info () { local _message="${*}"; echo "$(_alert info)"; }
function debug () { local _message="${*}"; echo "$(_alert debug)"; }
function success () { local _message="${*}"; echo "$(_alert success)"; }
function input() { local _message="${*}"; echo -n "$(_alert input)"; }
function header() { local _message="========== ${*} ========== "; echo "$(_alert header)"; }
# Log messages when verbose is set to "true"
verbose() { if ${verbose}; then debug "$@"; fi }
function seek_confirmation() {
# echo ""
input "$@"
if [[ "${force}" == "1" ]]; then
notice "Forcing confirmation with '--force' flag set"
else
read -p " (y/n) " -n 1
echo ""
fi
}
function is_confirmed() {
if [[ "${force}" == "1" ]]; then
return 0
else
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
return 0
fi
return 1
fi
}
function is_not_confirmed() {
if [[ "${force}" == "1" ]]; then
return 1
else
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
return 0
fi
return 1
fi
}
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$' \n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
# Run in debug mode, if set
if ${debug}; then set -x ; fi
# Exit on empty variable
if ${strict}; then set -o nounset ; fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Run your script
mainScript
# Exit cleanly
safeExit

5
etc/.gitignore vendored
View File

@@ -1,5 +0,0 @@
*
!.gitignore
!README.md
!*.sample

View File

@@ -1,15 +0,0 @@
This directory contains the configuration files needed for the scripts and functions within this repository.
# pushover.cfg.sample
To use the [Pushover][1] notification function in your scripts take the following steps.
1. If you haven't done so already, create a [Pushover][1] account and create a Pushover application.
2. Rename `pushover.cfg.sample` to `pushover.cfg`
3. Add your user API key and your application API key to this file.
[1]: https://pushover.net

View File

@@ -1,12 +0,0 @@
# PUSHOVER CONFIGURATION FILE
# -------------------------------------------------------------
# To enable the ability to send notifications from scripts
# via Pushover enter your Pushover API and USER keys below.
# Then rename this file to pushover.cfg
#
# You may obtain these credentials at https://pushover.net
#
# -------------------------------------------------------------
PUSHOVER_API_KEY="YourApplicationAPIKey"
PUSHOVER_USER_KEY="YourUserKey"

View File

@@ -1,27 +0,0 @@
This directory contains the shared libraries and functions that are required by the scripts within this repository.
# utils.sh
This script must be sourced from all my additional scripts. Contained within this are two important functions.
1. **Logging** - All scripts use the logging functions. There are nine different levels of logs. All log levels are called from within a script in the format `info "some message"`. The levels of logging are:
* **die** - Prints an error and exits the script
* **error** - prints an error and continues to run the script
* **warning** - prints a warning
* **notice** - prints a notice to the user
* **info** - prints information to the user
* **debug** - prints debug information. This output hidden unless scripts are run with the verbose (`-v`) flag
* **success** - prints success to a user
* **input** - Asks the user for input
* **header** - Prints a header to help format logs
2. **Sourcing Additional Files** - This script reads a list of additional files and sources them.
# setupScriptFunctions.sh
This script contains different functions used to install software and configure Mac computers from the scripts contained in the `setupScripts` directory.
# sharedVariables.sh
This script contains variables that can be called from any other script.
# sharedFunctions.sh
This script contains many different functions which can be used throughout different scripts. The script is well commented.

View File

@@ -1,229 +0,0 @@
#!/usr/bin/env bash
# ##################################################
# Shared bash functions used by my mac setup scripts.
#
# VERSION 1.0.0
#
# HISTORY
#
# * 2015-01-02 - v1.0.0 - First Creation
#
# ##################################################
# hasHomebrew
# ------------------------------------------------------
# This function checks for Homebrew being installed.
# If it is not found, we install it and its prerequisites
# ------------------------------------------------------
hasHomebrew () {
# Check for Homebrew
#verbose "Checking homebrew install"
if type_not_exists 'brew'; then
warning "No Homebrew. Gots to install it..."
seek_confirmation "Install Homebrew?"
if is_confirmed; then
# Ensure that we can actually, like, compile anything.
if [[ ! "$(type -P gcc)" && "$OSTYPE" =~ ^darwin ]]; then
notice "XCode or the Command Line Tools for XCode must be installed first."
seek_confirmation "Install Command Line Tools from here?"
if is_confirmed; then
xcode-select --install
else
die "Please come back after Command Line Tools are installed."
fi
fi
# Check for Git
if type_not_exists 'git'; then
die "Git should be installed. It isn't."
fi
# Install Homebrew
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew tap homebrew/dupes
brew tap homebrew/versions
brew tap argon/mas
else
die "Without Homebrew installed we won't get very far."
fi
fi
}
# brewMaintenance
# ------------------------------------------------------
# Will run the recommended Homebrew maintenance scripts
# ------------------------------------------------------
brewMaintenance () {
seek_confirmation "Run Homebrew maintenance?"
if is_confirmed; then
brew doctor
brew update
brew upgrade --all
fi
}
# hasCasks
# ------------------------------------------------------
# This function checks for Homebrew Casks and Fonts being installed.
# If it is not found, we install it and its prerequisites
# ------------------------------------------------------
hasCasks () {
if ! $(brew cask > /dev/null); then
brew install caskroom/cask/brew-cask
brew tap caskroom/fonts
brew tap caskroom/versions
fi
}
# My preferred installation of FFMPEG
install-ffmpeg () {
if [ ! $(type -P "ffmpeg") ]; then
brew install ffmpeg --with-faac --with-fdk-aac --with-ffplay --with-fontconfig --with-freetype --with-libcaca --with-libass --with-frei0r --with-libass --with-libbluray --with-libcaca --with-libquvi --with-libvidstab --with-libsoxr --with-libssh --with-libvo-aacenc --with-libvidstab --with-libvorbis --with-libvpx --with-opencore-amr --with-openjpeg --with-openssl --with-opus --with-rtmpdump --with-schroedinger --with-speex --with-theora --with-tools --with-webp --with-x265
fi
}
# doInstall
# ------------------------------------------------------
# Reads a list of items, checks if they are installed, installs
# those which are needed.
#
# Variables needed are:
# LISTINSTALLED: The command to list all previously installed items
# Ex: "brew list" or "gem list | awk '{print $1}'"
#
# INSTALLCOMMAND: The Install command for the desired items.
# Ex: "brew install" or "gem install"
#
# RECIPES: The list of packages to install.
# Ex: RECIPES=(
# package1
# package2
# )
#
# Credit: https://github.com/cowboy/dotfiles
# ------------------------------------------------------
function to_install() {
local debugger desired installed i desired_s installed_s remain
if [[ "$1" == 1 ]]; then debugger=1; shift; fi
# Convert args to arrays, handling both space- and newline-separated lists.
read -ra desired < <(echo "$1" | tr '\n' ' ')
read -ra installed < <(echo "$2" | tr '\n' ' ')
# Sort desired and installed arrays.
unset i; while read -r; do desired_s[i++]=$REPLY; done < <(
printf "%s\n" "${desired[@]}" | sort
)
unset i; while read -r; do installed_s[i++]=$REPLY; done < <(
printf "%s\n" "${installed[@]}" | sort
)
# Get the difference. comm is awesome.
unset i; while read -r; do remain[i++]=$REPLY; done < <(
comm -13 <(printf "%s\n" "${installed_s[@]}") <(printf "%s\n" "${desired_s[@]}")
)
[[ "$debugger" ]] && for v in desired desired_s installed installed_s remain; do
echo "$v ($(eval echo "\${#$v[*]}")) $(eval echo "\${$v[*]}")"
done
echo "${remain[@]}"
}
# Install the desired items that are not already installed.
function doInstall () {
list=$(to_install "${RECIPES[*]}" "$(${LISTINSTALLED})")
if [[ "${list}" ]]; then
seek_confirmation "Confirm each package before installing?"
if is_confirmed; then
for item in "${list[@]}"
do
seek_confirmation "Install ${item}?"
if is_confirmed; then
notice "Installing ${item}"
# FFMPEG takes additional flags
if [[ "${item}" = "ffmpeg" ]]; then
install-ffmpeg
elif [[ "${item}" = "tldr" ]]; then
brew tap tldr-pages/tldr
brew install tldr
else
${INSTALLCOMMAND} "${item}"
fi
fi
done
else
for item in "${list[@]}"
do
notice "Installing ${item}"
# FFMPEG takes additional flags
if [[ "${item}" = "ffmpeg" ]]; then
install-ffmpeg
elif [[ "${item}" = "tldr" ]]; then
brew tap tldr-pages/tldr
brew install tldr
else
${INSTALLCOMMAND} "${item}"
fi
done
fi
else
# only print notice when not checking dependencies via another script
if [ -z "$homebrewDependencies" ] && [ -z "$caskDependencies" ] && [ -z "$gemDependencies" ]; then
notice "Nothing to install. You've already installed all your recipes."
fi
fi
}
# brewCleanup
# ------------------------------------------------------
# This function cleans up an initial Homebrew installation
# ------------------------------------------------------
brewCleanup () {
# This is where brew stores its binary symlinks
binroot="$(brew --config | awk '/HOMEBREW_PREFIX/ {print $2}')"/bin
if [[ "$(type -P ${binroot}/bash)" && "$(cat /etc/shells | grep -q "$binroot/bash")" ]]; then
info "Adding ${binroot}/bash to the list of acceptable shells"
echo "$binroot/bash" | sudo tee -a /etc/shells >/dev/null
fi
if [[ "$SHELL" != "${binroot}/bash" ]]; then
info "Making ${binroot}/bash your default shell"
sudo chsh -s "${binroot}/bash" "$USER" >/dev/null 2>&1
success "Please exit and restart all your shells."
fi
brew cleanup
if $(brew cask > /dev/null); then
brew cask cleanup
fi
}
# hasDropbox
# ------------------------------------------------------
# This function checks for Dropbox being installed.
# If it is not found, we install it and its prerequisites
# ------------------------------------------------------
hasDropbox () {
# Confirm we have Dropbox installed
notice "Confirming that Dropbox is installed..."
if [ ! -e "/Applications/Dropbox.app" ]; then
notice "We don't have Dropbox. Let's get it installed."
seek_confirmation "Install Dropbox and all necessary prerequisites?"
if is_confirmed; then
# Run functions
hasHomebrew
brewMaintenance
hasCasks
# Set Variables
local LISTINSTALLED="brew cask list"
local INSTALLCOMMAND="brew cask install --appdir=/Applications"
local RECIPES=(dropbox)
Install
open -a dropbox
else
die "Can't run this script. Install Dropbox manually."
fi
else
success "Dropbox is installed."
fi
}

View File

@@ -1,749 +0,0 @@
#!/usr/bin/env bash
# ##################################################
# Shared bash functions used by my bash scripts.
#
# VERSION 1.4.0
#
# HISTORY
#
# * 2015-01-02 - v1.0.0 - First Creation
# * 2015-04-16 - v1.2.0 - Added 'checkDependencies' and 'pauseScript'
# * 2016-01-10 - v1.3.0 - Added 'join' function
# * 2016-01-11 - v1.4.9 - Added 'httpStatus' function
#
# ##################################################
# Traps
# ------------------------------------------------------
# These functions are for use with different trap scenarios
# ------------------------------------------------------
# Non destructive exit for when script exits naturally.
# Usage: Add this function at the end of every script
function safeExit() {
# Delete temp files, if any
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
fi
trap - INT TERM EXIT
exit
}
# readFile
# ------------------------------------------------------
# Function to read a line from a file.
#
# Most often used to read the config files saved in my etc directory.
# Outputs each line in a variable named $result
# ------------------------------------------------------
function readFile() {
unset "${result}"
while read result
do
echo "${result}"
done < "$1"
}
# Escape a string
# ------------------------------------------------------
# usage: var=$(escape "String")
# ------------------------------------------------------
escape() { echo "${@}" | sed 's/[]\.|$(){}?+*^]/\\&/g'; }
# needSudo
# ------------------------------------------------------
# If a script needs sudo access, call this function which
# requests sudo access and then keeps it alive.
# ------------------------------------------------------
function needSudo() {
# Update existing sudo time stamp if set, otherwise do nothing.
sudo -v
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
}
# convertsecs
# ------------------------------------------------------
# Convert Seconds to human readable time
#
# To use this, pass a number (seconds) into the function as this:
# print "$(convertsecs $TOTALTIME)"
#
# To compute the time it takes a script to run use tag the start and end times with
# STARTTIME=$(date +"%s")
# ENDTIME=$(date +"%s")
# TOTALTIME=$(($ENDTIME-$STARTTIME))
# ------------------------------------------------------
function convertsecs() {
((h=${1}/3600))
((m=(${1}%3600)/60))
((s=${1}%60))
printf "%02d:%02d:%02d\n" $h $m $s
}
function pushover() {
# pushover
# ------------------------------------------------------
# Sends notifications view Pushover
# Requires a file named 'pushover.cfg' be placed in '../etc/'
#
# Usage: pushover "Title Goes Here" "Message Goes Here"
#
# Credit: http://ryonsherman.blogspot.com/2012/10/shell-script-to-send-pushover.html
# ------------------------------------------------------
# Check for config file containing API Keys
if [ ! -f "${SOURCEPATH}/../etc/pushover.cfg" ]; then
error "Please locate the pushover.cfg to send notifications to Pushover."
else
# Grab variables from the config file
source "${SOURCEPATH}/../etc/pushover.cfg"
# Send to Pushover
PUSHOVERURL="https://api.pushover.net/1/messages.json"
API_KEY="${PUSHOVER_API_KEY}"
USER_KEY="${PUSHOVER_USER_KEY}"
DEVICE=""
TITLE="${1}"
MESSAGE="${2}"
curl \
-F "token=${API_KEY}" \
-F "user=${USER_KEY}" \
-F "device=${DEVICE}" \
-F "title=${TITLE}" \
-F "message=${MESSAGE}" \
"${PUSHOVERURL}" > /dev/null 2>&1
fi
}
# Join
# ----------------------------------------------
# This function joins items together with a user specified separator
# Taken whole cloth from: http://stackoverflow.com/questions/1527049/bash-join-elements-of-an-array
#
# Usage:
# join , a "b c" d #a,b c,d
# join / var local tmp #var/local/tmp
# join , "${FOO[@]}" #a,b,c
# ----------------------------------------------
function join() { local IFS="${1}"; shift; echo "${*}"; }
# File Checks
# ------------------------------------------------------
# A series of functions which make checks against the filesystem. For
# use in if/then statements.
#
# Usage:
# if is_file "file"; then
# ...
# fi
# ------------------------------------------------------
function is_exists() {
if [[ -e "$1" ]]; then
return 0
fi
return 1
}
function is_not_exists() {
if [[ ! -e "$1" ]]; then
return 0
fi
return 1
}
function is_file() {
if [[ -f "$1" ]]; then
return 0
fi
return 1
}
function is_not_file() {
if [[ ! -f "$1" ]]; then
return 0
fi
return 1
}
function is_dir() {
if [[ -d "$1" ]]; then
return 0
fi
return 1
}
function is_not_dir() {
if [[ ! -d "$1" ]]; then
return 0
fi
return 1
}
function is_symlink() {
if [[ -L "$1" ]]; then
return 0
fi
return 1
}
function is_not_symlink() {
if [[ ! -L "$1" ]]; then
return 0
fi
return 1
}
function is_empty() {
if [[ -z "$1" ]]; then
return 0
fi
return 1
}
function is_not_empty() {
if [[ -n "$1" ]]; then
return 0
fi
return 1
}
# Test whether a command exists
# ------------------------------------------------------
# Usage:
# if type_exists 'git'; then
# some action
# else
# some other action
# fi
# ------------------------------------------------------
function type_exists() {
if [ "$(type -P "$1")" ]; then
return 0
fi
return 1
}
function type_not_exists() {
if [ ! "$(type -P "$1")" ]; then
return 0
fi
return 1
}
# Test which OS the user runs
# $1 = OS to test
# Usage: if is_os 'darwin'; then
function is_os() {
if [[ "${OSTYPE}" == $1* ]]; then
return 0
fi
return 1
}
# SEEKING CONFIRMATION
# ------------------------------------------------------
# Asks questions of a user and then does something with the answer.
# y/n are the only possible answers.
#
# USAGE:
# seek_confirmation "Ask a question"
# if is_confirmed; then
# some action
# else
# some other action
# fi
#
# Credt: https://github.com/kevva/dotfiles
# ------------------------------------------------------
# Ask the question
function seek_confirmation() {
# echo ""
input "$@"
if "${force}"; then
notice "Forcing confirmation with '--force' flag set"
else
read -p " (y/n) " -n 1
echo ""
fi
}
# Test whether the result of an 'ask' is a confirmation
function is_confirmed() {
if "${force}"; then
return 0
else
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
return 0
fi
return 1
fi
}
function is_not_confirmed() {
if "${force}"; then
return 1
else
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
return 0
fi
return 1
fi
}
# Skip something
# ------------------------------------------------------
# Offer the user a chance to skip something.
# Credit: https://github.com/cowboy/dotfiles
# ------------------------------------------------------
function skip() {
REPLY=noskip
read -t 5 -n 1 -s -p "${bold}To skip, press ${underline}X${reset}${bold} within 5 seconds.${reset}"
if [[ "$REPLY" =~ ^[Xx]$ ]]; then
notice " Skipping!"
return 0
else
notice " Continuing..."
return 1
fi
}
# unmountDrive
# ------------------------------------------------------
# If an AFP drive is mounted as part of a script, this
# will unmount the volume. This will only work on Macs.
# ------------------------------------------------------
function unmountDrive() {
if [ -d "$1" ]; then
diskutil unmount "$1"
fi
}
# help
# ------------------------------------------------------
# Prints help for a script when invoked from the command
# line. Typically via '-h'. If additional flags or help
# text is available in the script they will be printed
# in the '$usage' variable.
# ------------------------------------------------------
function help () {
echo "" 1>&2
input " $@" 1>&2
if [ -n "${usage}" ]; then # print usage information if available
echo " ${usage}" 1>&2
fi
echo "" 1>&2
exit 1
}
# Dependencies
# -----------------------------------
# Arrays containing package dependencies needed to execute this script.
# The script will fail if dependencies are not installed. For Mac users,
# most dependencies can be installed automatically using the package
# manager 'Homebrew'.
# Usage in script: $ homebrewDependencies=(package1 package2)
# -----------------------------------
function checkDependencies() {
saveIFS=$IFS
IFS=$' \n\t'
if [ -n "${homebrewDependencies}" ]; then
LISTINSTALLED="brew list"
INSTALLCOMMAND="brew install"
RECIPES=("${homebrewDependencies[@]}")
# Invoke functions from setupScriptFunctions.sh
hasHomebrew
doInstall
fi
if [ -n "$caskDependencies" ]; then
LISTINSTALLED="brew cask list"
INSTALLCOMMAND="brew cask install --appdir=/Applications"
RECIPES=("${caskDependencies[@]}")
# Invoke functions from setupScriptFunctions.sh
hasHomebrew
hasCasks
doInstall
fi
if [ -n "$gemDependencies" ]; then
LISTINSTALLED="gem list | awk '{print $1}'"
INSTALLCOMMAND="gem install"
RECIPES=("${gemDependencies[@]}")
# Invoke functions from setupScriptFunctions.sh
doInstall
fi
IFS=$saveIFS
}
function pauseScript() {
# A simple function used to pause a script at any point and
# only continue on user input
seek_confirmation "Ready to continue?"
if is_confirmed; then
info "Continuing"
else
warning "Exiting Script."
safeExit
fi
}
function in_array() {
# Determine if a value is in an array.
# Usage: if in_array "VALUE" "${ARRAY[@]}"; then ...
local value="$1"; shift
for arrayItem in "$@"; do
[[ "${arrayItem}" == "${value}" ]] && return 0
done
return 1
}
# Text Transformations
# -----------------------------------
# Transform text using these functions.
# Adapted from https://github.com/jmcantrell/bashful
# -----------------------------------
lower() {
# Convert stdin to lowercase.
# usage: text=$(lower <<<"$1")
# echo "MAKETHISLOWERCASE" | lower
tr '[:upper:]' '[:lower:]'
}
upper() {
# Convert stdin to uppercase.
# usage: text=$(upper <<<"$1")
# echo "MAKETHISUPPERCASE" | upper
tr '[:lower:]' '[:upper:]'
}
ltrim() {
# Removes all leading whitespace (from the left).
local char=${1:-[:space:]}
sed "s%^[${char//%/\\%}]*%%"
}
rtrim() {
# Removes all trailing whitespace (from the right).
local char=${1:-[:space:]}
sed "s%[${char//%/\\%}]*$%%"
}
trim() {
# Removes all leading/trailing whitespace
# Usage examples:
# echo " foo bar baz " | trim #==> "foo bar baz"
ltrim "$1" | rtrim "$1"
}
squeeze() {
# Removes leading/trailing whitespace and condenses all other consecutive
# whitespace into a single space.
#
# Usage examples:
# echo " foo bar baz " | squeeze #==> "foo bar baz"
local char=${1:-[[:space:]]}
sed "s%\(${char//%/\\%}\)\+%\1%g" | trim "$char"
}
squeeze_lines() {
# <doc:squeeze_lines> {{{
#
# Removes all leading/trailing blank lines and condenses all other
# consecutive blank lines into a single blank line.
#
# </doc:squeeze_lines> }}}
sed '/^[[:space:]]\+$/s/.*//g' | cat -s | trim_lines
}
progressBar() {
# progressBar
# -----------------------------------
# Prints a progress bar within a for/while loop.
# To use this function you must pass the total number of
# times the loop will run to the function.
#
# usage:
# for number in $(seq 0 100); do
# sleep 1
# progressBar 100
# done
# -----------------------------------
if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then
return
fi
local width
width=30
bar_char="#"
# Don't run this function when scripts are running in verbose mode
if ${verbose}; then return; fi
# Reset the count
if [ -z "${progressBarProgress}" ]; then
progressBarProgress=0
fi
# Do nothing if the output is not a terminal
if [ ! -t 1 ]; then
echo "Output is not a terminal" 1>&2
return
fi
# Hide the cursor
tput civis
trap 'tput cnorm; exit 1' SIGINT
if [ ! "${progressBarProgress}" -eq $(( $1 - 1 )) ]; then
# Compute the percentage.
perc=$(( progressBarProgress * 100 / $1 ))
# Compute the number of blocks to represent the percentage.
num=$(( progressBarProgress * width / $1 ))
# Create the progress bar string.
bar=
if [ ${num} -gt 0 ]; then
bar=$(printf "%0.s${bar_char}" $(seq 1 ${num}))
fi
# Print the progress bar.
progressBarLine=$(printf "%s [%-${width}s] (%d%%)" "Running Process" "${bar}" "${perc}")
echo -en "${progressBarLine}\r"
progressBarProgress=$(( progressBarProgress + 1 ))
else
# Clear the progress bar when complete
echo -ne "${width}%\033[0K\r"
unset progressBarProgress
fi
tput cnorm
}
htmlDecode() {
# Decode HTML characters with sed
# Usage: htmlDecode <string>
echo "${1}" | sed -f "${SOURCEPATH}/htmlDecode.sed"
}
htmlEncode() {
# Encode HTML characters with sed
# Usage: htmlEncode <string>
echo "${1}" | sed -f "${SOURCEPATH}/htmlEncode.sed"
}
urlencode() {
# URL encoding/decoding from: https://gist.github.com/cdown/1163649
# Usage: urlencode <string>
local length="${#1}"
for (( i = 0; i < length; i++ )); do
local c="${1:i:1}"
case $c in
[a-zA-Z0-9.~_-]) printf "%s" "$c" ;;
*) printf '%%%02X' "'$c"
esac
done
}
urldecode() {
# Usage: urldecode <string>
local url_encoded="${1//+/ }"
printf '%b' "${url_encoded//%/\x}"
}
parse_yaml() {
# Function to parse YAML files and add values to variables. Send it to a temp file and source it
# https://gist.github.com/DinoChiesa/3e3c3866b51290f31243 which is derived from
# https://gist.github.com/epiloque/8cf512c6d64641bde388
#
# Usage:
# $ parse_yaml sample.yml > /some/tempfile
#
# parse_yaml accepts a prefix argument so that imported settings all have a common prefix
# (which will reduce the risk of name-space collisions).
#
# $ parse_yaml sample.yml "CONF_"
local prefix=$2
local s
local w
local fs
s='[[:space:]]*'
w='[a-zA-Z0-9_]*'
fs="$(echo @|tr @ '\034')"
sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s[:-]$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$1" |
awk -F"$fs" '{
indent = length($1)/2;
if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";}
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s%s=(\"%s\")\n", "'"$prefix"'",vn, $2, conj[indent-1],$3);
}
}' | sed 's/_=/+=/g'
}
httpStatus() {
# -----------------------------------
# Shamelessly taken from: https://gist.github.com/rsvp/1171304
#
# Usage: httpStatus URL [timeout] [--code or --status] [see 4.]
# ^message with code (default)
# ^code (numeric only)
# ^in secs (default: 3)
# ^URL without "http://" prefix works fine.
#
# 4. curl options: e.g. use -L to follow redirects.
#
# Dependencies: curl
#
# Example: $ httpStatus bit.ly
# 301 Redirection: Moved Permanently
#
# Example: $ httpStatus www.google.com 100 -c
# 200
#
# -----------------------------------
local curlops
local arg4
local arg5
local arg6
local arg7
local flag
local timeout
local url
saveIFS=${IFS}
IFS=$' \n\t'
url=${1}
timeout=${2:-'3'}
# ^in seconds
flag=${3:-'--status'}
# curl options, e.g. -L to follow redirects
arg4=${4:-''}
arg5=${5:-''}
arg6=${6:-''}
arg7=${7:-''}
curlops="${arg4} ${arg5} ${arg6} ${arg7}"
# __________ get the CODE which is numeric:
code=`echo $(curl --write-out %{http_code} --silent --connect-timeout ${timeout} \
--no-keepalive ${curlops} --output /dev/null ${url})`
# __________ get the STATUS (from code) which is human interpretable:
case $code in
000) status="Not responding within ${timeout} seconds" ;;
100) status="Informational: Continue" ;;
101) status="Informational: Switching Protocols" ;;
200) status="Successful: OK within ${timeout} seconds" ;;
201) status="Successful: Created" ;;
202) status="Successful: Accepted" ;;
203) status="Successful: Non-Authoritative Information" ;;
204) status="Successful: No Content" ;;
205) status="Successful: Reset Content" ;;
206) status="Successful: Partial Content" ;;
300) status="Redirection: Multiple Choices" ;;
301) status="Redirection: Moved Permanently" ;;
302) status="Redirection: Found residing temporarily under different URI" ;;
303) status="Redirection: See Other" ;;
304) status="Redirection: Not Modified" ;;
305) status="Redirection: Use Proxy" ;;
306) status="Redirection: status not defined" ;;
307) status="Redirection: Temporary Redirect" ;;
400) status="Client Error: Bad Request" ;;
401) status="Client Error: Unauthorized" ;;
402) status="Client Error: Payment Required" ;;
403) status="Client Error: Forbidden" ;;
404) status="Client Error: Not Found" ;;
405) status="Client Error: Method Not Allowed" ;;
406) status="Client Error: Not Acceptable" ;;
407) status="Client Error: Proxy Authentication Required" ;;
408) status="Client Error: Request Timeout within ${timeout} seconds" ;;
409) status="Client Error: Conflict" ;;
410) status="Client Error: Gone" ;;
411) status="Client Error: Length Required" ;;
412) status="Client Error: Precondition Failed" ;;
413) status="Client Error: Request Entity Too Large" ;;
414) status="Client Error: Request-URI Too Long" ;;
415) status="Client Error: Unsupported Media Type" ;;
416) status="Client Error: Requested Range Not Satisfiable" ;;
417) status="Client Error: Expectation Failed" ;;
500) status="Server Error: Internal Server Error" ;;
501) status="Server Error: Not Implemented" ;;
502) status="Server Error: Bad Gateway" ;;
503) status="Server Error: Service Unavailable" ;;
504) status="Server Error: Gateway Timeout within ${timeout} seconds" ;;
505) status="Server Error: HTTP Version Not Supported" ;;
*) echo " !! httpstatus: status not defined." && safeExit ;;
esac
# _______________ MAIN
case ${flag} in
--status) echo "${code} ${status}" ;;
-s) echo "${code} ${status}" ;;
--code) echo "${code}" ;;
-c) echo "${code}" ;;
*) echo " !! httpstatus: bad flag" && safeExit;;
esac
IFS="${saveIFS}"
}
function makeCSV() {
# Creates a new CSV file if one does not already exist.
# Takes passed arguments and writes them as a header line to the CSV
# Usage 'makeCSV column1 column2 column3'
# Set the location and name of the CSV File
if [ -z "${csvLocation}" ]; then
csvLocation="${HOME}/Desktop"
fi
if [ -z "${csvName}" ]; then
csvName="$(LC_ALL=C date +%Y-%m-%d)-${FUNCNAME[1]}.csv"
fi
csvFile="${csvLocation}/${csvName}"
# Overwrite existing file? If not overwritten, new content is added
# to the bottom of the existing file
if [ -f "${csvFile}" ]; then
seek_confirmation "${csvFile} already exists. Overwrite?"
if is_confirmed; then
rm "${csvFile}"
writeCSV "$@"
fi
fi
}
function writeCSV() {
# Takes passed arguments and writes them as a comma separated line
# Usage 'writeCSV column1 column2 column3'
csvInput=($@)
saveIFS=$IFS
IFS=','
echo "${csvInput[*]}" >> "${csvFile}"
IFS=$saveIFS
}
function json2yaml() {
# convert json files to yaml using python and PyYAML
python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' < "$1"
}
function yaml2json() {
# convert yaml files to json using python and PyYAML
python -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin), sys.stdout, indent=4)' < "$1"
}

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env bash
# ##################################################
# Shared bash functions used by my bash scripts.
#
# VERSION 1.0.2
#
# HISTORY
#
# * 2015-01-02 - v1.0.0 - First Creation
# * 2015-08-05 - v1.0.1 - Now has $hourstamp (10:34:40 PM)
# * 2016-01-10 - v1.0.2 - Now has $longdate and set LC_ALL=C to all dates
# * 2016-01-13 - v1.0.3 - now has $gmtdate
#
# ##################################################
# SCRIPTNAME
# ------------------------------------------------------
# Will return the name of the script being run
# ------------------------------------------------------
scriptName=`basename $0` #Set Script Name variable
scriptBasename="$(basename ${scriptName} .sh)" # Strips '.sh' from scriptName
# TIMESTAMPS
# ------------------------------------------------------
# Prints the current date and time in a variety of formats:
#
# ------------------------------------------------------
now=$(LC_ALL=C date +"%m-%d-%Y %r") # Returns: 06-14-2015 10:34:40 PM
datestamp=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14
hourstamp=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM
timestamp=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440
today=$(LC_ALL=C date +"%m-%d-%Y") # Returns: 06-14-2015
longdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500
gmtdate=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT
# THISHOST
# ------------------------------------------------------
# Will print the current hostname of the computer the script
# is being run on.
# ------------------------------------------------------
thisHost=$(hostname)

View File

@@ -1,107 +0,0 @@
#!/usr/bin/env bash
# ##################################################
# Bash scripting Utilities.
#
# VERSION 1.0.0
#
# This script sources my collection of scripting utilities making
# it possible to source this one script and gain access to a
# complete collection of functions, variables, and other options.
#
# HISTORY
#
# * 2015-01-02 - v1.0.0 - First Creation
# * 2016-02-10 - v1.1.1 - Minor changes to satisfy Shellcheck
#
# ##################################################
# Logging and Colors
# ------------------------------------------------------
# Here we set the colors for our script feedback.
# Example usage: success "sometext"
#------------------------------------------------------
# Set Colors
bold=$(tput bold)
underline=$(tput sgr 0 1)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
blue=$(tput setaf 38)
function _alert() {
if [ "${1}" = "emergency" ]; then
local color="${bold}${red}"
fi
if [ "${1}" = "error" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "warning" ]; then local color="${red}"; fi
if [ "${1}" = "success" ]; then local color="${green}"; fi
if [ "${1}" = "debug" ]; then local color="${purple}"; fi
if [ "${1}" = "header" ]; then local color="${bold}""${tan}"; fi
if [ "${1}" = "input" ]; then local color="${bold}"; printLog="false"; fi
if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi
# Don't use colors on pipes or non-recognized terminals
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi
# Print to $logFile
if [[ ${printLog} = "true" ]] || [ "${printLog}" == "1" ]; then
echo -e "$(date +"%m-%d-%Y %r") $(printf "[%9s]" "${1}") ${_message}" >> "${logFile}";
fi
# Print to console when script is not 'quiet'
if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then
return
else
echo -e "$(date +"%r") ${color}$(printf "[%9s]" "${1}") ${_message}${reset}";
fi
}
function die () { local _message="${*} Exiting."; echo "$(_alert emergency)"; safeExit;}
function error () { local _message="${*}"; echo "$(_alert error)"; }
function warning () { local _message="${*}"; echo "$(_alert warning)"; }
function notice () { local _message="${*}"; echo "$(_alert notice)"; }
function info () { local _message="${*}"; echo "$(_alert info)"; }
function debug () { local _message="${*}"; echo "$(_alert debug)"; }
function success () { local _message="${*}"; echo "$(_alert success)"; }
function input() { local _message="${*}"; echo -n "$(_alert input)"; }
function header() { local _message="========== ${*} ========== "; echo "$(_alert header)"; }
# Log messages when verbose is set to "true"
verbose() {
if [[ "${verbose}" = "true" ]] || [ "${verbose}" == "1" ]; then
debug "$@"
fi
}
# Source additional /lib files
# ------------------------------------------------------
# First we locate this script and populate the $SCRIPTPATH variable
# Doing so allows us to source additional files from this utils file.
SOURCE="${BASH_SOURCE[0]}"
while [ -h "${SOURCE}" ]; do # resolve ${SOURCE} until the file is no longer a symlink
DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
SOURCE="$(readlink "${SOURCE}")"
[[ ${SOURCE} != /* ]] && SOURCE="${DIR}/${SOURCE}" # if ${SOURCE} was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SOURCEPATH="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
if [ ! -d "${SOURCEPATH}" ]
then
die "Failed to find library files expected in: ${SOURCEPATH}"
fi
for utility_file in "${SOURCEPATH}"/*.sh
do
if [ -e "${utility_file}" ]; then
# Don't source self
if [[ "${utility_file}" == *"utils.sh"* ]]; then
continue
fi
source "$utility_file"
fi
done

572
scriptTemplate.sh Normal file → Executable file
View File

@@ -1,255 +1,393 @@
#!/usr/bin/env bash
# ##################################################
# My Generic BASH script template
#
version="1.0.0" # Sets version variable
#
scriptTemplateVersion="1.5.0" # Version of scriptTemplate.sh that this script is based on
# v1.1.0 - Added 'debug' option
# v1.1.1 - Moved all shared variables to Utils
# - Added $PASS variable when -p is passed
# v1.2.0 - Added 'checkDependencies' function to ensure needed
# Bash packages are installed prior to execution
# v1.3.0 - Can now pass CLI without an option to $args
# v1.4.0 - checkDependencies now checks gems and mac apps via
# Homebrew cask
# v1.5.0 - Now has preferred IFS setting
# - Preset flags now respect true/false
# - Moved 'safeExit' function into template where it should
# have been all along.
#
# HISTORY:
#
# * DATE - v1.0.0 - First Creation
#
# ##################################################
_mainScript_() {
# Provide a variable with the location of this script.
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
info "Hello world"
# Source Scripting Utilities
# -----------------------------------
# These shared utilities provide many functions which are needed to provide
# the functionality in this boilerplate. This script will fail if they can
# not be found.
# -----------------------------------
} # end _mainScript_
utilsLocation="${scriptPath}/lib/utils.sh" # Update this path to find the utilities.
# Set flags and default variables
# Script specific
if [ -f "${utilsLocation}" ]; then
source "${utilsLocation}"
else
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
exit 1
fi
# Common
LOGFILE="${HOME}/logs/$(basename "$0")"
QUIET=false
LOGLEVEL=ERROR
VERBOSE=false
FORCE=false
DRYRUN=false
declare -a args=()
now=$(LC_ALL=C date +"%m-%d-%Y %r") # Returns: 06-14-2015 10:34:40 PM
datestamp=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14
hourstamp=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM
timestamp=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440
longdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500
gmtdate=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
function trapCleanup() {
echo ""
# Delete temp files, if any
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
# Colors
if tput setaf 1 &>/dev/null; then
bold=$(tput bold)
white=$(tput setaf 7)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
yellow=$(tput setaf 3)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
else
bold="\033[4;37m"
white="\033[0;37m"
reset="\033[0m"
purple="\033[0;35m"
red="\033[0;31m"
green="\033[1;32m"
tan="\033[0;33m"
yellow="\033[0;33m"
blue="\033[0;34m"
underline="\033[4;37m"
fi
die "Exit trapped. In function: '${FUNCNAME[*]}'"
}
# safeExit
# -----------------------------------
# Non destructive exit for when script exits naturally.
# Usage: Add this function at the end of every script.
# -----------------------------------
function safeExit() {
# Delete temp files, if any
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
_alert_() {
# DESC: Controls all printing of messages to log files and stdout.
# ARGS: $1 (required) - The type of alert to print
# (success, header, notice, dryrun, debug, warning, error,
# fatal, info, input)
# $2 (required) - The message to be printed to stdout and/or a log file
# $3 (optional) - Pass '$LINENO' to print the line number where the _alert_ was triggered
# OUTS: None
# USAGE: [ALERTTYPE] "[MESSAGE]" "$LINENO"
# NOTES: The colors of each alert type are set in this function
# For specified alert types, the funcstac will be printed
local function_name color
local alertType="${1}"
local message="${2}"
local line="${3-}"
[ -z "${LOGFILE-}" ] && fatal "\$LOGFILE must be set"
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
if [ -z "${line}" ]; then
[[ "$1" =~ ^(fatal|error|debug|warning) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} $(_functionStack_)"
else
[[ "$1" =~ ^(fatal|error|debug) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} (line: $line) $(_functionStack_)"
fi
trap - INT TERM EXIT
exit
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=false
printLog=false
verbose=false
force=false
strict=false
debug=false
args=()
if [ -n "${line}" ]; then
[[ "$1" =~ ^(warning|info|notice|dryrun) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} (line: $line)"
fi
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
if [[ "${alertType}" =~ ^(error|fatal) ]]; then
color="${bold}${red}"
elif [ "${alertType}" = "warning" ]; then
color="${red}"
elif [ "${alertType}" = "success" ]; then
color="${green}"
elif [ "${alertType}" = "debug" ]; then
color="${purple}"
elif [ "${alertType}" = "header" ]; then
color="${bold}${tan}"
elif [[ "${alertType}" =~ ^(input|notice) ]]; then
color="${bold}"
elif [ "${alertType}" = "dryrun" ]; then
color="${blue}"
else
color=""
fi
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="$HOME/Library/Logs/${scriptBasename}.log"
_writeToScreen_() {
# Check for Dependencies
# -----------------------------------
# Arrays containing package dependencies needed to execute this script.
# The script will fail if dependencies are not installed. For Mac users,
# most dependencies can be installed automatically using the package
# manager 'Homebrew'. Mac applications will be installed using
# Homebrew Casks. Ruby and gems via RVM.
# -----------------------------------
homebrewDependencies=()
caskDependencies=()
gemDependencies=()
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
[[ ${VERBOSE} == false && "${alertType}" =~ ^(debug|verbose) ]] && return 0
function mainScript() {
############## Begin Script Here ###################
####################################################
if ! [[ -t 1 ]]; then # Don't use colors on non-recognized terminals
color=""
reset=""
fi
echo -n
echo -e "$(date +"%r") ${color}$(printf "[%7s]" "${alertType}") ${message}${reset}"
}
_writeToScreen_
####################################################
############### End Script Here ####################
}
_writeToLog_() {
[[ "${alertType}" == "input" ]] && return 0
[[ "${LOGLEVEL}" =~ (off|OFF|Off) ]] && return 0
[[ ! -f "${LOGFILE}" ]] && touch "${LOGFILE}"
############## Begin Options and Usage ###################
# Don't use colors in logs
if command -v gsed &>/dev/null; then
local cleanmessage="$(echo "${message}" | gsed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
else
local cleanmessage="$(echo "${message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
fi
echo -e "$(date +"%b %d %R:%S") $(printf "[%7s]" "${alertType}") [$(/bin/hostname)] ${cleanmessage}" >>"${LOGFILE}"
}
# Print usage
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
This is a script template. Edit this description to print help to users.
${bold}Options:${reset}
-u, --username Username for script
-p, --password User password
--force Skip all user interaction. Implied 'Yes' to all actions.
-q, --quiet Quiet (no output)
-l, --log Print log to file
-s, --strict Exit script with null variables. i.e 'set -o nounset'
-v, --verbose Output more information. (Items echoed to 'verbose')
-d, --debug Runs script in BASH debug mode (set -x)
-h, --help Display this help and exit
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
# Write specified log level data to logfile
case "${LOGLEVEL:-ERROR}" in
ALL|all|All)
_writeToLog_
;;
DEBUG|debug|Debug)
_writeToLog_
;;
INFO|info|Info)
if [[ "${alertType}" =~ ^(die|error|fatal|warning|info|notice|success) ]]; then
_writeToLog_
fi
;;
WARN|warn|Warn)
if [[ "${alertType}" =~ ^(die|error|fatal|warning) ]]; then
_writeToLog_
fi
;;
ERROR|error|Error)
if [[ "${alertType}" =~ ^(die|error|fatal) ]]; then
_writeToLog_
fi
;;
FATAL|fatal|Fatal)
if [[ "${alertType}" =~ ^(die|fatal) ]]; then
_writeToLog_
fi
;;
OFF|off)
return 0
;;
*)
if [[ "${alertType}" =~ ^(die|error|fatal) ]]; then
_writeToLog_
fi
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# [[ $# -eq 0 ]] && set -- "--help"
} # /_alert_
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-u|--username) shift; username=${1} ;;
-p|--password) shift; echo "Enter Pass: "; stty -echo; read PASS; stty echo;
echo ;;
-v|--verbose) verbose=true ;;
-l|--log) printLog=true ;;
-q|--quiet) quiet=true ;;
-s|--strict) strict=true;;
-d|--debug) debug=true;;
--force) force=true ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
error() { _alert_ error "${1}" "${2-}"; }
warning() { _alert_ warning "${1}" "${2-}"; }
notice() { _alert_ notice "${1}" "${2-}"; }
info() { _alert_ info "${1}" "${2-}"; }
success() { _alert_ success "${1}" "${2-}"; }
dryrun() { _alert_ dryrun "${1}" "${2-}"; }
input() { _alert_ input "${1}" "${2-}"; }
header() { _alert_ header "== ${1} ==" "${2-}"; }
die() { _alert_ fatal "${1}" "${2-}"; _safeExit_ "1" ; }
fatal() { _alert_ fatal "${1}" "${2-}"; _safeExit_ "1" ; }
debug() { _alert_ debug "${1}" "${2-}"; }
verbose() { _alert_ debug "${1}" "${2-}"; }
# Store the remaining part as arguments.
args+=("$@")
_safeExit_() {
# DESC: Cleanup and exit from a script
# ARGS: $1 (optional) - Exit code (defaults to 0)
# OUTS: None
############## End Options and Usage ###################
if [[ -d "${script_lock-}" ]]; then
if command rm -rf "${script_lock}"; then
debug "Removing script lock"
else
warning "Script lock could not be removed. Try manually deleting ${tan}'${lock_dir}'${red}"
fi
fi
if [[ -n "${tmpDir-}" && -d "${tmpDir-}" ]]; then
if [[ ${1-} == 1 && -n "$(ls "${tmpDir}")" ]]; then
command rm -r "${tmpDir}"
else
command rm -r "${tmpDir}"
debug "Removing temp directory"
fi
fi
trap - INT TERM EXIT
exit ${1:-0}
}
_trapCleanup_() {
# DESC: Log errors and cleanup from script when an error is trapped
# ARGS: $1 - Line number where error was trapped
# $2 - Line number in function
# $3 - Command executing at the time of the trap
# $4 - Names of all shell functions currently in the execution call stack
# $5 - Scriptname
# $6 - $BASH_SOURCE
# OUTS: None
# ############# ############# #############
# ## TIME TO RUN THE SCRIPT ##
# ## ##
# ## You shouldn't need to edit anything ##
# ## beneath this line ##
# ## ##
# ############# ############# #############
local line=${1-} # LINENO
local linecallfunc=${2-}
local command="${3-}"
local funcstack="${4-}"
local script="${5-}"
local sourced="${6-}"
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
funcstack="'$(echo "$funcstack" | sed -E 's/ / < /g')'"
# Set IFS to preferred implementation
IFS=$'\n\t'
if [[ "${script##*/}" == "${sourced##*/}" ]]; then
fatal "${7-} command: '$command' (line: $line) [func: $(_functionStack_)]"
else
fatal "${7-} command: '$command' (func: ${funcstack} called at line $linecallfunc of '${script##*/}') (line: $line of '${sourced##*/}') "
fi
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
_safeExit_ "1"
}
# Run in debug mode, if set
if ${debug}; then set -x ; fi
_makeTempDir_() {
# DESC: Creates a temp directory to house temporary files
# ARGS: $1 (Optional) - First characters/word of directory name
# OUTS: $tmpDir - Temporary directory
# USAGE: _makeTempDir_ "$(basename "$0")"
# Exit on empty variable
if ${strict}; then set -o nounset ; fi
[ -d "${tmpDir:-}" ] && return 0
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
if [ -n "${1-}" ]; then
tmpDir="${TMPDIR:-/tmp/}${1}.$RANDOM.$RANDOM.$$"
else
tmpDir="${TMPDIR:-/tmp/}$(basename "$0").$RANDOM.$RANDOM.$RANDOM.$$"
fi
(umask 077 && mkdir "${tmpDir}") || {
fatal "Could not create temporary directory! Exiting."
}
debug "\$tmpDir=$tmpDir"
}
# Invoke the checkDependenices function to test for Bash packages. Uncomment if needed.
# checkDependencies
_acquireScriptLock_() {
# DESC: Acquire script lock
# ARGS: $1 (optional) - Scope of script execution lock (system or user)
# OUTS: $script_lock - Path to the directory indicating we have the script lock
# NOTE: This lock implementation is extremely simple but should be reliable
# across all platforms. It does *not* support locking a script with
# symlinks or multiple hardlinks as there's no portable way of doing so.
# If the lock was acquired it's automatically released in _safeExit_()
# Run your script
mainScript
local lock_dir
if [[ ${1-} == 'system' ]]; then
lock_dir="${TMPDIR:-/tmp/}$(basename "$0").lock"
else
lock_dir="${TMPDIR:-/tmp/}$(basename "$0").$UID.lock"
fi
# Exit cleanlyd
safeExit
if command mkdir "${lock_dir}" 2>/dev/null; then
readonly script_lock="${lock_dir}"
debug "Acquired script lock: ${tan}${script_lock}${purple}"
else
error "Unable to acquire script lock: ${tan}${lock_dir}${red}"
fatal "If you trust the script isn't running, delete the lock dir"
fi
}
_functionStack_() {
# DESC: Prints the function stack in use
# ARGS: None
# OUTS: Prints [function]:[file]:[line]
# NOTE: Does not print functions from the alert class
local _i
funcStackResponse=()
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | verbose | debug | die) continue ;; esac
funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[$_i - 1]}")
done
printf "( "
printf %s "${funcStackResponse[0]}"
printf ' < %s' "${funcStackResponse[@]:1}"
printf ' )\n'
}
_parseOptions_() {
# Iterate over options
# breaking -ab into -a -b when needed and --foo=bar into --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i = 1; i < ${#1}; i++)); do
c=${1:i:1}
options+=("-$c") # Add current char to options
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring == *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Read the options and set stuff
while [[ ${1-} == -?* ]]; do
case $1 in
-h | --help)
_usage_ >&2
_safeExit_
;;
-l | --loglevel)
shift
LOGLEVEL=${1}
;;
-n | --dryrun) DRYRUN=true ;;
-v | --verbose) VERBOSE=true ;;
-q | --quiet) QUIET=true ;;
--force) FORCE=true ;;
--endopts)
shift
break
;;
*) fatal "invalid option: '$1'." ;;
esac
shift
done
args+=("$@") # Store the remaining user input as arguments.
}
_usage_() {
cat <<EOF
${bold}$(basename "$0") [OPTION]... [FILE]...${reset}
This is a script template. Edit this description to print help to users.
${bold}Options:${reset}
-h, --help Display this help and exit
-l, --loglevel One of: FATAL, ERROR, WARN, INFO, DEBUG, ALL, OFF (Default is 'ERROR')
$ $(basename "$0") --loglevel 'WARN'
-n, --dryrun Non-destructive. Makes no permanent changes.
-q, --quiet Quiet (no output)
-v, --verbose Output more information. (Items echoed to 'verbose')
--force Skip all user interaction. Implied 'Yes' to all actions.
EOF
}
# Initialize and run the script
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' \
EXIT INT TERM SIGINT SIGQUIT
set -o errtrace # Trap errors in subshells and functions
set -o errexit # Exit on error. Append '||true' if you expect an error
set -o pipefail # Use last non-zero exit code in a pipeline
# shopt -s nullglob globstar # Make `for f in *.txt` work when `*.txt` matches zero files
IFS=$' \n\t' # Set IFS to preferred implementation
# set -o xtrace # Run in debug mode
set -o nounset # Disallow expansion of unset variables
# [[ $# -eq 0 ]] && _parseOptions_ "-h" # Force arguments when invoking the script
_parseOptions_ "$@" # Parse arguments passed to script
# _makeTempDir_ "$(basename "$0")" # Create a temp directory '$tmpDir'
# _acquireScriptLock_ # Acquire script lock
_mainScript_ # Run the main logic script
_safeExit_ # Exit cleanly

2
sedfiles/README.md Normal file
View File

@@ -0,0 +1,2 @@
# Sed Files
These files are used by utility functions to perform complex operations with sed. If you plan on using those functions, ensure they point to these files.

View File

@@ -480,8 +480,9 @@ s/&#60;/</g
s/&lt;/</g
s/&#62;/>/g
s/&gt;/>/g
s/&#38;/&/g
s/&amp;/&/g
s/&#38;/\&/g
s/&amp;/\&/g
# http://www.w3schools.com/tags/ref_entities.asp
# ^([^ \t]+)[ \t]+(&amp;[^;]*;)[ \t]+(&amp;[^;]*;).*$
# s/\2/\1/g\ns/\3/\1/g

1293
sedfiles/stopwords.sed Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +0,0 @@
# Setup Scripts Readme File
These are the files I have created to mange the configuration of new computers.
## File Manifest
* **casks.sh** - Installs native Mac applications via [Homebrew Cask][1] casks and runs Brew maintenance scripts.
* **dropbox.sh** - Installs [Dropbox][2] if not already installed.
* **fileTest.sh** - Maintenance script to run occasionally. This ensures that the files called from the `mackup.sh` actually exist. If any of these files fail `mackup.sh` will fail as well.
* **homebrew.sh** - Installs [Homebrew][3] and associated packages and runs Brew maintenance scripts
* **mackup.sh** - This script configures and installs [mackup][4], and then restores my files from Dropbox to their correct locations. **Note:** *This script relies on the existence of certain files in Dropbox to ensure that synching has completed. This list of files needs maintenance from time to time. You can test these files using `fileeTest.sh`*
* **newMackSetup.sh** - This is script calls all of the other scripts in the correct order to configure a new computer from scratch. **Start here**.
* **osx.sh** - This script contains the Mac OSX specific settings.
* **ruby.sh** - This script installs [RVM (Ruby Version Manager)][5] and certain Gems including Jekyll.
* **ssh.sh** - Script to configure SSH and link it to Github.
## USAGE
To configure a new computer simply run the script `newMacSetup.sh` in Terminal.app and follow the onscreen instructions.
[1]: https://github.com/caskroom/homebrew-cask
[2]: http://dropbox.com
[3]: http://brew.sh/
[4]: https://github.com/lra/mackup
[5]: http://rvm.io/

View File

@@ -1,336 +0,0 @@
#!/usr/bin/env bash
# ##################################################
#
# This script was taken in its entirety from:
# https://github.com/rtrouton/rtrouton_scripts/
#
# This script will download a disk image containing the latest Adobe Flash
# Player and install Flash Player using the installer package stored inside
# the downloaded disk image.
#
# How the script works:
#
# 1. Uses curl to download a disk image containing the latest Flash Player
# installer from Adobe's web site
# 2. Renames the downloaded disk image to flash.dmg and stores it in /tmp
# 2. Mounts the disk image silently in /tmp. Disk image will not be visible
# to any logged-in user.
# 3. Installs the latest Flash Player using the installer package stored on
# the disk image
# 4. After installation, unmounts the disk image and removes it from the Mac
# in question.
#
version="1.0.0" # Sets version variable
#
scriptTemplateVersion="1.4.1" # Version of scriptTemplate.sh that this script is based on
#
# HISTORY:
#
# * 2015-06-21 - v1.0.0 - First Creation
#
# ##################################################
# Provide a variable with the location of this script.
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Source Scripting Utilities
# -----------------------------------
# These shared utilities provide many functions which are needed to provide
# the functionality in this boilerplate. This script will fail if they can
# not be found.
# -----------------------------------
utilsLocation="${scriptPath}/../lib/utils.sh" # Update this path to find the utilities.
if [ -f "${utilsLocation}" ]; then
source "${utilsLocation}"
else
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
exit 1
fi
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
function trapCleanup() {
echo ""
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
fi
die "Exit trapped." # Edit this if you like.
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=0
printLog=0
verbose=0
force=0
strict=0
debug=0
args=()
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="$HOME/Library/Logs/${scriptBasename}.log"
# Check for Dependencies
# -----------------------------------
# Arrays containing package dependencies needed to execute this script.
# The script will fail if dependencies are not installed. For Mac users,
# most dependencies can be installed automatically using the package
# manager 'Homebrew'. Mac applications will be installed using
# Homebrew Casks. Ruby and gems via RVM.
# -----------------------------------
homebrewDependencies=()
caskDependencies=()
gemDependencies=()
function mainScript() {
############## Begin Script Here ###################
####################################################
# invoke verbose usage of commands when set
if $verbose; then v="-v" ; fi
# Determine OS version
osvers=$(sw_vers -productVersion | awk -F. '{print $2}')
# Determine current major version of Adobe Flash for use
# with the fileURL variable
flash_major_version=`/usr/bin/curl --silent http://fpdownload2.macromedia.com/get/flashplayer/update/current/xml/version_en_mac_pl.xml | cut -d , -f 1 | awk -F\" '/update version/{print $NF}'`
# Specify the complete address of the Adobe Flash Player
# disk image
fileURL="http://fpdownload.macromedia.com/get/flashplayer/current/licensing/mac/install_flash_player_"${flash_major_version}"_osx_pkg.dmg"
flash_dmg="${tmpDir}/flash.dmg"
if [[ ${osvers} -lt 6 ]]; then
echo "Adobe Flash Player is not available for Mac OS X 10.5.8 or below."
fi
if [[ ${osvers} -ge 6 ]]; then
# Download the latest Adobe Flash Player software disk image
/usr/bin/curl --output "${flash_dmg}" "${fileURL}"
# Specify a /tmp/flashplayer.XXXX mountpoint for the disk image
TMPMOUNT=`/usr/bin/mktemp -d ${tmpDir}/flashplayer.XXXX`
# Mount the latest Flash Player disk image to /tmp/flashplayer.XXXX mountpoint
hdiutil attach "$flash_dmg" -mountpoint "$TMPMOUNT" -nobrowse -noverify -noautoopen
pkg_path="$(/usr/bin/find $TMPMOUNT -maxdepth 1 \( -iname \*Flash*\.pkg -o -iname \*Flash*\.mpkg \))"
# Before installation on Mac OS X 10.7.x and later, the installer's
# developer certificate is checked to see if it has been signed by
# Adobe's developer certificate. Once the certificate check has been
# passed, the package is then installed.
if [[ ${pkg_path} != "" ]]; then
if [[ ${osvers} -ge 7 ]]; then
signature_check=`/usr/sbin/pkgutil --check-signature "$pkg_path" | awk /'Developer ID Installer/{ print $5 }'`
if [[ ${signature_check} = "Adobe" ]]; then
# Install Adobe Flash Player from the installer package stored inside the disk image
/usr/sbin/installer -dumplog -verbose -pkg "${pkg_path}" -target "/"
fi
fi
# On Mac OS X 10.6.x, the developer certificate check is not an
# available option, so the package is just installed.
if [[ ${osvers} -eq 6 ]]; then
# Install Adobe Flash Player from the installer package stored inside the disk image
/usr/sbin/installer -dumplog -verbose -pkg "${pkg_path}" -target "/"
fi
fi
# Clean-up
# Unmount the Flash Player disk image from /tmp/flashplayer.XXXX
/usr/bin/hdiutil detach "$TMPMOUNT"
# Remove the /tmp/flashplayer.XXXX mountpoint
rm -rf $v "$TMPMOUNT"
# Remove the downloaded disk image
rm -rf $v "$flash_dmg"
fi
####################################################
############### End Script Here ####################
}
############## Begin Options and Usage ###################
# Print usage
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
This script was taken in its entirety from:
https://github.com/rtrouton/rtrouton_scripts/
This script will download a disk image containing the latest Adobe Flash
Player and install Flash Player using the installer package stored inside
the downloaded disk image.
How the script works:
1. Uses curl to download a disk image containing the latest Flash Player
installer from Adobe's web site
2. Renames the downloaded disk image to flash.dmg and stores it in /tmp
2. Mounts the disk image silently in /tmp. Disk image will not be visible
to any logged-in user.
3. Installs the latest Flash Player using the installer package stored on
the disk image
4. After installation, unmounts the disk image and removes it from the Mac
in question.
Options:
-q, --quiet Quiet (no output)
-l, --log Print log to file
-s, --strict Exit script with null variables. i.e 'set -o nounset'
-v, --verbose Output more information. (Items echoed to 'verbose')
-d, --debug Runs script in BASH debug mode (set -x)
-h, --help Display this help and exit
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# [[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-u|--username) shift; username=${1} ;;
-p|--password) shift; echo "Enter Pass: "; stty -echo; read PASS; stty echo;
echo ;;
-v|--verbose) verbose=true ;;
-l|--log) printLog=1 ;;
-q|--quiet) quiet=1 ;;
-s|--strict) strict=1;;
-d|--debug) debug=1;;
--force) force=1 ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
############## End Options and Usage ###################
# ############# ############# #############
# ## TIME TO RUN THE SCRIPT ##
# ## ##
# ## You shouldn't need to edit anything ##
# ## beneath this line ##
# ## ##
# ############# ############# #############
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$'\n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
# Run in debug mode, if set
if [ "${debug}" == "1" ]; then
set -x
fi
# Exit on empty variable
if [ "${strict}" == "1" ]; then
set -o nounset
fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Invoke the checkDependenices function to test for Bash packages
# checkDependencies
# Run your script
mainScript
safeExit # Exit cleanly

View File

@@ -1,929 +0,0 @@
#!/usr/bin/env bash
# ##################################################
#
version="1.0.0" # Sets version variable
#
# HISTORY:
#
# * 2016-04-19 - v1.0.0 - First Creation
#
# ##################################################
function mainScript() {
# invoke verbose usage when set
if ${verbose}; then v="-v" ; fi
# Helper Functions
# ###################
function isAppInstalled() {
# Feed this function either the bundleID (com.apple.finder) or a name (finder) for a native
# mac app and it will determine whether it is installed or not
#
# usage: if isAppInstalled 'finder' &>/dev/null; then ...
#
# http://stackoverflow.com/questions/6682335/how-can-check-if-particular-application-software-is-installed-in-mac-os
local appNameOrBundleId="$1" isAppName=0 bundleId
# Determine whether an app *name* or *bundle ID* was specified.
[[ $appNameOrBundleId =~ \.[aA][pP][pP]$ || $appNameOrBundleId =~ ^[^.]+$ ]] && isAppName=1
if (( isAppName )); then # an application NAME was specified
# Translate to a bundle ID first.
bundleId=$(osascript -e "id of application \"$appNameOrBundleId\"" 2>/dev/null) ||
{ echo "$FUNCNAME: ERROR: Application with specified name not found: $appNameOrBundleId" 1>&2; return 1; }
else # a BUNDLE ID was specified
bundleId=$appNameOrBundleId
fi
# Let AppleScript determine the full bundle path.
osascript -e "tell application \"Finder\" to POSIX path of (get application file id \"$bundleId\" as alias)" 2>/dev/null ||
{ echo "$FUNCNAME: ERROR: Application with specified bundle ID not found: $bundleId" 1>&2; return 1; }
}
function brewMaintenance () {
# brewMaintenance
# ------------------------------------------------------
# Will run the recommended Homebrew maintenance scripts
# ------------------------------------------------------
seek_confirmation "Run Homebrew maintenance?"
if is_confirmed; then
brew doctor
brew update
brew upgrade --all
fi
}
function brewCleanup () {
# This function cleans up an initial Homebrew installation
notice "Running Homebrew maintenance..."
# This is where brew stores its binary symlinks
binroot="$(brew --config | awk '/HOMEBREW_PREFIX/ {print $2}')"/bin
if [[ "$(type -P ${binroot}/bash)" && "$(cat /etc/shells | grep -q "$binroot/bash")" ]]; then
info "Adding ${binroot}/bash to the list of acceptable shells"
echo "$binroot/bash" | sudo tee -a /etc/shells >/dev/null
fi
if [[ "$SHELL" != "${binroot}/bash" ]]; then
info "Making ${binroot}/bash your default shell"
sudo chsh -s "${binroot}/bash" "$USER" >/dev/null 2>&1
success "Please exit and restart all your shells."
fi
brew cleanup
if brew cask > /dev/null; then
brew cask cleanup
fi
}
function doInstall () {
# Reads a list of items, checks if they are installed, installs
# those which are needed.
#
# Variables needed are:
# LISTINSTALLED: The command to list all previously installed items
# Ex: "brew list" or "gem list | awk '{print $1}'"
#
# INSTALLCOMMAND: The Install command for the desired items.
# Ex: "brew install" or "gem install"
#
# RECIPES: The list of packages to install.
# Ex: RECIPES=(
# package1
# package2
# )
#
# Credit: https://github.com/cowboy/dotfiles
function to_install() {
local desired installed i desired_s installed_s remain
# Convert args to arrays, handling both space- and newline-separated lists.
read -ra desired < <(echo "$1" | tr '\n' ' ')
read -ra installed < <(echo "$2" | tr '\n' ' ')
# Sort desired and installed arrays.
unset i; while read -r; do desired_s[i++]=$REPLY; done < <(
printf "%s\n" "${desired[@]}" | sort
)
unset i; while read -r; do installed_s[i++]=$REPLY; done < <(
printf "%s\n" "${installed[@]}" | sort
)
# Get the difference. comm is awesome.
unset i; while read -r; do remain[i++]=$REPLY; done < <(
comm -13 <(printf "%s\n" "${installed_s[@]}") <(printf "%s\n" "${desired_s[@]}")
)
echo "${remain[@]}"
}
function checkInstallItems() {
# If we are working with 'cask' we need to dedupe lists
# since apps might be installed by hand
if [[ $INSTALLCOMMAND =~ cask ]]; then
if isAppInstalled "${item}" &>/dev/null; then
continue
fi
fi
# If we installing from mas (mac app store), we need to dedupe the list AND
# sign in to the app store
if [[ $INSTALLCOMMAND =~ mas ]]; then
# Lookup the name of the application being installed
appName="$(curl -s https://itunes.apple.com/lookup?id=$item | jq .results[].trackName)"
if isAppInstalled "${appName}" &> /dev/null; then
continue
fi
# Tell the user the name of the app
notice "$item --> $appName"
fi
}
# Log in to the Mac App Store if using mas
if [[ $INSTALLCOMMAND =~ mas ]]; then
mas signout
input "Please enter your Mac app store username: "
read macStoreUsername
input "Please enter your Mac app store password: "
read -s macStorePass
echo ""
mas signin $macStoreUsername "$macStorePass"
fi
list=($(to_install "${RECIPES[*]}" "$(${LISTINSTALLED})"))
if [ ${#list[@]} -gt 0 ]; then
seek_confirmation "Confirm each package before installing?"
if is_confirmed; then
for item in "${list[@]}"; do
checkInstallItems
seek_confirmation "Install ${item}?"
if is_confirmed; then
notice "Installing ${item}"
# FFMPEG takes additional flags
if [[ "${item}" = "ffmpeg" ]]; then
installffmpeg
elif [[ "${item}" = "tldr" ]]; then
brew tap tldr-pages/tldr
brew install tldr
else
${INSTALLCOMMAND} "${item}"
fi
fi
done
else
for item in "${list[@]}"; do
checkInstallItems
notice "Installing ${item}"
# FFMPEG takes additional flags
if [[ "${item}" = "ffmpeg" ]]; then
installffmpeg
elif [[ "${item}" = "tldr" ]]; then
brew tap tldr-pages/tldr
brew install tldr
else
${INSTALLCOMMAND} "${item}"
fi
done
fi
fi
}
# Installation Commands
# ###################
function installCommandLineTools() {
notice "Checking for Command Line Tools..."
if [[ ! "$(type -P gcc)" || ! "$(type -P make)" ]]; then
local osx_vers=$(sw_vers -productVersion | awk -F "." '{print $2}')
local cmdLineToolsTmp="${tmpDir}/.com.apple.dt.CommandLineTools.installondemand.in-progress"
# Create the placeholder file which is checked by the software update tool
# before allowing the installation of the Xcode command line tools.
touch "${cmdLineToolsTmp}"
# Find the last listed update in the Software Update feed with "Command Line Tools" in the name
cmd_line_tools=$(softwareupdate -l | awk '/\*\ Command Line Tools/ { $1=$1;print }' | tail -1 | sed 's/^[[ \t]]*//;s/[[ \t]]*$//;s/*//' | cut -c 2-)
softwareupdate -i "${cmd_line_tools}" -v
# Remove the temp file
if [ -f "${cmdLineToolsTmp}" ]; then
rm ${v} "${cmdLineToolsTmp}"
fi
fi
success "Command Line Tools installed"
}
function installHomebrew () {
# Check for Homebrew
notice "Checking for Homebrew..."
if [ ! "$(type -P brew)" ]; then
notice "No Homebrew. Gots to install it..."
# Ensure that we can actually, like, compile anything.
if [[ ! $(type -P gcc) && "$OSTYPE" =~ ^darwin ]]; then
notice "XCode or the Command Line Tools for XCode must be installed first."
installCommandLineTools
fi
# Check for Git
if [ ! "$(type -P git)" ]; then
notice "XCode or the Command Line Tools for XCode must be installed first."
installCommandLineTools
fi
# Install Homebrew
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
installHomebrewTaps
fi
success "Homebrew installed"
}
function checkTaps() {
verbose "Confirming we have required Homebrew taps"
if ! brew cask help &>/dev/null; then
installHomebrewTaps
fi
if [ ! "$(type -P mas)" ]; then
installHomebrewTaps
fi
}
function installHomebrewTaps() {
brew tap homebrew/dupes
brew tap homebrew/versions
brew install argon/mas/mas
brew tap argon/mas
brew tap caskroom/cask
# brew tap caskroom/fonts
brew tap caskroom/versions
}
function installXcode() {
notice "Checking for XCode..."
if ! isAppInstalled 'xcode' &>/dev/null; then
unset LISTINSTALLED INSTALLCOMMAND RECIPES
checkTaps
LISTINSTALLED="mas list"
INSTALLCOMMAND="mas install"
RECIPES=(
497799835 #xCode
)
doInstall
# we also accept the license
sudo xcodebuild -license accept
fi
success "XCode installed"
}
function installDropbox () {
# This function checks for Dropbox being installed.
# If it is not found, we install it and its prerequisites
notice "Checking for Dropbox..."
checkTaps
if ! isAppInstalled 'Dropbox' &>/dev/null; then
unset LISTINSTALLED INSTALLCOMMAND RECIPES
LISTINSTALLED="brew cask list"
INSTALLCOMMAND="brew cask install --appdir=/Applications"
RECIPES=(
dropbox
)
doInstall
open -a dropbox
fi
success "Dropbox installed"
}
function installffmpeg () {
notice "Checking for ffmpeg...."
# My preferred install of ffmpeg
if [ ! $(type -P "ffmpeg") ]; then
brew install ffmpeg --with-faac --with-fdk-aac --with-ffplay --with-fontconfig --with-freetype --with-libcaca --with-libass --with-frei0r --with-libass --with-libbluray --with-libcaca --with-libquvi --with-libvidstab --with-libsoxr --with-libssh --with-libvo-aacenc --with-libvidstab --with-libvorbis --with-libvpx --with-opencore-amr --with-openjpeg --with-openssl --with-opus --with-rtmpdump --with-schroedinger --with-speex --with-theora --with-tools --with-webp --with-x265
fi
success "Done ffmpeg installed"
}
function installCaskApps() {
unset LISTINSTALLED INSTALLCOMMAND RECIPES
notice "Checking for casks to install..."
checkTaps
LISTINSTALLED="brew cask list"
INSTALLCOMMAND="brew cask install --appdir=/Applications"
RECIPES=(
alfred
arq
bartender
betterzipql
carbon-copy-cloner
controlplane
dash
default-folder-x
fantastical
firefox
flux
fluid
google-chrome
hazel
houdahgeo
iterm2
istat-menus
java
marked
mailplane
moom
ngrok
nvalt
omnifocus
omnifocus-clip-o-tron
1password
plex-home-theater
qlcolorcode
qlmarkdown
qlprettypatch
qlstephen
quicklook-csv
quicklook-json
skitch
spillo
sublime-text3
textexpander
trickster
vlc
vyprvpn
webpquicklook
xld
)
# for item in "${RECIPES[@]}"; do
# info "$item"
# done
doInstall
success "Done installing cask apps"
}
function installAppStoreApps() {
unset LISTINSTALLED INSTALLCOMMAND RECIPES
notice "Checking for App Store apps to install..."
checkTaps
LISTINSTALLED="mas list"
INSTALLCOMMAND="mas install"
RECIPES=(
836505650 # Battery Monitor
420212497 # Byword
696977615 # Capo
411643860 # DaisyDisk
498944723 # JPEGmini
711830901 # OmniGraffle
429449079 # Patterns - RegEx Validation
445189367 # PopClip
803453959 # Slack
403388562 # Transmit
494803304 # WiFi Explorer
848311469 # Write
)
doInstall
success "Done installing app store apps"
}
function installDevApps() {
unset LISTINSTALLED INSTALLCOMMAND RECIPES
notice "Checking for dev apps to install"
checkTaps
LISTINSTALLED="brew cask list"
INSTALLCOMMAND="brew cask install --appdir=/Applications"
RECIPES=(
charles
codekit
github
imagealpha
imageoptim
java
kaleidoscope
licecap # Movie screen captures
mamp # mac-based LAMP development stack
paw # REST IDE
tower # Mac GUI for git
)
# for item in "${RECIPES[@]}"; do
# info "$item"
# done
doInstall
success "Done installing dev apps"
}
function installHomebrewPackages() {
unset LISTINSTALLED INSTALLCOMMAND RECIPES
notice "Checking for Homebrew packages to install..."
checkTaps
LISTINSTALLED="brew list"
INSTALLCOMMAND="brew install"
RECIPES=(
autoconf
automake
bash
bash-completion
colordiff
coreutils
ffmpeg
gifsicle
git
git-extras
git-flow
hub
hr
id3tool
imagemagick
jpegoptim
jq
lesspipe
libksba
libtool
libyaml
mackup
man2html
multimarkdown
node
openssl
optipng
pkg-config
pngcrush
p7zip
readline
rename
shellcheck # Bash linter
sl
source-highlight
ssh-copy-id
sqlite
tag
terminal-notifier
tldr # Better man pages
tree
unison # Rsynch like tool
)
doInstall
success "Done installing Homebrew packages"
}
function installRuby() {
notice "Checking for RVM (Ruby Version Manager)..."
local RUBYVERSION="2.1.2" # Version of Ruby to install via RVM
# Check for RVM
if [ ! "$(type -P rvm)" ]; then
seek_confirmation "Couldn't find RVM. Install it?"
if is_confirmed; then
curl -L https://get.rvm.io | bash -s stable
source "${HOME}/.rvm/scripts/rvm"
source "${HOME}/.bash_profile"
#rvm get stable --autolibs=enable
rvm install ${RUBYVERSION}
rvm use ${RUBYVERSION} --default
fi
fi
success "RVM and Ruby are installed"
}
function installRubyGems() {
unset LISTINSTALLED INSTALLCOMMAND RECIPES
notice "Checking for Ruby gems..."
LISTINSTALLED="gem list | awk '{print $1}'"
INSTALLCOMMAND="gem install"
RECIPES=(
bundler
classifier
compass
digest
fileutils
jekyll
kramdown
kss
less
logger
mini_magick
rake
reduce
s3_website
sass
smusher
)
doInstall
success "Done installing Ruby Gems"
}
function configureSSH() {
notice "Configuring SSH"
info "Checking for SSH key in ~/.ssh/id_rsa.pub, generating one if it doesn't exist"
[[ -f "${HOME}/.ssh/id_rsa.pub" ]] || ssh-keygen -t rsa
info "Copying public key to clipboard"
[[ -f "${HOME}/.ssh/id_rsa.pub" ]] && cat "${HOME}/.ssh/id_rsa.pub" | pbcopy
# Add SSH keys to Github
seek_confirmation "Add SSH key to Github?"
if is_confirmed; then
info "Paste the key into Github"
open https://github.com/account/ssh
seek_confirmation "Test Github Authentication via ssh?"
if is_confirmed; then
info "Note that even when successful, this will fail the script."
ssh -T git@github.com
fi
fi
success "SSH Configured"
}
function configureMackup() {
notice "Running mackup config..."
local DIRCFG="${HOME}/Dropbox/sharedConfiguration/Mackup"
installDropbox
dropboxFilesTest=(
"Dropbox/sharedConfiguration/Mackup/Library/Application Support/PaxGalaxia/net.txt"
"Dropbox/sharedConfiguration/Mackup/Pictures/DeviantartBackup/clouds2.jpg"
"Dropbox/sharedConfiguration/Mackup/Library/init/bash/aliases.bash"
"Dropbox/sharedConfiguration/Mackup/.mackup/my-files.cfg"
"Dropbox/sharedConfiguration/App Configuration Files/Alfred2/Alfred.alfredpreferences"
"Dropbox/sharedConfiguration/Mackup/Library/Preferences/com.dustinrue.ControlPlane.plist"
)
info "Confirming that Dropbox has synced by looking for files..."
info "(This might fail if the list of files is out of date)"
for dropboxFile in "${dropboxFilesTest[@]}"; do
verbose "Checking: $dropboxFile"
while [ ! -e "${HOME}/${dropboxFile}" ]; do
info " Waiting for Dropbox to Sync files..."
sleep 10
done
done
#Add some additional time just to be sure....
for ((i=1; i<=6; i++)); do
info " Waiting for Dropbox to Sync files..."
sleep 10
done
# Sync Complete
success "Dropbox has synced"
# Confirm Mackup exists
if [ ! "$(type -P mackup)" ]; then
installHomebrew
brew install mackup
fi
notice "Checking for Mackup config files..."
if [ ! -L "${HOME}/.mackup" ]; then
info "Symlinking ~/.mackup"
ln -s "${MACKUPDIR}/.mackup" "${HOME}/.mackup"
else
verbose "${HOME}/.mackup is symlinked"
fi
if [ ! -L "${HOME}/.mackup.cfg" ]; then
info "Symlinking ~/.mackup.cfg"
ln -s "${MACKUPDIR}"/.mackup.cfg "${HOME}"/.mackup.cfg
else
verbose "~${HOME}.mackup.cfg is symlinked"
fi
success "Mackup config files are symlinked"
seek_confirmation "Run Mackup Restore?"
if is_confirmed; then
mackup restore
fi
}
# ###################
# Run the script
# ###################
# Ask for the administrator password upfront
sudo -v
installCommandLineTools
installHomebrew
checkTaps
brewCleanup
installXcode
installDropbox
installHomebrewPackages
installCaskApps
installAppStoreApps
installDevApps
installRuby
installRubyGems
configureSSH
configureMackup
}
## SET SCRIPTNAME VARIABLE ##
scriptName=$(basename "$0")
function trapCleanup() {
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
echo ""
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
die "Exit trapped."
}
function safeExit() {
# safeExit
# -----------------------------------
# Non destructive exit for when script exits naturally.
# Usage: Add this function at the end of every script.
# -----------------------------------
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
trap - INT TERM EXIT
exit
}
function seek_confirmation() {
# Asks questions of a user and then does something with the answer.
# y/n are the only possible answers.
#
# USAGE:
# seek_confirmation "Ask a question"
# if is_confirmed; then
# some action
# else
# some other action
# fi
#
# Credt: https://github.com/kevva/dotfiles
# ------------------------------------------------------
input "$@"
if ${force}; then
notice "Forcing confirmation with '--force' flag set"
else
read -p " (y/n) " -n 1
echo ""
fi
}
function is_confirmed() {
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
return 0
fi
return 1
}
function is_not_confirmed() {
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
return 0
fi
return 1
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=false
printLog=false
verbose=false
force=false
strict=false
debug=false
args=()
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="${HOME}/Library/Logs/${scriptBasename}.log"
# Options and Usage
# -----------------------------------
# Print usage
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
This is a script template. Edit this description to print help to users.
${bold}Options:${reset}
-u, --username Username for script
-p, --password User password
--force Skip all user interaction. Implied 'Yes' to all actions.
-q, --quiet Quiet (no output)
-l, --log Print log to file
-s, --strict Exit script with null variables. i.e 'set -o nounset'
-v, --verbose Output more information. (Items echoed to 'verbose')
-d, --debug Runs script in BASH debug mode (set -x)
-h, --help Display this help and exit
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# -------------------------------------
# [[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-u|--username) shift; username=${1} ;;
-p|--password) shift; echo "Enter Pass: "; stty -echo; read PASS; stty echo;
echo ;;
-v|--verbose) verbose=true ;;
-l|--log) printLog=true ;;
-q|--quiet) quiet=true ;;
-s|--strict) strict=true;;
-d|--debug) debug=true;;
--force) force=true ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
# Logging and Colors
# -----------------------------------------------------
# Here we set the colors for our script feedback.
# Example usage: success "sometext"
#------------------------------------------------------
# Set Colors
bold=$(tput bold)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
function _alert() {
if [ "${1}" = "emergency" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "error" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "warning" ]; then local color="${red}"; fi
if [ "${1}" = "success" ]; then local color="${green}"; fi
if [ "${1}" = "debug" ]; then local color="${purple}"; fi
if [ "${1}" = "header" ]; then local color="${bold}""${tan}"; fi
if [ "${1}" = "input" ]; then local color="${bold}"; printLog="false"; fi
if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi
# Don't use colors on pipes or non-recognized terminals
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi
# Print to $logFile
if ${printLog}; then
echo -e "$(date +"%m-%d-%Y %r") $(printf "[%9s]" "${1}") ${_message}" >> "${logFile}";
fi
# Print to console when script is not 'quiet'
if ${quiet}; then
return
else
echo -e "$(date +"%r") ${color}$(printf "[%9s]" "${1}") ${_message}${reset}";
fi
}
function die () { local _message="${*} Exiting."; echo "$(_alert emergency)"; safeExit;}
function error () { local _message="${*}"; echo "$(_alert error)"; }
function warning () { local _message="${*}"; echo "$(_alert warning)"; }
function notice () { local _message="${*}"; echo "$(_alert notice)"; }
function info () { local _message="${*}"; echo "$(_alert info)"; }
function debug () { local _message="${*}"; echo "$(_alert debug)"; }
function success () { local _message="${*}"; echo "$(_alert success)"; }
function input() { local _message="${*}"; echo -n "$(_alert input)"; }
function header() { local _message="${*}"; echo "$(_alert header)"; }
# Log messages when verbose is set to "true"
verbose() { if ${verbose}; then debug "$@"; fi }
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$' \n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
# Run in debug mode, if set
if ${debug}; then set -x ; fi
# Exit on empty variable
if ${strict}; then set -o nounset ; fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Run your script
mainScript
# Exit cleanly
safeExit

View File

@@ -1,982 +0,0 @@
#!/usr/bin/env bash
# ##################################################
#
version="1.0.0" # Sets version variable
#
# ##################################################
function mainScript() {
header "Beginning script to set OSX preferences"
info "This script runs a series of commands to pre-configure OSX."
function setComputerName() {
seek_confirmation "Would you like to set your computer name (as done via System Preferences >> Sharing)?"
if is_confirmed; then
input "What would you like the name to be?"
read COMPUTER_NAME
sudo scutil --set ComputerName "$COMPUTER_NAME"
sudo scutil --set HostName "$COMPUTER_NAME"
sudo scutil --set LocalHostName "$COMPUTER_NAME"
sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName -string "$COMPUTER_NAME"
fi
}
function generalUITPrefs() {
seek_confirmation "Run General UI Tweaks?"
if is_confirmed; then
success "Disabled Sound Effects on Boot"
sudo nvram SystemAudioVolume=" "
success "Hide the Time Machine, Volume, User, and Bluetooth icons"
# Get the system Hardware UUID and use it for the next menubar stuff
for domain in ~/Library/Preferences/ByHost/com.apple.systemuiserver.*; do
defaults write "${domain}" dontAutoLoad -array \
"/System/Library/CoreServices/Menu Extras/TimeMachine.menu" \
"/System/Library/CoreServices/Menu Extras/Volume.menu" \
"/System/Library/CoreServices/Menu Extras/User.menu"
done
defaults write com.apple.systemuiserver menuExtras -array \
"/System/Library/CoreServices/Menu Extras/Bluetooth.menu" \
"/System/Library/CoreServices/Menu Extras/AirPort.menu" \
"/System/Library/CoreServices/Menu Extras/Battery.menu" \
"/System/Library/CoreServices/Menu Extras/Clock.menu"
success "Set highlight color to yellow"
defaults write NSGlobalDomain AppleHighlightColor -string '0.984300 0.929400 0.450900'
success "Set sidebar icon size to small"
defaults write NSGlobalDomain NSTableViewDefaultSizeMode -int 1
# Possible values for int: 1=small, 2=medium
success "Always show scrollbars"
defaults write NSGlobalDomain AppleShowScrollBars -string "Always"
# Possible values: `WhenScrolling`, `Automatic` and `Always`
#success "Disable transparency in the menu bar and elsewhere on Yosemite"
#defaults write com.apple.universalaccess reduceTransparency -bool true
success "Disable opening and closing window animations"
defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool false
success "Expand save panel by default"
defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true
defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode2 -bool true
success "Expand print panel by default"
defaults write NSGlobalDomain PMPrintingExpandedStateForPrint -bool true
defaults write NSGlobalDomain PMPrintingExpandedStateForPrint2 -bool true
success "Save to disk (not to iCloud) by default"
defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
success "Automatically quit printer app once the print jobs complete"
defaults write com.apple.print.PrintingPrefs "Quit When Finished" -bool true
success "Disable the 'Are you sure you want to open this application?' dialog"
defaults write com.apple.LaunchServices LSQuarantine -bool false
success "General:Display ASCII control characters using caret notation in standard text views"
# Try e.g. `cd /tmp; unidecode "\x{0000}" > cc.txt; open -e cc.txt`
defaults write NSGlobalDomain NSTextShowsControlCharacters -bool true
success "Disable automatic termination of inactive apps"
defaults write NSGlobalDomain NSDisableAutomaticTermination -bool true
success "Disable Resume system-wide"
defaults write com.apple.systempreferences NSQuitAlwaysKeepsWindows -bool false
success "Set Help Viewer windows to non-floating mode"
defaults write com.apple.helpviewer DevMode -bool true
success "Reveal info when clicking the clock in the login window"
sudo defaults write /Library/Preferences/com.apple.loginwindow AdminHostInfo HostName
#success "Restart automatically if the computer freezes"
#systemsetup -setrestartfreeze on
#success "Never go into computer sleep mode"
#systemsetup -setcomputersleep Off > /dev/null
success "Check for software updates daily, not just once per week"
defaults write com.apple.SoftwareUpdate ScheduleFrequency -int 1
#success "Disable Notification Center and remove the menu bar icon"
#launchctl unload -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist 2> /dev/null
success "Disabled smart quotes as they are annoying when typing code"
defaults write NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled -bool false
success "Disabled smart dashes as they are annoying when typing code"
defaults write NSGlobalDomain NSAutomaticDashSubstitutionEnabled -bool false
success "Removing duplicates in the 'Open With' menu"
#/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user
#success "Disable hibernation? (speeds up entering sleep mode)"
#sudo pmset -a hibernatemode 0
fi
}
function inputDevicePrefs() {
seek_confirmation "Run Trackpad, Mouse, Keyboard Tweaks?"
if is_confirmed; then
#success "Trackpad: enable tap to click for this user and for the login screen"
#defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true
#defaults -currentHost write NSGlobalDomain com.apple.mouse.tapBehavior -int 1
#defaults write NSGlobalDomain com.apple.mouse.tapBehavior -int 1
# success "Trackpad: map bottom right corner to right-click"
# defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad TrackpadCornerSecondaryClick -int 2
# defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad TrackpadRightClick -bool true
# defaults -currentHost write NSGlobalDomain com.apple.trackpad.trackpadCornerClickBehavior -int 1
# defaults -currentHost write NSGlobalDomain com.apple.trackpad.enableSecondaryClick -bool true
# success "Disable “natural” (Lion-style) scrolling"
# defaults write NSGlobalDomain com.apple.swipescrolldirection -bool false
success "Setting trackpad & mouse speed to a reasonable number"
defaults write -g com.apple.trackpad.scaling 2
defaults write -g com.apple.mouse.scaling 2.5
success "Increase sound quality for Bluetooth headphones/headsets"
defaults write com.apple.BluetoothAudioAgent "Apple Bitpool Min (editable)" -int 40
success "Enable full keyboard access for all controls"
# (e.g. enable Tab in modal dialogs)
defaults write NSGlobalDomain AppleKeyboardUIMode -int 3
success "Use scroll gesture with the Ctrl (^) modifier key to zoom"
defaults write com.apple.universalaccess closeViewScrollWheelToggle -bool true
defaults write com.apple.universalaccess HIDScrollZoomModifierMask -int 262144
# Follow the keyboard focus while zoomed in
defaults write com.apple.universalaccess closeViewZoomFollowsFocus -bool true
success "Disable press-and-hold for keys in favor of key repeat"
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false
success "Set a blazingly fast keyboard repeat rate"
defaults write NSGlobalDomain KeyRepeat -int 0.02
success "Automatically illuminate built-in MacBook keyboard in low light"
defaults write com.apple.BezelServices kDim -bool true
success "Turn off keyboard illumination when computer is not used for 5 minutes"
defaults write com.apple.BezelServices kDimTime -int 300
success "Set language and text formats"
# Note: if youre in the US, replace `EUR` with `USD`, `Centimeters` with
# `Inches`, `en_GB` with `en_US`, and `true` with `false`.
defaults write NSGlobalDomain AppleLanguages -array "en" "nl"
defaults write NSGlobalDomain AppleLocale -string "en_US@currency=USD"
defaults write NSGlobalDomain AppleMeasurementUnits -string "Inches"
defaults write NSGlobalDomain AppleMetricUnits -bool false
success "Set the timezone"
systemsetup -settimezone "America/New_York" > /dev/null
#see `systemsetup -listtimezones` for other values
#success "Disable spelling auto-correct"
#defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false
# Stop iTunes from responding to the keyboard media keys
#launchctl unload -w /System/Library/LaunchAgents/com.apple.rcd.plist 2> /dev/null
fi
}
function screenPrefs() {
seek_confirmation "Run Screen Configurations?"
if is_confirmed; then
success "Require password immediately after sleep or screen saver begins"
defaults write com.apple.screensaver askForPassword -int 1
defaults write com.apple.screensaver askForPasswordDelay -int 0
success "Save screenshots to the desktop"
defaults write com.apple.screencapture location -string "${HOME}/Desktop"
success "Save screenshots in PNG format"
defaults write com.apple.screencapture type -string "png"
# other options: BMP, GIF, JPG, PDF, TIFF, PNG
#success "Disable shadow in screenshots"
#defaults write com.apple.screencapture disable-shadow -bool true
success "Enable subpixel font rendering on non-Apple LCDs"
defaults write NSGlobalDomain AppleFontSmoothing -int 2
#success "Enabling HiDPI display modes (requires restart)"
#sudo defaults write /Library/Preferences/com.apple.windowserver DisplayResolutionEnabled -bool true
fi
}
function finderPrefs() {
seek_confirmation "Run Finder Tweaks?"
if is_confirmed; then
success "Finder: allow quitting via ⌘ + Q"
defaults write com.apple.finder QuitMenuItem -bool true
success "Finder: disable window animations and Get Info animations"
defaults write com.apple.finder DisableAllAnimations -bool true
success "Set Home Folder as the default location for new Finder windows"
# For other paths, use `PfLo` and `file:///full/path/here/`
defaults write com.apple.finder NewWindowTarget -string "PfHm"
defaults write com.apple.finder NewWindowTargetPath -string "file://${HOME}/"
success "Show icons for hard drives, servers, and removable media on the desktop"
defaults write com.apple.finder ShowExternalHardDrivesOnDesktop -bool true
defaults write com.apple.finder ShowHardDrivesOnDesktop -bool true
defaults write com.apple.finder ShowMountedServersOnDesktop -bool true
defaults write com.apple.finder ShowRemovableMediaOnDesktop -bool true
#success "Finder: show hidden files by default"
#defaults write com.apple.finder AppleShowAllFiles -bool true
success "Finder: show all filename extensions"
defaults write NSGlobalDomain AppleShowAllExtensions -bool true
success "Finder: show status bar"
defaults write com.apple.finder ShowStatusBar -bool true
success "Finder: show path bar"
defaults write com.apple.finder ShowPathbar -bool true
success "Finder: allow text selection in Quick Look"
defaults write com.apple.finder QLEnableTextSelection -bool true
#success "Display full POSIX path as Finder window title"
#defaults write com.apple.finder _FXShowPosixPathInTitle -bool true
success "When performing a search, search the current folder by default"
defaults write com.apple.finder FXDefaultSearchScope -string "SCcf"
success "Disable the warning when changing a file extension"
defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false
success "Enable spring loading for directories"
defaults write NSGlobalDomain com.apple.springing.enabled -bool true
success "Remove the spring loading delay for directories"
defaults write NSGlobalDomain com.apple.springing.delay -float 0
success "Avoid creating .DS_Store files on network volumes"
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true
defaults write com.apple.desktopservices DSDontWriteUSBStores -bool true
success "Disable disk image verification"
defaults write com.apple.frameworks.diskimages skip-verify -bool true
defaults write com.apple.frameworks.diskimages skip-verify-locked -bool true
defaults write com.apple.frameworks.diskimages skip-verify-remote -bool true
# success "Automatically open a new Finder window when a volume is mounted"
# defaults write com.apple.frameworks.diskimages auto-open-ro-root -bool true
# defaults write com.apple.frameworks.diskimages auto-open-rw-root -bool true
# defaults write com.apple.finder OpenWindowForNewRemovableDisk -bool true
success "Show item info to the right of the icons on the desktop"
/usr/libexec/PlistBuddy -c "Set DesktopViewSettings:IconViewSettings:labelOnBottom false" ~/Library/Preferences/com.apple.finder.plist
success "Enable snap-to-grid for icons on the desktop and in other icon views"
/usr/libexec/PlistBuddy -c "Set DesktopViewSettings:IconViewSettings:arrangeBy grid" ~/Library/Preferences/com.apple.finder.plist
/usr/libexec/PlistBuddy -c "Set StandardViewSettings:IconViewSettings:arrangeBy grid" ~/Library/Preferences/com.apple.finder.plist
success "Increase grid spacing for icons on the desktop and in other icon views"
/usr/libexec/PlistBuddy -c "Set DesktopViewSettings:IconViewSettings:gridSpacing 100" ~/Library/Preferences/com.apple.finder.plist
/usr/libexec/PlistBuddy -c "Set StandardViewSettings:IconViewSettings:gridSpacing 100" ~/Library/Preferences/com.apple.finder.plist
success "Increase the size of icons on the desktop and in other icon views"
/usr/libexec/PlistBuddy -c "Set DesktopViewSettings:IconViewSettings:iconSize 40" ~/Library/Preferences/com.apple.finder.plist
/usr/libexec/PlistBuddy -c "Set StandardViewSettings:IconViewSettings:iconSize 40" ~/Library/Preferences/com.apple.finder.plist
success "Use column view in all Finder windows by default"
defaults write com.apple.finder FXPreferredViewStyle -string "clmv"
# Four-letter codes for the other view modes: `icnv`, `clmv`, `Flwv`, `Nlsv`
success "Disable the warning before emptying the Trash"
defaults write com.apple.finder WarnOnEmptyTrash -bool false
# success "Empty Trash securely by default"
# defaults write com.apple.finder EmptyTrashSecurely -bool true
success "Show the ~/Library folder"
chflags nohidden ${HOME}/Library
success "Show the /Volumes folder"
sudo chflags nohidden /Volumes
#success "Remove Dropboxs green checkmark icons in Finder"
#file=/Applications/Dropbox.app/Contents/Resources/emblem-dropbox-uptodate.icns
#[ -e "${file}" ] && mv -f "${file}" "${file}.bak"
success "Expand File Info panes"
# “General”, “Open with”, and “Sharing & Permissions”
defaults write com.apple.finder FXInfoPanesExpanded -dict \
General -bool true \
OpenWith -bool true \
Privileges -bool true
# Enable AirDrop over Ethernet and on unsupported Macs running Lion
# defaults write com.apple.NetworkBrowser BrowseAllInterfaces -bool true
fi
}
function dockDashPrefs() {
seek_confirmation "Configure Dock, Dashboard, Corners?"
if is_confirmed; then
success "Enable highlight hover effect for the grid view of a stack"
defaults write com.apple.dock mouse-over-hilite-stack -bool true
success "Change minimize/maximize window effect"
defaults write com.apple.dock mineffect -string "genie"
success "Set the icon size of Dock items to 36 pixels"
defaults write com.apple.dock tilesize -int 36
success "Show only open applications in the Dock"
defaults write com.apple.dock static-only -bool true
success "Minimize windows into their applications icon"
defaults write com.apple.dock minimize-to-application -bool true
success "Enable spring loading for all Dock items"
defaults write com.apple.dock enable-spring-load-actions-on-all-items -bool true
success "Show indicator lights for open applications in the Dock"
defaults write com.apple.dock show-process-indicators -bool true
success "Wipe all (default) app icons from the Dock"
# This is only really useful when setting up a new Mac, or if you dont use
# the Dock to launch apps.
defaults write com.apple.dock persistent-apps -array
success "Disable App Persistence (re-opening apps on login)"
defaults write -g ApplePersistence -bool no
success "Dont animate opening applications from the Dock"
defaults write com.apple.dock launchanim -bool false
success "Speed up Mission Control animations"
defaults write com.apple.dock expose-animation-duration -float 0.1
# success "Dont group windows by application in Mission Control"
# # (i.e. use the old Exposé behavior instead)
# defaults write com.apple.dock expose-group-by-app -bool false
success "Disable Dashboard"
defaults write com.apple.dashboard mcx-disabled -bool true
success "Dont show Dashboard as a Space"
defaults write com.apple.dock dashboard-in-overlay -bool true
# success "Dont automatically rearrange Spaces based on most recent use"
# defaults write com.apple.dock mru-spaces -bool false
success "Remove the auto-hiding Dock delay"
defaults write com.apple.dock autohide-delay -float 0
#success "Remove the animation when hiding/showing the Dock"
#defaults write com.apple.dock autohide-time-modifier -float 0
success "Automatically hide and show the Dock"
defaults write com.apple.dock autohide -bool true
success "Make Dock icons of hidden applications translucent"
defaults write com.apple.dock showhidden -bool true
# Add a spacer to the left side of the Dock (where the applications are)
#defaults write com.apple.dock persistent-apps -array-add '{tile-data={}; tile-type="spacer-tile";}'
# Add a spacer to the right side of the Dock (where the Trash is)
#defaults write com.apple.dock persistent-others -array-add '{tile-data={}; tile-type="spacer-tile";}'
success "Disabled hot corners"
# Possible values:
# 0: no-op
# 2: Mission Control
# 3: Show application windows
# 4: Desktop
# 5: Start screen saver
# 6: Disable screen saver
# 7: Dashboard
# 10: Put display to sleep
# 11: Launchpad
# 12: Notification Center
# Top left screen corner → Mission Control
defaults write com.apple.dock wvous-tl-corner -int 0
defaults write com.apple.dock wvous-tl-modifier -int 0
# Top right screen corner → Desktop
defaults write com.apple.dock wvous-tr-corner -int 0
defaults write com.apple.dock wvous-tr-modifier -int 0
# Bottom left screen corner → Start screen saver
defaults write com.apple.dock wvous-bl-corner -int 0
defaults write com.apple.dock wvous-bl-modifier -int 0
fi
}
function safariPrefs() {
seek_confirmation "Safari & Webkit tweaks?"
if is_confirmed; then
success "Privacy: dont send search queries to Apple"
defaults write com.apple.Safari UniversalSearchEnabled -bool false
defaults write com.apple.Safari SuppressSearchSuggestions -bool true
success "Show the full URL in the address bar (note: this still hides the scheme)"
defaults write com.apple.Safari ShowFullURLInSmartSearchField -bool true
success "Set Safaris home page to about:blank for faster loading"
defaults write com.apple.Safari HomePage -string "about:blank"
success "Prevent Safari from opening safe files automatically after downloading"
defaults write com.apple.Safari AutoOpenSafeDownloads -bool false
# success "Allow hitting the Backspace key to go to the previous page in history"
# defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2BackspaceKeyNavigationEnabled -bool true
# # Hide Safaris bookmarks bar by default
# defaults write com.apple.Safari ShowFavoritesBar -bool false
# # Hide Safaris sidebar in Top Sites
# defaults write com.apple.Safari ShowSidebarInTopSites -bool false
# # Disable Safaris thumbnail cache for History and Top Sites
# defaults write com.apple.Safari DebugSnapshotsUpdatePolicy -int 2
success "Enable Safaris debug menu"
defaults write com.apple.Safari IncludeInternalDebugMenu -bool true
success "Make Safaris search banners default to Contains instead of Starts With"
defaults write com.apple.Safari FindOnPageMatchesWordStartsOnly -bool false
success "Remove useless icons from Safaris bookmarks bar"
defaults write com.apple.Safari ProxiesInBookmarksBar "()"
success "Enable the Develop menu and the Web Inspector in Safari"
defaults write com.apple.Safari IncludeDevelopMenu -bool true
defaults write com.apple.Safari WebKitDeveloperExtrasEnabledPreferenceKey -bool true
defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled -bool true
success "Add a context menu item for showing the Web Inspector in web views"
defaults write NSGlobalDomain WebKitDeveloperExtras -bool true
fi
}
function mailPrefs() {
seek_confirmation "Configure Mail.app?"
if is_confirmed; then
success "Disable send and reply animations in Mail.app"
defaults write com.apple.mail DisableReplyAnimations -bool true
defaults write com.apple.mail DisableSendAnimations -bool true
success "Copy sane email addresses to clipboard"
# Copy email addresses as `foo@example.com` instead of `Foo Bar <foo@example.com>` in Mail.app
defaults write com.apple.mail AddressesIncludeNameOnPasteboard -bool false
#success "Add the keyboard shortcut ⌘ + Enter to send an email in Mail.app"
#defaults write com.apple.mail NSUserKeyEquivalents -dict-add "Send" -string "@\\U21a9"
success "Display emails in threaded mode, sorted by date (newest at the top)"
defaults write com.apple.mail DraftsViewerAttributes -dict-add "DisplayInThreadedMode" -string "yes"
defaults write com.apple.mail DraftsViewerAttributes -dict-add "SortedDescending" -string "no"
defaults write com.apple.mail DraftsViewerAttributes -dict-add "SortOrder" -string "received-date"
#success "Disable inline attachments (just show the icons)"
#defaults write com.apple.mail DisableInlineAttachmentViewing -bool false
fi
}
function spotlightPrefs() {
seek_confirmation "Configure Spotlight?"
if is_confirmed; then
# Hide Spotlight tray-icon (and subsequent helper)
#sudo chmod 600 /System/Library/CoreServices/Search.bundle/Contents/MacOS/Search
success "Disabled Spotlight indexing for any new mounted volume"
# Use `sudo mdutil -i off "/Volumes/foo"` to stop indexing any volume.
sudo defaults write /.Spotlight-V100/VolumeConfiguration Exclusions -array "/Volumes"
success "Change indexing order and disable some file types"
# Yosemite-specific search results (remove them if your are using OS X 10.9 or older):
# MENU_DEFINITION
# MENU_CONVERSION
# MENU_EXPRESSION
# MENU_SPOTLIGHT_SUGGESTIONS (send search queries to Apple)
# MENU_WEBSEARCH (send search queries to Apple)
# MENU_OTHER
# defaults write com.apple.spotlight orderedItems -array \
# '{"enabled" = 1;"name" = "APPLICATIONS";}' \
# '{"enabled" = 1;"name" = "SYSTEM_PREFS";}' \
# '{"enabled" = 1;"name" = "DIRECTORIES";}' \
# '{"enabled" = 1;"name" = "PDF";}' \
# '{"enabled" = 1;"name" = "FONTS";}' \
# '{"enabled" = 0;"name" = "DOCUMENTS";}' \
# '{"enabled" = 0;"name" = "MESSAGES";}' \
# '{"enabled" = 0;"name" = "CONTACT";}' \
# '{"enabled" = 0;"name" = "EVENT_TODO";}' \
# '{"enabled" = 0;"name" = "IMAGES";}' \
# '{"enabled" = 0;"name" = "BOOKMARKS";}' \
# '{"enabled" = 0;"name" = "MUSIC";}' \
# '{"enabled" = 0;"name" = "MOVIES";}' \
# '{"enabled" = 0;"name" = "PRESENTATIONS";}' \
# '{"enabled" = 0;"name" = "SPREADSHEETS";}' \
# '{"enabled" = 0;"name" = "SOURCE";}' \
# '{"enabled" = 0;"name" = "MENU_DEFINITION";}' \
# '{"enabled" = 0;"name" = "MENU_OTHER";}' \
# '{"enabled" = 0;"name" = "MENU_CONVERSION";}' \
# '{"enabled" = 0;"name" = "MENU_EXPRESSION";}' \
# '{"enabled" = 0;"name" = "MENU_WEBSEARCH";}' \
# '{"enabled" = 0;"name" = "MENU_SPOTLIGHT_SUGGESTIONS";}'
# Load new settings before rebuilding the index
# killall mds > /dev/null 2>&1
# Make sure indexing is enabled for the main volume
#sudo mdutil -i on / > /dev/null
# Rebuild the index from scratch
#sudo mdutil -E / > /dev/null
fi
}
function terminalPrefs() {
seek_confirmation "Configure Terminal.app?"
if is_confirmed; then
success "Only use UTF-8 in Terminal.app"
defaults write com.apple.terminal StringEncodings -array 4
# Use a modified version of the Pro theme by default in Terminal.app
open "${HOME}/Dropbox/sharedConfiguration/App Configuration Files/Terminal/solarizedDark.terminal"
sleep 2 # Wait a bit to make sure the theme is loaded
defaults write com.apple.terminal "Default Window Settings" -string "solarizedDark"
defaults write com.apple.terminal "Startup Window Settings" -string "solarizedDark"
# Enable “focus follows mouse” for Terminal.app and all X11 apps
# i.e. hover over a window and start typing in it without clicking first
#defaults write com.apple.terminal FocusFollowsMouse -bool true
#defaults write org.x.X11 wm_ffm -bool true
fi
seek_confirmation "Configure iTerm2?"
if is_confirmed; then
success "Installed pretty iTerm colors"
open "${HOME}/Dropbox/sharedConfiguration/App Configuration Files/iTerm/nate.itermcolors"
success "Don't display the annoying prompt when quitting iTerm"
defaults write com.googlecode.iterm2 PromptOnQuit -bool false
fi
}
function timeMachinePrefs() {
seek_confirmation "Disable Time Machine?"
if is_confirmed; then
success "Prevent Time Machine from prompting to use new hard drives as backup volume"
defaults write com.apple.TimeMachine DoNotOfferNewDisksForBackup -bool true
success "Disable local Time Machine backups"
hash tmutil &> /dev/null && sudo tmutil disablelocal
fi
}
function applicationPrefs() {
seek_confirmation "Configure Activity Monitor?"
if is_confirmed; then
success "Show the main window when launching Activity Monitor"
defaults write com.apple.ActivityMonitor OpenMainWindow -bool true
success "Visualize CPU usage in the Activity Monitor Dock icon"
defaults write com.apple.ActivityMonitor IconType -int 5
success "Show all processes in Activity Monitor"
defaults write com.apple.ActivityMonitor ShowCategory -int 0
success "Sort Activity Monitor results by CPU usage"
defaults write com.apple.ActivityMonitor SortColumn -string "CPUUsage"
defaults write com.apple.ActivityMonitor SortDirection -int 0
fi
seek_confirmation "Stop Photos from opening whenever a camera is connected?"
if is_confirmed; then
defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool YES
fi
seek_confirmation "Configure Google Chrome?"
if is_confirmed; then
# Use the system-native print preview dialog
defaults write com.google.Chrome DisablePrintPreview -bool true
defaults write com.google.Chrome.canary DisablePrintPreview -bool true
fi
seek_confirmation "Configure Contacts, Calendar, TextEdit, Disk Util?"
if is_confirmed; then
success "Enable the debug menu in Address Book"
defaults write com.apple.addressbook ABShowDebugMenu -bool true
# Enable Dashboard dev mode (allows keeping widgets on the desktop)
# defaults write com.apple.dashboard devmode -bool true
# Enable the debug menu in iCal (pre-10.8)
# defaults write com.apple.iCal IncludeDebugMenu -bool true
success "Use plain text mode for new TextEdit documents"
defaults write com.apple.TextEdit RichText -int 0
success "Open and save files as UTF-8 in TextEdit"
defaults write com.apple.TextEdit PlainTextEncoding -int 4
defaults write com.apple.TextEdit PlainTextEncodingForWrite -int 4
success "Enable the debug menu in Disk Utility"
defaults write com.apple.DiskUtility DUDebugMenuEnabled -bool true
defaults write com.apple.DiskUtility advanced-image-options -bool true
fi
seek_confirmation "Configure Sublime Text 3 in Terminal?"
if is_confirmed; then
if [ ! -e "/Applications/Sublime Text.app" ]; then
error "We don't have Sublime Text.app. Get it installed and try again."
else
if [ ! -e "/usr/local/bin/subl" ]; then
ln -s "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" /usr/local/bin/subl
success "Symlink created."
else
notice "Symlink already exists. Nothing done."
fi
fi
fi
seek_confirmation "Configure Messages.app?"
if is_confirmed; then
success "Disable automatic emoji substitution in Messages.app? (i.e. use plain text smileys) (y/n)"
defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticEmojiSubstitutionEnablediMessage" -bool false
success "Disable smart quotes in Messages.app? (it's annoying for messages that contain code) (y/n)"
defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticQuoteSubstitutionEnabled" -bool false
success "Disabled continuous spell checking in Messages.app? (y/n)"
defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "continuousSpellCheckingEnabled" -bool false
fi
}
function ssdPrefs() {
seek_confirmation "Confirm that you have an SSD Hard Drive and want to disable sudden motion sensor."
if is_confirmed; then
success "Remove the sleep image file to save disk space"
sudo rm /Private/var/vm/sleepimage
success "Create a zero-byte file instead…"
sudo touch /Private/var/vm/sleepimage
success "…and make sure it cant be rewritten"
sudo chflags uchg /Private/var/vm/sleepimage
success "Disable the sudden motion sensor as its not useful for SSDs"
sudo pmset -a sms 0
fi
}
function cleanup() {
seek_confirmation "Kill all effected applications?"
if is_confirmed; then
for app in "Activity Monitor" "Address Book" "Calendar" "Contacts" "cfprefsd" \
"Dock" "Finder" "Mail" "Messages" "Safari" "SystemUIServer" \
"Terminal" "iCal"; do
killall "${app}" > /dev/null 2>&1
done
success "Apps killed"
fi
notice "Some of these changes require a logout/restart to take effect."
}
# Run the script
# ###############
# Ask for the administrator password upfront
sudo -v
setComputerName
generalUITPrefs
inputDevicePrefs
screenPrefs
finderPrefs
dockDashPrefs
safariPrefs
mailPrefs
spotlightPrefs
terminalPrefs
timeMachinePrefs
applicationPrefs
ssdPrefs
cleanup
}
## SET SCRIPTNAME VARIABLE ##
scriptName=$(basename "$0")
function trapCleanup() {
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
echo ""
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
die "Exit trapped."
}
function safeExit() {
# safeExit
# -----------------------------------
# Non destructive exit for when script exits naturally.
# Usage: Add this function at the end of every script.
# -----------------------------------
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
trap - INT TERM EXIT
exit
}
function seek_confirmation() {
# Asks questions of a user and then does something with the answer.
# y/n are the only possible answers.
#
# USAGE:
# seek_confirmation "Ask a question"
# if is_confirmed; then
# some action
# else
# some other action
# fi
#
# Credt: https://github.com/kevva/dotfiles
# ------------------------------------------------------
# echo ""
input "$@"
if ${force}; then
notice "Forcing confirmation with '--force' flag set"
else
read -p " (y/n) " -n 1
echo ""
fi
}
function is_confirmed() {
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
return 0
fi
return 1
}
function is_not_confirmed() {
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
return 0
fi
return 1
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=false
printLog=false
verbose=false
force=false
strict=false
debug=false
args=()
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="${HOME}/Library/Logs/${scriptBasename}.log"
# Options and Usage
# -----------------------------------
# Print usage
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
This script configures Mac OS default preferences. Common usage is to bootstrap
a new computer at first use.
${bold}Options:${reset}
--force Skip all user interaction. Implied 'Yes' to all actions.
-q, --quiet Quiet (no output)
-l, --log Print log to file
-s, --strict Exit script with null variables. i.e 'set -o nounset'
-v, --verbose Output more information. (Items echoed to 'verbose')
-d, --debug Runs script in BASH debug mode (set -x)
-h, --help Display this help and exit
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# -------------------------------------
# [[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-v|--verbose) verbose=true ;;
-l|--log) printLog=true ;;
-q|--quiet) quiet=true ;;
-s|--strict) strict=true;;
-d|--debug) debug=true;;
--force) force=true ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
# Logging and Colors
# -----------------------------------------------------
# Here we set the colors for our script feedback.
# Example usage: success "sometext"
#------------------------------------------------------
# Set Colors
bold=$(tput bold)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
function _alert() {
if [ "${1}" = "emergency" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "error" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "warning" ]; then local color="${red}"; fi
if [ "${1}" = "success" ]; then local color="${green}"; fi
if [ "${1}" = "debug" ]; then local color="${purple}"; fi
if [ "${1}" = "header" ]; then local color="${bold}${tan}"; fi
if [ "${1}" = "input" ]; then local color="${bold}"; printLog="false"; fi
if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi
# Don't use colors on pipes or non-recognized terminals
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi
# Print to $logFile
if ${printLog}; then
echo -e "$(date +"%m-%d-%Y %r") $(printf "[%9s]" "${1}") ${_message}" >> "${logFile}";
fi
# Print to console when script is not 'quiet'
if ${quiet}; then
return
else
echo -e "$(date +"%r") ${color}$(printf "[%9s]" "${1}") ${_message}${reset}";
fi
}
function die () { local _message="${*} Exiting."; echo "$(_alert emergency)"; safeExit;}
function error () { local _message="${*}"; echo "$(_alert error)"; }
function warning () { local _message="${*}"; echo "$(_alert warning)"; }
function notice () { local _message="${*}"; echo "$(_alert notice)"; }
function info () { local _message="${*}"; echo "$(_alert info)"; }
function debug () { local _message="${*}"; echo "$(_alert debug)"; }
function success () { local _message="${*}"; echo "$(_alert success)"; }
function input() { local _message="${*}"; echo -n "$(_alert input)"; }
function header() { local _message="${*}"; echo "$(_alert header)"; }
# Log messages when verbose is set to "true"
verbose() { if ${verbose}; then debug "$@"; fi }
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$' \n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
# Run in debug mode, if set
if ${debug}; then set -x ; fi
# Exit on empty variable
if ${strict}; then set -o nounset ; fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Run your script
mainScript
# Exit cleanly
safeExit

View File

@@ -1,250 +0,0 @@
#!/usr/bin/env bash
# ##################################################
#
version="1.0.0" # Sets version variable
#
# HISTORY:
#
# * DATE - v1.0.0 - First Creation
#
# ##################################################
function mainScript() {
echo -n
}
function trapCleanup() {
echo ""
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
die "Exit trapped. In function: '${FUNCNAME[*]}'"
}
function safeExit() {
# Delete temp files, if any
if [ -d "${tmpDir}" ] ; then
rm -r "${tmpDir}"
fi
trap - INT TERM EXIT
exit
}
# Set Base Variables
# ----------------------
scriptName=$(basename "$0")
# Set Flags
quiet=false
printLog=false
verbose=false
force=false
strict=false
debug=false
args=()
# Set Colors
bold=$(tput bold)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
# Set Temp Directory
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
logFile="${HOME}/Library/Logs/${scriptBasename}.log"
# Options and Usage
# -----------------------------------
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
This is a script template. Edit this description to print help to users.
${bold}Options:${reset}
-u, --username Username for script
-p, --password User password
--force Skip all user interaction. Implied 'Yes' to all actions.
-q, --quiet Quiet (no output)
-l, --log Print log to file
-s, --strict Exit script with null variables. i.e 'set -o nounset'
-v, --verbose Output more information. (Items echoed to 'verbose')
-d, --debug Runs script in BASH debug mode (set -x)
-h, --help Display this help and exit
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# -------------------------------------
[[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-u|--username) shift; username=${1} ;;
-p|--password) shift; echo "Enter Pass: "; stty -echo; read PASS; stty echo;
echo ;;
-v|--verbose) verbose=true ;;
-l|--log) printLog=true ;;
-q|--quiet) quiet=true ;;
-s|--strict) strict=true;;
-d|--debug) debug=true;;
--force) force=true ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
# Logging & Feedback
# -----------------------------------------------------
function _alert() {
if [ "${1}" = "error" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "warning" ]; then local color="${red}"; fi
if [ "${1}" = "success" ]; then local color="${green}"; fi
if [ "${1}" = "debug" ]; then local color="${purple}"; fi
if [ "${1}" = "header" ]; then local color="${bold}${tan}"; fi
if [ "${1}" = "input" ]; then local color="${bold}"; fi
if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi
# Don't use colors on pipes or non-recognized terminals
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi
# Print to console when script is not 'quiet'
if ${quiet}; then return; else
echo -e "$(date +"%r") ${color}$(printf "[%7s]" "${1}") ${_message}${reset}";
fi
# Print to Logfile
if ${printLog} && [ "${1}" != "input" ]; then
color=""; reset="" # Don't use colors in logs
echo -e "$(date +"%m-%d-%Y %r") $(printf "[%7s]" "${1}") ${_message}" >> "${logFile}";
fi
}
function die () { local _message="${*} Exiting."; echo -e "$(_alert error)"; safeExit;}
function error () { local _message="${*}"; echo -e "$(_alert error)"; }
function warning () { local _message="${*}"; echo -e "$(_alert warning)"; }
function notice () { local _message="${*}"; echo -e "$(_alert notice)"; }
function info () { local _message="${*}"; echo -e "$(_alert info)"; }
function debug () { local _message="${*}"; echo -e "$(_alert debug)"; }
function success () { local _message="${*}"; echo -e "$(_alert success)"; }
function input() { local _message="${*}"; echo -n "$(_alert input)"; }
function header() { local _message="== ${*} == "; echo -e "$(_alert header)"; }
function verbose() { if ${verbose}; then debug "$@"; fi }
# SEEKING CONFIRMATION
# ------------------------------------------------------
function seek_confirmation() {
# echo ""
input "$@"
if "${force}"; then
notice "Forcing confirmation with '--force' flag set"
else
read -p " (y/n) " -n 1
echo ""
fi
}
function is_confirmed() {
if "${force}"; then
return 0
else
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
return 0
fi
return 1
fi
}
function is_not_confirmed() {
if "${force}"; then
return 1
else
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
return 0
fi
return 1
fi
}
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$' \n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
# Run in debug mode, if set
if ${debug}; then set -x ; fi
# Exit on empty variable
if ${strict}; then set -o nounset ; fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Run your script
mainScript
# Exit cleanly
safeExit

View File

@@ -1,4 +0,0 @@
*
!.gitignore
!syncTemplate.sh
!README.md

View File

@@ -1,16 +0,0 @@
# syncTemplate.sh
This sync script template provides a mechanism to use either [rsync][1] or [unision][2] to keep two directories in sync.
## Usage
1. Execute the template and follow the prompts to create a new script for your needs
2. Execute the new script to create a new configuration file.
3. Edit the information within that configuration file.
4. Execute the script again. It will optionally encrypt your configuration file to keep your passwords safe, and then sync your directories.
For help and additional options, run the script with the `-h` flag.
[1]: https://rsync.samba.org
[2]: http://www.cis.upenn.edu/~bcpierce/unison/

View File

@@ -1,630 +0,0 @@
#!/usr/bin/env bash
# ##################################################
# My Generic sync script.
#
version="2.1.0" # Sets version variable
#
scriptTemplateVersion="1.1.1" # Version of scriptTemplate.sh that this script is based on
# v.1.1.0 - Added 'debug' option
# v.1.1.1 - Moved all shared variables to Utils
#
# This script will give you the option of using rsync
# or Unison. Rsync is for one-way syncing, Unison is for
# two-way syncing.
#
# Depending on what flags are passed to the script and
# what information is written in the config file, this script
# will perform different behavior.
#
# USAGE:
#
# 1) IMPORTANT: Copy this script and rename it for your purpose before running.
# 2) Run the script. This will create a blank config file for you and then exit.
# 3) Enter your information within the config file
# 4) Run the script again.
#
# TO DO:
# * Add SSH functionality
#
# DISCLAIMER:
# I am a novice programmer and I bear no responsibility whatsoever
# if this (or any other) script that I write wipes your computer,
# destroys your data, crashes your car, or otherwise causes mayhem
# and destruction. USE AT YOUR OWN RISK.
#
#
# HISTORY:
# * 2015-01-02 - v1.0.0 - First Creation
# * 2015-01-03 - v1.1.0 - Added support for using roots in Unison .prf
# * 2015-03-10 - v1.1.1 - Updated script template version
# - Removed $logFile from config. Default is now '~/library/logs/'
# * 2015-03-15 - v2.0.0 - Added support for encrypted config files.
# * 2015-03-21 - v2.1.0 - Added support for extended RSYNC configurations.
#
# ##################################################
# Source Scripting Utilities
# Source Scripting Utilities
# -----------------------------------
# If these can't be found, update the path to the file
# -----------------------------------
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ -f "${scriptPath}/../lib/utils.sh" ]; then
source "${scriptPath}/../lib/utils.sh"
else
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
exit 1
fi
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
function trapCleanup() {
echo ""
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
fi
die "Exit trapped." # Edit this if you like.
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=0
printLog=0
verbose=0
force=0
strict=0
debug=0
editConfig=0
mountTest=0
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="$HOME/Library/Logs/${scriptBasename}.log"
# Configuration file(s)
# -----------------------------------
# This script calls for a configuration file.
# This is its location. Default is the location
# where it will be automatically created.`
# -----------------------------------
tmpConfig="${tmpDir}/${scriptName}.cfg"
newConfig="./${scriptName}.cfg"
encConfig="../etc/${scriptName}.cfg.enc"
############## Begin Script Functions Here ###################
# Create new copy of the script if template is being executed
function newCopy() {
if [ "${scriptName}" = "syncTemplate.sh" ]; then
input "name your new script:"
read newname
verbose "Copying SyncTemplate.sh to ${newname}"
cp "${scriptPath}"/"${scriptName}" "${scriptPath}"/"${newname}" && verbose "cp ${scriptPath}/${scriptName} ${scriptPath}/${newname}"
success "${newname} created."
safeExit
fi
}
function encryptConfig() {
# If a non-encrypted config file exists (ie - it was being edited) we encrypt it
if is_file "${newConfig}"; then
verbose "${newConfig} exists"
seek_confirmation "Are you ready to encrypt your config file?"
if is_confirmed; then
if is_file "${encConfig}"; then
rm "${encConfig}" && verbose "Existing encoded config file exists. Running: rm ${encConfig}"
fi
if is_empty ${PASS}; then # Look for password from CLI
verbose "openssl enc -aes-256-cbc -salt -in ${newConfig} -out ${encConfig}"
openssl enc -aes-256-cbc -salt -in "${newConfig}" -out "${encConfig}"
else
verbose "openssl enc -aes-256-cbc -salt -in ${newConfig} -out ${encConfig} -k [PASSWORD]"
openssl enc -aes-256-cbc -salt -in "${newConfig}" -out "${encConfig}" -k ${PASS}
fi
rm "${newConfig}" && verbose "rm ${newConfig}"
success "Encoded the config file."
safeExit
else
warning "You need to encrypt your config file before proceeding"
safeExit
fi
fi
}
function createTempConfig() {
# If we find the encoded config file, we decrypt it to the temp location
if is_file "${encConfig}"; then
if is_empty ${PASS}; then # Look for password from CLI
verbose "openssl enc -aes-256-cbc -d -in ${encConfig} -out ${tmpConfig}"
openssl enc -aes-256-cbc -d -in "${encConfig}" -out "${tmpConfig}"
else
verbose "openssl enc -aes-256-cbc -d -in ${encConfig} -out ${tmpConfig} -k [PASSWORD]"
openssl enc -aes-256-cbc -d -in "${encConfig}" -out "${tmpConfig}" -k ${PASS}
fi
fi
}
function sourceConfiguration() {
# Here we source the Config file or create a new one if none exists.
if is_file "${tmpConfig}"; then
source "${tmpConfig}" && verbose "source ${tmpConfig}"
else
seek_confirmation "Config file does not exist. Would you like to create one?"
if is_not_confirmed; then
die "No config file."
else
touch "${newConfig}" && verbose "touch ${newConfig}"
cat >"${newConfig}" <<EOL
# ##################################################
# CONFIG FILE FOR ${scriptName}
# CREATED ON ${now}
#
# Created by version "$version" of "SyncTemplate.sh"
# ##################################################
# METHOD
# ---------------------------
# This script will work with both Unison and rsync.
# Set the METHOD variable to either 'unison' or 'rsync'
METHOD=""
# ---------------------------
# Network Volume Mounting
# ---------------------------
# If one of the directies you need to sync is on a network drive set
# the variable NEEDMOUNT to 'true'
NEEDMOUNT="false"
# MOUNTPOINT is the address of the drive to be mounted.
# Use the format afp://username:password@address/mountname
MOUNTPOINT=""
# REMOTEVOLUME is the directory that the drive should be mounted
# into on the local computer. Typically this is in the /Volumes/ dir.
# and should be named the same as the mountname in the MOUNTPOINT.
# Use a complete path, not a relative path without a trailing slash.
REMOTEVOLUME=""
# ---------------------------
# Directories To Sync
# ---------------------------
# These are the COMPLETE paths two directories that will be synced.
# Be sure to include trailing slashes on directories.
SOURCEDIRECTORY=""
TARGETDIRECTORY=""
# ---------------------------
# UNISON PREFERENCES
# ---------------------------
# If you are using UNISON to sync your directories, fill in this section.
# Unison keeps its own config profiles which configure
# much of the script. These .prf files are located in '~/.unison/'
# more info: http://www.cis.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html
#
# If you wish to use a Unison profile change USERPROFILE to 'true'
# and add the profile name to UNISONPROFILE.
#
# If your Unison profile contains the 'roots' to by synced, change PROFILEROOTS to 'true'.
# If this remains 'false', the directories to by synced will be the ones specified above.
USEPROFILE="false"
PROFILEROOTS="false"
UNISONPROFILE=""
# ---------------------------
# RSYNC PREFENCES
# ---------------------------
# If you are using rsync, complete this section
# EXCLUDE sets rsync to exclude files from syncing based on a pattern.
# Defaults to null.
# If needed, add individual excludes in the format of "--exclude file1.txt --exclude file2.txt".
EXCLUDE=""
# EXCLUDELIST is a text file that contains all the rsync excludes.
# Anything listed within this file will be ignored during sync.
# Default is null.
# Set value to "--exclude-from=/some/file/location.txt" if needed
EXCLUDELIST=""
# DELETE sets the variable to delete files in the target directory that are deleted from
# the source directory. In effect, keeping them 'in-sync'. Defaults to equal "--delete"
# which sets that flag. Set to null to ensure all files on the target remain when deleted
# from the source.
DELETE="--delete"
# ---------------------------
# ADDITIONAL OPTIONS
# ---------------------------
# PUSHOVER is an online notification tool.
# If you want to receive notifications upon completion
# set the following value to "true"
PUSHOVERNOTIFY="false"
# CANONICALHOST is used to denote a sync hub which should never initiate a sync.
# Leave blank if not needed.
CANONICALHOST=""
EOL
success "Config file created. Edit the values before running this script again."
notice "The file is located at: ${newConfig}. Exiting."
safeExit
fi
fi
}
function editConfiguration() {
# If the '--config' is set to true, we create an editable config file for re-encryption
if [ "${editConfig}" == "1" ]; then
verbose "editConfig is true"
seek_confirmation "Would you like to edit your config file?"
if is_confirmed; then
if is_file "${tmpConfig}"; then
cp "${tmpConfig}" "${newConfig}" && verbose "cp ${tmpConfig} ${newConfig}"
success "Config file has been decrypted to ${newConfig}. Edit the file and rerun the script."
safeExit
else
die "Couldn't find ${tmpConfig}."
fi
else
notice "Exiting."
safeExit
fi
fi
}
# HostCheck
# Confirm we can run this script. If a canonical host is set in
# the config file we check it here.
function hostCheck() {
if [ "${thisHost}" = "${CANONICALHOST}" ]; then
notice "We are currently on ${THISHOST} and can not proceed. Be sure to run this script on the non-canonical host. Exiting"
safeExit
fi
}
# MethodCheck
# Confirm we have either Unison or Rsync specified
# in the config file. Exit if not.
function MethodCheck() {
if [ "${METHOD}" != "rsync" ] && [ "${METHOD}" != "unison" ]; then
die "Script aborted without a method specified in the config file."
fi
}
function moutDrives() {
if [ "${NEEDMOUNT}" = "true" ] || [ "${NEEDMOUNT}" = "TRUE" ] || [ "${NEEDMOUNT}" = "True" ]; then
# Mount AFP volume
if is_not_dir "${REMOTEVOLUME}"; then
notice "Mounting drive"
mkdir "${REMOTEVOLUME}" && verbose "mkdir ${REMOTEVOLUME}"
if [ "${MOUTPW}" = "true" ]; then # if password prompt needed
mount_afp -i "${MOUNTPOINT}" "${REMOTEVOLUME}" && verbose "mount_afp -i ${MOUNTPOINT} ${REMOTEVOLUME}"
else
mount_afp "${MOUNTPOINT}" "${REMOTEVOLUME}" && verbose "mount_afp ${MOUNTPOINT} ${REMOTEVOLUME}"
fi
sleep 5
notice "${REMOTEVOLUME} Mounted"
else
notice "${REMOTEVOLUME} was already mounted."
fi
#Allow for debugging with only the mount function
if [ ${mountTest} = 1 ]; then
seek_confirmation "Are you ready to unmount the drive"
if is_confirmed; then
unmountDrives
safeExit
fi
fi
fi
}
function unmountDrives() {
# Unmount the drive (if mounted)
if [ "${NEEDMOUNT}" = "true" ] || [ "${NEEDMOUNT}" = "TRUE" ]; then
unmountDrive "${REMOTEVOLUME}" && verbose "unmountDrive ${REMOTEVOLUME}"
notice "${REMOTEVOLUME} UnMounted"
fi
}
function testSources() {
# Test for source directories.
# If they don't exist we can't continue
# test for target
if is_dir "${TARGETDIRECTORY}"; then
verbose "${TARGETDIRECTORY} exists"
else
if [ "${NEEDMOUNT}" = "true" ] || [ "${NEEDMOUNT}" = "TRUE" ]; then
unmountDrive "${REMOTEVOLUME}" && verbose "Unmounting ${REMOTEVOLUME}"
if is_dir "${REMOTEVOLUME}"; then
rm -r "${REMOTEVOLUME}" && verbose "rm -r ${REMOTEVOLUME}"
fi
fi
die "Target directory: ${TARGETDIRECTORY} does not exist."
fi
# Test for source directory
if is_dir "${SOURCEDIRECTORY}"; then
verbose "${SOURCEDIRECTORY} exists"
else
if [ "${NEEDMOUNT}" = "true" ] || [ "${NEEDMOUNT}" = "TRUE" ]; then
unmountDrive "${REMOTEVOLUME}" && verbose "Unmounting ${REMOTEVOLUME}"
if is_dir "${REMOTEVOLUME}"; then
rm -r "${REMOTEVOLUME}" && verbose "rm -r ${REMOTEVOLUME}"
fi
fi
die "Source directory: ${SOURCEDIRECTORY} does not exist."
fi
notice "Source directories passed filesystem check. Continuing."
}
function runRsync() {
# Populate logfile variable if "printlog=1"
if [ "${printLog}" = 1 ]; then
RSYNCLOG="--log-file=${logFile}"
else
RSYNCLOG=""
fi
if [ "${METHOD}" = "rsync" ]; then
notice "Commencing rsync"
verbose "rsync -vahh${DRYRUN}${COMPRESS} --progress --force ${DELETE} ${EXCLUDE} ${EXCLUDELIST} ${SOURCEDIRECTORY} ${TARGETDIRECTORY} ${RSYNCLOG}"
rsync -vahh${DRYRUN}${COMPRESS} --progress --force ${DELETE} ${EXCLUDE} ${EXCLUDELIST} "${SOURCEDIRECTORY}" "${TARGETDIRECTORY}" ${RSYNCLOG}
fi
}
function runUnison() {
if [ "${METHOD}" = "unison" ]; then
# Check if Unison is installed. It is not a standard package
if type_not_exists 'unison'; then
seek_confirmation "Unison not installed, try to install it with Homebrew?"
if is_confirmed; then
notice "Attempting to install Unison."
hasHomebrew
brew install unison
else
if [ "${NEEDMOUNT}" = "true" ] || [ "${NEEDMOUNT}" = "TRUE" ]; then
unmountDrive "${REMOTEVOLUME}" && verbose "unmountDrive ${REMOTEVOLUME}"
if is_dir "${REMOTEVOLUME}"; then
rm -r "${REMOTEVOLUME}" && verbose "rm -r ${REMOTEVOLUME}"
fi
fi
die "Can not continue without having Unison installed."
fi
fi
# Run Unison
if [ "${PROFILEROOTS}" = "true" ]; then
# Throw error if we don't have enough information
if [ "${USEPROFILE}" = "false" ] || [ "${UNISONPROFILE}" = "" ]; then
die "We were missing the Unison Profile. Could not sync."
fi
# Run unison with a profile and no sources
notice "Commencing Unison"
verbose "unison ${UNISONPROFILE}" && unison "${UNISONPROFILE}"
else
if [ "${USEPROFILE}" = "true" ]; then
# Throw error if we can't find the profile
if [ "${UNISONPROFILE}" = "" ]; then
die "We were missing the Unison Profile. Could not sync."
fi
# Run unison with a profile and specified sources
notice "Commencing Unison"
verbose "unision ${UNISONPROFILE} ${SOURCEDIRECTORY} ${TARGETDIRECTORY}" && unison "${UNISONPROFILE}" "${SOURCEDIRECTORY}" "${TARGETDIRECTORY}"
else
# Run Unison without a profile
notice "Commencing Unison"
verbose "unison ${SOURCEDIRECTORY} ${TARGETDIRECTORY}" && unison "${SOURCEDIRECTORY}" "${TARGETDIRECTORY}"
fi
fi
fi
}
function notifyPushover() {
if [ "${PUSHOVERNOTIFY}" = "true" ]; then
verbose "\"pushover ${SCRIPTNAME} Completed\" \"${SCRIPTNAME} was run in $(convertsecs $TOTALTIME)\""
pushover "${SCRIPTNAME} Completed" "${SCRIPTNAME} was run in $(convertsecs $TOTALTIME)"
fi
}
############## End Script Functions Here ###################
############## Begin Options and Usage ###################
# Print usage
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
This script will give you the option of using rsync
or Unison. Rsync is for one-way syncing, Unison is for
two-way syncing.
Depending on what flags are passed to the script and
what information is written in the config file, this script
will perform different behavior.
USAGE:
1) Copy this script and rename it for your purpose before running.
The script will do this for you when run.
2) Run the new script. This will create a blank config file
for you and then exit.
3) Enter your information within the config file
4) Run the script again.
This script requires an encoded config file located at: ${encConfig}
Ensure that the config file is correct before running.
If the config file is not found at all, the script will
create a new one for you.
To edit the configuration file, run the script with the '-c' flag.
Options:
-c, --config Decrypts the configuration file to allow it to be edited.
-d, --debug Prints commands to console. Runs no syncs.
-f, --force Rsync only. Skip all user interaction. Implied 'Yes' to all actions.
-h, --help Display this help and exit.
-l, --log Print log to file.
-n, --dryrun Rsync only. Dry run - will run everything without making any changes.
-m, --mounttest Will run the mount/unmount drive portion of the script and bypass all syncing.
-p, --password Prompts for the password which decrypts the configuration file.
-q, --quiet Quiet (no output)
-s, --strict Exit script with null variables. 'set -o nounset'
-v, --verbose Output more information. (Items echoed to 'verbose')
-z, --compress Rsync only. This will compress data before transferring.
Good for slow internet connections.
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# [[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) $version"; safeExit ;;
-p|--password) shift; echo "Enter Pass: "; stty -echo; read PASS; stty echo;
echo ;;
-v|--verbose) verbose=1 ;;
-l|--log) printLog=1 ;;
-c|--config) editConfig=1 ;;
-d|--debug) debug=1 ;;
-q|--quiet) quiet=1 ;;
-s|--strict) strict=1;;
-f|--force) force=1 ;;
-n|--dryrun) DRYRUN=n ;;
-m|--mounttest) mountTest=1 ;;
-z|--compress) COMPRESS=z ;;
--endopts) shift; break ;;
*) warning "invalid option: $1.\n"; usage >&2; safeExit ;;
# *) die "invalid option: '$1'." ;;
esac
shift
done
############## End Options and Usage ###################
# ############# ############# #############
# ## TIME TO RUN THE SCRIPT ##
# ## ##
# ## You shouldn't need to edit anything ##
# ## beneath this line ##
# ## ##
# ############# ############# #############
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Exit on error. Append ||true if you expect an error.
set -o errexit
# Exit on empty variable
if [ "${strict}" == "1" ]; then
set -o nounset
fi
# Run in debug mode, if set
if [ "${debug}" == "1" ]; then
set -x
fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`
set -o pipefail
# Set timer for script to start
STARTTIME=$(date +"%s")
header "${scriptName} Begun"
newCopy
encryptConfig
createTempConfig
editConfiguration
sourceConfiguration
hostCheck
MethodCheck
moutDrives
testSources
runRsync
runUnison
unmountDrives
# Time the script by logging the end time
ENDTIME=$(date +"%s")
TOTALTIME=$(($ENDTIME-$STARTTIME))
notifyPushover
header "${scriptName} completed in $(convertsecs $TOTALTIME)"
safeExit # Exit cleanly

5
tmp/.gitignore vendored
View File

@@ -1,5 +0,0 @@
*
!.gitignore
!README.md
!*.sample

159
utilities/alerts.bash Normal file
View File

@@ -0,0 +1,159 @@
# Colors
if tput setaf 1 &>/dev/null; then
bold=$(tput bold)
white=$(tput setaf 7)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
yellow=$(tput setaf 3)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
else
bold="\033[4;37m"
white="\033[0;37m"
reset="\033[0m"
purple="\033[0;35m"
red="\033[0;31m"
green="\033[1;32m"
tan="\033[0;33m"
yellow="\033[0;33m"
blue="\033[0;34m"
underline="\033[4;37m"
fi
_alert_() {
# DESC: Controls all printing of messages to log files and stdout.
# ARGS: $1 (required) - The type of alert to print
# (success, header, notice, dryrun, debug, warning, error,
# fatal, info, input)
# $2 (required) - The message to be printed to stdout and/or a log file
# $3 (optional) - Pass '$LINENO' to print the line number where the _alert_ was triggered
# OUTS: None
# USAGE: [ALERTTYPE] "[MESSAGE]" "$LINENO"
# NOTES: - Requires the variable LOGFILE to be set prior to
# calling this function.
# - The colors of each alert type are set in this function
# - For specified alert types, the funcstac will be printed
local scriptName logLocation logName function_name color
local alertType="${1}"
local message="${2}"
local line="${3-}"
[ -z "${LOGFILE-}" ] && fatal "\$LOGFILE must be set"
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
if [ -z "${line}" ]; then
[[ "$1" =~ ^(fatal|error|debug|warning) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} $(_functionStack_)"
else
[[ "$1" =~ ^(fatal|error|debug) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} (line: $line) $(_functionStack_)"
fi
if [ -n "${line}" ]; then
[[ "$1" =~ ^(warning|info|notice|dryrun) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} (line: $line)"
fi
if [[ "${alertType}" =~ ^(error|fatal) ]]; then
color="${bold}${red}"
elif [ "${alertType}" = "warning" ]; then
color="${red}"
elif [ "${alertType}" = "success" ]; then
color="${green}"
elif [ "${alertType}" = "debug" ]; then
color="${purple}"
elif [ "${alertType}" = "header" ]; then
color="${bold}${tan}"
elif [[ "${alertType}" =~ ^(input|notice) ]]; then
color="${bold}"
elif [ "${alertType}" = "dryrun" ]; then
color="${blue}"
else
color=""
fi
_writeToScreen_() {
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
[[ ${VERBOSE} == false && "${alertType}" =~ ^(debug|verbose) ]] && return 0
if ! [[ -t 1 ]]; then # Don't use colors on non-recognized terminals
color=""
reset=""
fi
echo -e "$(date +"%r") ${color}$(printf "[%7s]" "${alertType}") ${message}${reset}"
}
_writeToScreen_
_writeToLog_() {
[[ "${alertType}" == "input" ]] && return 0
[[ "${LOGLEVEL}" =~ (off|OFF|Off) ]] && return 0
[[ ! -f "${LOGFILE}" ]] && touch "${LOGFILE}"
# Don't use colors in logs
if command -v gsed &>/dev/null; then
local cleanmessage="$(echo "${message}" | gsed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
else
local cleanmessage="$(echo "${message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
fi
echo -e "$(date +"%b %d %R:%S") $(printf "[%7s]" "${alertType}") [$(/bin/hostname)] ${cleanmessage}" >>"${LOGFILE}"
}
# Write specified log level data to LOGFILE
case "${LOGLEVEL:-ERROR}" in
ALL|all|All)
_writeToLog_
;;
DEBUG|debug|Debug)
_writeToLog_
;;
INFO|info|Info)
if [[ "${alertType}" =~ ^(die|error|fatal|warning|info|notice|success) ]]; then
_writeToLog_
fi
;;
WARN|warn|Warn)
if [[ "${alertType}" =~ ^(die|error|fatal|warning) ]]; then
_writeToLog_
fi
;;
ERROR|error|Error)
if [[ "${alertType}" =~ ^(die|error|fatal) ]]; then
_writeToLog_
fi
;;
FATAL|fatal|Fatal)
if [[ "${alertType}" =~ ^(die|fatal) ]]; then
_writeToLog_
fi
;;
OFF|off)
return 0
;;
*)
if [[ "${alertType}" =~ ^(die|error|fatal) ]]; then
_writeToLog_
fi
;;
esac
} # /_alert_
error() { _alert_ error "${1}" "${2-}"; }
warning() { _alert_ warning "${1}" "${2-}"; }
notice() { _alert_ notice "${1}" "${2-}"; }
info() { _alert_ info "${1}" "${2-}"; }
success() { _alert_ success "${1}" "${2-}"; }
dryrun() { _alert_ dryrun "${1}" "${2-}"; }
input() { _alert_ input "${1}" "${2-}"; }
header() { _alert_ header "== ${1} ==" "${2-}"; }
die() { _alert_ fatal "${1}" "${2-}"; _safeExit_ "1" ; }
fatal() { _alert_ fatal "${1}" "${2-}"; _safeExit_ "1" ; }
debug() { _alert_ debug "${1}" "${2-}"; }
verbose() { _alert_ debug "${1}" "${2-}"; }

98
utilities/arrays.bash Normal file
View File

@@ -0,0 +1,98 @@
_inArray_() {
# DESC: Determine if a value is in an array
# ARGS: $1 (Required) - Value to search for
# $2 (Required) - Array written as ${ARRAY[@]}
# OUTS: true/false
# USAGE: if _inArray_ "VALUE" "${ARRAY[@]}"; then ...
[[ $# -lt 2 ]] && fatal 'Missing required argument to _inArray_()!'
local value="$1"
shift
for arrayItem in "$@"; do
[[ "${arrayItem}" == "${value}" ]] && return 0
done
return 1
}
_join_() {
# DESC: Joins items together with a user specified separator
# ARGS: $1 (Required) - Separator
# $@ (Required) - Items to be joined
# OUTS: Prints joined terms
# USAGE:
# _join_ , a "b c" d #a,b c,d
# _join_ / var local tmp #var/local/tmp
# _join_ , "${foo[@]}" #a,b,c
# NOTE: http://stackoverflow.com/questions/1527049/bash-join-elements-of-an-array
[[ $# -lt 2 ]] && fatal 'Missing required argument to _join_()!'
local IFS="${1}"
shift
echo "${*}"
}
_setdiff_() {
# DESC: Return items that exist in ARRAY1 that are do not exist in ARRAY2
# ARGS: $1 (Required) - Array 1 in format ${ARRAY[*]}
# $2 (Required) - Array 2 in format ${ARRAY[*]}
# OUTS: Prints unique terms
# USAGE: _setdiff_ "${array1[*]}" "${array2[*]}"
# NOTE: http://stackoverflow.com/a/1617303/142339
[[ $# -lt 2 ]] && fatal 'Missing required argument to _setdiff_()!'
local debug skip a b
if [[ "$1" == 1 ]]; then
debug=1
shift
fi
if [[ "$1" ]]; then
local setdiffA setdiffB setdiffC
# shellcheck disable=SC2206
setdiffA=($1)
# shellcheck disable=SC2206
setdiffB=($2)
fi
setdiffC=()
for a in "${setdiffA[@]}"; do
skip=
for b in "${setdiffB[@]}"; do
[[ "$a" == "$b" ]] && skip=1 && break
done
[[ "$skip" ]] || setdiffC=("${setdiffC[@]}" "$a")
done
[[ "$debug" ]] && for a in setdiffA setdiffB setdiffC; do
#shellcheck disable=SC1087
echo "$a ($(eval echo "\${#$a[*]}")) $(eval echo "\${$a[*]}")" 1>&2
done
[[ "$1" ]] && echo "${setdiffC[@]}"
}
_removeDupes_() {
# DESC: Removes duplicate array elements.
# ARGS: $1 (Required) - Input array
# OUTS: Prints de-duped elements to standard out
# USAGE: _removeDups_ "${array@]}"
# NOTE: List order may not stay the same.
# https://github.com/dylanaraps/pure-bash-bible
declare -A tmp_array
for i in "$@"; do
[[ $i ]] && IFS=" " tmp_array["${i:- }"]=1
done
printf '%s\n' "${!tmp_array[@]}"
}
_randomArrayElement_() {
# DESC: Selects a random item from an array
# ARGS: $1 (Required) - Input array
# OUTS: Prints result
# USAGE: _randomArrayElement_ "${array[@]}"
# NOTE: https://github.com/dylanaraps/pure-bash-bible
# Usage: random_array_element "array"
local arr=("$@")
printf '%s\n' "${arr[RANDOM % $#]}"
}

277
utilities/baseHelpers.bash Normal file
View File

@@ -0,0 +1,277 @@
_execute_() {
# DESC: Executes commands with safety and logging options
# ARGS: $1 (Required) - The command to be executed. Quotation marks MUST be escaped.
# $2 (Optional) - String to display after command is executed
# OPTS: -v Always print debug output from the execute function
# -p Pass a failed command with 'return 0'. This effecively bypasses set -e.
# -e Bypass _alert_ functions and use 'echo RESULT'
# -s Use '_alert_ success' for successful output. (default is 'info')
# -q Do not print output (QUIET mode)
# OUTS: None
# USE : _execute_ "cp -R \"~/dir/somefile.txt\" \"someNewFile.txt\"" "Optional message"
# _execute_ -sv "mkdir \"some/dir\""
# NOTE:
# If $DRYRUN=true no commands are executed
# If $VERBOSE=true the command's native output is printed to
# stderr and stdin. This can be forced with `_execute_ -v`
local localVerbose=false
local passFailures=false
local echoResult=false
local successResult=false
local quietResult=false
local opt
local OPTIND=1
while getopts ":vVpPeEsSqQ" opt; do
case $opt in
v | V) localVerbose=true ;;
p | P) passFailures=true ;;
e | E) echoResult=true ;;
s | S) successResult=true ;;
q | Q) quietResult=true ;;
*)
{
error "Unrecognized option '$1' passed to _execute_. Exiting."
_safeExit_
}
;;
esac
done
shift $((OPTIND - 1))
local cmd="${1:?_execute_ needs a command}"
local message="${2:-$1}"
local saveVerbose=$VERBOSE
if "${localVerbose}"; then
VERBOSE=true
fi
if "${DRYRUN}"; then
if "$quietResult"; then
VERBOSE=$saveVerbose
return 0
fi
if [ -n "${2-}" ]; then
dryrun "${1} (${2})" "$(caller)"
else
dryrun "${1}" "$(caller)"
fi
elif ${VERBOSE}; then
if eval "${cmd}"; then
if "$echoResult"; then
echo "${message}"
elif "${successResult}"; then
success "${message}"
else
info "${message}"
fi
VERBOSE=$saveVerbose
return 0
else
if "$echoResult"; then
echo "warning: ${message}"
else
warning "${message}"
fi
VERBOSE=$saveVerbose
"${passFailures}" && return 0 || return 1
fi
else
if eval "${cmd}" &>/dev/null; then
if "$quietResult"; then
VERBOSE=$saveVerbose
return 0
elif "$echoResult"; then
echo "${message}"
elif "${successResult}"; then
success "${message}"
else
info "${message}"
fi
VERBOSE=$saveVerbose
return 0
else
if "$echoResult"; then
echo "error: ${message}"
else
warning "${message}"
fi
VERBOSE=$saveVerbose
"${passFailures}" && return 0 || return 1
fi
fi
}
_findBaseDir_() {
# DESC: Locates the real directory of the script being run. Similar to GNU readlink -n
# ARGS: None
# OUTS: Echo result to STDOUT
# USE : baseDir="$(_findBaseDir_)"
# cp "$(_findBaseDir_ "somefile.txt")" "other_file.txt"
local SOURCE
local DIR
# Is file sourced?
[[ $_ != "$0" ]] \
&& SOURCE="${BASH_SOURCE[1]}" \
|| SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="${DIR}/${SOURCE}" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
echo "$(cd -P "$(dirname "${SOURCE}")" && pwd)"
}
_checkBinary_() {
# DESC: Check if a binary exists in the search PATH
# ARGS: $1 (Required) - Name of the binary to check for existence
# OUTS: true/false
# USAGE: (_checkBinary_ ffmpeg ) && [SUCCESS] || [FAILURE]
if [[ $# -lt 1 ]]; then
error 'Missing required argument to _checkBinary_()!'
return 1
fi
if ! command -v "$1" >/dev/null 2>&1; then
debug "Did not find dependency: '$1'"
return 1
fi
return 0
}
_haveFunction_() {
# DESC: Tests if a function exists.
# ARGS: $1 (Required) - Function name
# OUTS: true/false
local f
f="$1"
if declare -f "$f" &>/dev/null 2>&1; then
return 0
else
return 1
fi
}
_pauseScript_() {
# DESC: Pause a script at any point and continue after user input
# ARGS: $1 (Optional) - String for customized message
# OUTS: None
local pauseMessage
pauseMessage="${1:-Paused}. Ready to continue?"
if _seekConfirmation_ "${pauseMessage}"; then
info "Continuing..."
else
notice "Exiting Script"
_safeExit_
fi
}
_progressBar_() {
# DESC: Prints a progress bar within a for/while loop
# ARGS: $1 (Required) - The total number of items counted
# $2 (Optional) - The optional title of the progress bar
# OUTS: None
# USAGE:
# for number in $(seq 0 100); do
# sleep 1
# _progressBar_ "100" "Counting numbers"
# done
($QUIET) && return
($VERBOSE) && return
[ ! -t 1 ] && return # Do nothing if the output is not a terminal
[ $1 == 1 ] && return # Do nothing with a single element
local width bar_char perc num bar progressBarLine barTitle n
n="${1:?_progressBar_ needs input}"
((n = n - 1))
barTitle="${2:-Running Process}"
width=30
bar_char="#"
# Reset the count
[ -z "${progressBarProgress}" ] && progressBarProgress=0
tput civis # Hide the cursor
trap 'tput cnorm; exit 1' SIGINT
if [ ! "${progressBarProgress}" -eq $n ]; then
#echo "progressBarProgress: $progressBarProgress"
# Compute the percentage.
perc=$((progressBarProgress * 100 / $1))
# Compute the number of blocks to represent the percentage.
num=$((progressBarProgress * width / $1))
# Create the progress bar string.
bar=""
if [ ${num} -gt 0 ]; then
bar=$(printf "%0.s${bar_char}" $(seq 1 ${num}))
fi
# Print the progress bar.
progressBarLine=$(printf "%s [%-${width}s] (%d%%)" " ${barTitle}" "${bar}" "${perc}")
echo -ne "${progressBarLine}\r"
progressBarProgress=$((progressBarProgress + 1))
else
# Clear the progress bar when complete
# echo -ne "\033[0K\r"
tput el # Clear the line
unset progressBarProgress
fi
tput cnorm
}
_seekConfirmation_() {
# DESC: Seek user input for yes/no question
# ARGS: $1 (Optional) - Question being asked
# OUTS: true/false
# USAGE: _seekConfirmation_ "Do something?" && echo "okay" || echo "not okay"
# OR
# if _seekConfirmation_ "Answer this question"; then
# something
# fi
input "${1-}"
if "${FORCE}"; then
debug "Forcing confirmation with '--force' flag set"
echo -e ""
return 0
else
while true; do
read -r -p " (y/n) " yn
case $yn in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
*) input "Please answer yes or no." ;;
esac
done
fi
}
_setPATH_() {
# DESC: Add directories to $PATH so script can find executables
# ARGS: $@ - One or more paths
# OUTS: $PATH
# USAGE: _setPATH_ "/usr/local/bin" "${HOME}/bin" "$(npm bin)"
local NEWPATH NEWPATHS USERPATH
for USERPATH in "$@"; do
NEWPATHS+=("$USERPATH")
done
for NEWPATH in "${NEWPATHS[@]}"; do
if ! echo "$PATH" | grep -Eq "(^|:)${NEWPATH}($|:)"; then
PATH="${NEWPATH}:${PATH}"
debug "Added '${tan}${NEWPATH}${purple}' to PATH"
fi
done
}

34
utilities/csv.bash Normal file
View File

@@ -0,0 +1,34 @@
_makeCSV_() {
# Creates a new CSV file if one does not already exist
# Takes passed arguments and writes them as a header line to the CSV
# Usage '_makeCSV_ column1 column2 column3'
# Set the location and name of the CSV File
if [ -z "${csvLocation}" ]; then
csvLocation="${HOME}/Desktop"
fi
if [ -z "${csvName}" ]; then
csvName="$(LC_ALL=C date +%Y-%m-%d)-${FUNCNAME[1]}.csv"
fi
csvFile="${csvLocation}/${csvName}"
# Overwrite existing file? If not overwritten, new content is added
# to the bottom of the existing file
if [ -f "${csvFile}" ]; then
if _seekConfirmation_ "${csvFile} already exists. Overwrite?"; then
rm "${csvFile}"
fi
fi
_writeCSV_ "$@"
}
_writeCSV_() {
# Takes passed arguments and writes them as a comma separated line
# Usage '_writeCSV_ column1 column2 column3'
local csvInput=("$@")
saveIFS=$IFS
IFS=','
echo "${csvInput[*]}" >>"${csvFile}"
IFS=${saveIFS}
}

381
utilities/dates.bash Normal file
View File

@@ -0,0 +1,381 @@
_monthToNumber_() {
# DESC: Convert a month name to a number
# ARGS: None
# OUTS: Prints the number of the month to stdout
# USAGE: _monthToNumber_ "January"
local mon="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
case "$mon" in
january|jan|ja) echo 1 ;;
february|feb|fe) echo 2 ;;
march|mar|ma) echo 3 ;;
april|apr|ap) echo 4 ;;
may) echo 5 ;;
june|jun|ju) echo 6 ;;
july|jul) echo 7 ;;
august|aug|au) echo 8 ;;
september|sep|se) echo 9 ;;
october|oct) echo 10 ;;
november|nov|no) echo 11 ;;
december|dec|de) echo 12 ;;
*)
warning "month_monthToNumber_: Bad monthname: $1"
return 1 ;;
esac
}
_numberToMonth_() {
# DESC: Convert a month number to its name
# ARGS: None
# OUTS: Prints the name of the month to stdout
# USAGE: _numberToMonth_ 1
local mon="$1"
case "$mon" in
1|01) echo January ;;
2|02) echo February ;;
3|03) echo March ;;
4|04) echo April ;;
5|05) echo May ;;
6|06) echo June ;;
7|07) echo July ;;
8|08) echo August ;;
9|09) echo September ;;
10) echo October ;;
11) echo November ;;
12) echo December ;;
*)
warning "_numberToMonth_: Bad month number: $1"
return 1 ;;
esac
}
_parseDate_() {
# DESC: Takes a string as input and attempts to find a date within it
# to parse into component parts (day, month, year)
# ARGS: $1 (required) - A string
# OUTS: Returns error if no date found
# $_parseDate_found - The date found in the string
# $_parseDate_year - The year
# $_parseDate_month - The number month
# $_parseDate_monthName - The name of the month
# $_parseDate_day - The day
# $_parseDate_hour - The hour (if avail)
# $_parseDate_minute - The minute (if avail)
# USAGE: if _parseDate_ "[STRING]"; then ...
# NOTE: This function only recognizes dates from the year 2000 to 2029
# NOTE: Will recognize dates in the following formats separated by '-_ ./'
# * YYYY-MM-DD * Month DD, YYYY * DD Month, YYYY
# * Month, YYYY * Month, DD YY * MM-DD-YYYY
# * MMDDYYYY * YYYYMMDD * DDMMYYYY
# * YYYYMMDDHHMM * YYYYMMDDHH * DD-MM-YYYY
# * DD MM YY * MM DD YY
# TODO: Impelemt the following date formats
# * MMDDYY * YYMMDD * mon-DD-YY
[[ $# -eq 0 ]] && {
error 'Missing required argument to _parseDate_()!'
return 1
}
local date="${1:-$(date +%F)}"
_parseDate_found="" _parseDate_year="" _parseDate_month="" _parseDate_monthName=""
_parseDate_day="" _parseDate_hour="" _parseDate_minute=""
shopt -s nocasematch #Use case-insensitive regex
debug "_parseDate_() input ${tan}$date${purple}"
# YYYY MM DD or YYYY-MM-DD
pat="(.*[^0-9]|^)((20[0-2][0-9])[-\.\/_ ]+([ 0-9]{1,2})[-\.\/_ ]+([ 0-9]{1,2}))([^0-9].*|$)"
if [[ "${date}" =~ $pat ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$(( 10#${BASH_REMATCH[3]} ))
_parseDate_month=$(( 10#${BASH_REMATCH[4]} ))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[5]} ))
debug "regex match: ${tan}YYYY-MM-DD${purple}"
# Month DD, YYYY
elif [[ "${date}" =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec)[-\./_ ]+([0-9]{1,2})(nd|rd|th|st)?,?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[1]:-}"
_parseDate_month=$(_monthToNumber_ ${BASH_REMATCH[2]:-})
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month:-}")"
_parseDate_day=$(( 10#${BASH_REMATCH[3]:-} ))
_parseDate_year=$(( 10#${BASH_REMATCH[5]:-} ))
debug "regex match: ${tan}Month DD, YYYY${purple}"
# Month DD, YY
elif [[ "${date}" =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec)[-\./_ ]+([0-9]{1,2})(nd|rd|th|st)?,?[-\./_ ]+([0-9]{2}))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[1]}"
_parseDate_month=$(_monthToNumber_ ${BASH_REMATCH[2]})
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[3]} ))
_parseDate_year="20$(( 10#${BASH_REMATCH[5]} ))"
debug "regex match: ${tan}Month DD, YY${purple}"
# DD Month YYYY
elif [[ "${date}" =~ (.*[^0-9]|^)(([0-9]{2})[-\./_ ]+(january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec),?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day=$(( 10#"${BASH_REMATCH[3]}" ))
_parseDate_month="$(_monthToNumber_ "${BASH_REMATCH[4]}")"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year=$(( 10#"${BASH_REMATCH[5]}" ))
debug "regex match: ${tan}DD Month, YYYY${purple}"
# MM-DD-YYYY or DD-MM-YYYY
elif [[ "${date}" =~ (.*[^0-9]|^)(([ 0-9]{1,2})[-\.\/_ ]+([ 0-9]{1,2})[-\.\/_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
if [[ $(( 10#${BASH_REMATCH[3]} )) -lt 13 && \
$(( 10#${BASH_REMATCH[4]} )) -gt 12 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 32
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$(( 10#${BASH_REMATCH[5]} ))
_parseDate_month=$(( 10#${BASH_REMATCH[3]} ))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[4]} ))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
elif [[ $(( 10#${BASH_REMATCH[3]} )) -gt 12 && \
$(( 10#${BASH_REMATCH[3]} )) -lt 32 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 13
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$(( 10#${BASH_REMATCH[5]} ))
_parseDate_month=$(( 10#${BASH_REMATCH[4]} ))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[3]} ))
debug "regex match: ${tan}DD-MM-YYYY${purple}"
elif [[ $(( 10#${BASH_REMATCH[3]} )) -lt 32 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 13
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$(( 10#${BASH_REMATCH[5]} ))
_parseDate_month=$(( 10#${BASH_REMATCH[3]} ))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[4]} ))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
else
shopt -u nocasematch
return 1
fi
elif [[ "${date}" =~ (.*[^0-9]|^)(([0-9]{1,2})[-\.\/_ ]+([0-9]{1,2})[-\.\/_ ]+([0-9]{2}))([^0-9].*|$) ]]; then
if [[ $(( 10#${BASH_REMATCH[3]} )) -lt 13 && \
$(( 10#${BASH_REMATCH[4]} )) -gt 12 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 32
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year="20$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_month=$(( 10#${BASH_REMATCH[3]} ))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[4]} ))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
elif [[ $(( 10#${BASH_REMATCH[3]} )) -gt 12 && \
$(( 10#${BASH_REMATCH[3]} )) -lt 32 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 13
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year="20$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_month=$(( 10#${BASH_REMATCH[4]} ))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[3]} ))
debug "regex match: ${tan}DD-MM-YYYY${purple}"
elif [[ $(( 10#${BASH_REMATCH[3]} )) -lt 32 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 13
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year="20$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_month=$(( 10#${BASH_REMATCH[3]} ))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$(( 10#${BASH_REMATCH[4]} ))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
else
shopt -u nocasematch
return 1
fi
# Month, YYYY
elif [[ "${date}" =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec),?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[1]}"
_parseDate_day="1"
_parseDate_month="$(_monthToNumber_ "${BASH_REMATCH[2]}")"
_parseDate_monthName="$(_numberToMonth_ $_parseDate_month)"
_parseDate_year="$(( 10#${BASH_REMATCH[3]} ))"
debug "regex match: ${tan}Month, YYYY${purple}"
# YYYYMMDDHHMM
elif [[ "${date}" =~ (.*[^0-9]|^)((20[0-2][0-9])([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[4]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="$(( 10#${BASH_REMATCH[3]} ))"
_parseDate_hour="$(( 10#${BASH_REMATCH[6]} ))"
_parseDate_minute="$(( 10#${BASH_REMATCH[7]} ))"
debug "regex match: ${tan}YYYYMMDDHHMM${purple}"
# YYYYMMDDHH 1 2 3 4 5 6
elif [[ "${date}" =~ (.*[^0-9]|^)((20[0-2][0-9])([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[4]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="$(( 10#${BASH_REMATCH[3]} ))"
_parseDate_hour="${BASH_REMATCH[6]}"
_parseDate_minute="00"
debug "regex match: ${tan}YYYYMMDDHHMM${purple}"
# MMDDYYYY or YYYYMMDD or DDMMYYYY
# 1 2 3 4 5 6
elif [[ "${date}" =~ (.*[^0-9]|^)(([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
# MMDDYYYY
if [[ $(( 10#${BASH_REMATCH[5]} )) -eq 20 && \
$(( 10#${BASH_REMATCH[3]} )) -lt 13 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 32
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$(( 10#${BASH_REMATCH[4]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[3]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[5]}${BASH_REMATCH[6]}"
debug "regex match: ${tan}MMDDYYYY${purple}"
# DDMMYYYY
elif [[ $(( 10#${BASH_REMATCH[5]} )) -eq 20 && \
$(( 10#${BASH_REMATCH[3]} )) -gt 12 && \
$(( 10#${BASH_REMATCH[3]} )) -lt 32 && \
$(( 10#${BASH_REMATCH[4]} )) -lt 13
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$(( 10#${BASH_REMATCH[3]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[4]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[5]}${BASH_REMATCH[6]}"
debug "regex match: ${tan}DDMMYYYY${purple}"
# YYYYMMDD
elif [[ $(( 10#${BASH_REMATCH[3]} )) -eq 20 \
&& $(( 10#${BASH_REMATCH[6]} )) -gt 12 \
&& $(( 10#${BASH_REMATCH[6]} )) -lt 32 \
&& $(( 10#${BASH_REMATCH[5]} )) -lt 13 \
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$(( 10#${BASH_REMATCH[6]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
# YYYYDDMM
elif [[ $(( 10#${BASH_REMATCH[3]} )) -eq 20 \
&& $(( 10#${BASH_REMATCH[5]} )) -gt 12 \
&& $(( 10#${BASH_REMATCH[5]} )) -lt 32 \
&& $(( 10#${BASH_REMATCH[6]} )) -lt 13 \
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[6]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
# Assume YYYMMDD
elif [[ $(( 10#${BASH_REMATCH[3]} )) -eq 20 \
&& $(( 10#${BASH_REMATCH[6]} )) -lt 32 \
&& $(( 10#${BASH_REMATCH[5]} )) -lt 13 \
]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$(( 10#${BASH_REMATCH[6]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[5]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
else
shopt -u nocasematch
return 1
fi
# # MMDD or DDYY
# elif [[ "$date" =~ .*(([0-9]{2})([0-9]{2})).* ]]; then
# debug "regex match: ${tan}MMDD or DDMM${purple}"
# _parseDate_found="${BASH_REMATCH[1]}"
# # Figure out if days are months or vice versa
# if [[ $(( 10#${BASH_REMATCH[2]} )) -gt 12 \
# && $(( 10#${BASH_REMATCH[2]} )) -lt 32 \
# && $(( 10#${BASH_REMATCH[3]} )) -lt 13 \
# ]]; then
# _parseDate_day="$(( 10#${BASH_REMATCH[2]} ))"
# _parseDate_month="$(( 10#${BASH_REMATCH[3]} ))"
# _parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
# _parseDate_year="$(date +%Y )"
# elif [[ $(( 10#${BASH_REMATCH[2]} )) -lt 13 \
# && $(( 10#${BASH_REMATCH[3]} )) -lt 32 \
# ]]; then
# _parseDate_day="$(( 10#${BASH_REMATCH[3]} ))"
# _parseDate_month="$(( 10#${BASH_REMATCH[2]} ))"
# _parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
# _parseDate_year="$(date +%Y )"
# else
# shopt -u nocasematch
# return 1
# fi
else
shopt -u nocasematch
return 1
fi
[[ -z ${_parseDate_year:-} ]] && { shopt -u nocasematch; return 1 ; }
(( _parseDate_month >= 1 && _parseDate_month <= 12 )) || { shopt -u nocasematch; return 1 ; }
(( _parseDate_day >= 1 && _parseDate_day <= 31 )) || { shopt -u nocasematch; return 1 ; }
debug "${tan}\$_parseDate_found: ${_parseDate_found}${purple}"
debug "${tan}\$_parseDate_year: ${_parseDate_year}${purple}"
debug "${tan}\$_parseDate_month: ${_parseDate_month}${purple}"
debug "${tan}\$_parseDate_monthName: ${_parseDate_monthName}${purple}"
debug "${tan}\$_parseDate_day: ${_parseDate_day}${purple}"
[[ -z ${_parseDate_hour:-} ]] || debug "${tan}\$_parseDate_hour: ${_parseDate_hour}${purple}"
[[ -z ${_parseDate_inute:-} ]] || debug "${tan}\$_parseDate_minute: ${_parseDate_minute}${purple}"
shopt -u nocasematch
# Output results for BATS tests
if [ "${automated_test_in_progress:-}" ]; then
echo "_parseDate_found: ${_parseDate_found}"
echo "_parseDate_year: ${_parseDate_year}"
echo "_parseDate_month: ${_parseDate_month}"
echo "_parseDate_monthName: ${_parseDate_monthName}"
echo "_parseDate_day: ${_parseDate_day}"
echo "_parseDate_hour: ${_parseDate_hour}"
echo "_parseDate_minute: ${_parseDate_minute}"
fi
}
_formatDate_() {
# DESC: Reformats dates into user specified formats
# ARGS: $1 (Required) - Date to be formatted
# $2 (Optional) - Format in any format accepted by bash's date command. Examples listed below.
# %F - YYYY-MM-DD
# %D - MM/DD/YY
# %a - Name of weekday in short (like Sun, Mon, Tue, Wed, Thu, Fri, Sat)
# %A - Name of weekday in full (like Sunday, Monday, Tuesday)
# '+%m %d, %Y' - 12 27, 2019
# OUTS: Echo result to STDOUT
# USAGE: _formatDate_ "Jan 10, 2019" "%D"
# NOTE: Defaults to YYYY-MM-DD or $(date +%F)
[[ $# -eq 0 ]] && {
error 'Missing required argument to _formatDate_()'
return 1
}
local d="${1}"
local format="${2:-%F}"
format="${format//+/}"
if command -v gdate >/dev/null 2>&1; then
gdate -d "${d}" "+${format}"
else
date -d "${d}" "+${format}"
fi
}

651
utilities/files.bash Normal file
View File

@@ -0,0 +1,651 @@
_listFiles_() {
# DESC: Find files in a directory. Use either glob or regex
# ARGS: $1 (Required) - 'g|glob' or 'r|regex'
# $2 (Required) - pattern to match
# $3 (Optional) - directory
# OUTS: Prints files to STDOUT
# NOTE: Searches are NOT case sensitive and MUST be quoted
# USAGE: _listFiles_ glob "*.txt" "some/backup/dir"
# _listFiles_ regex ".*\.txt" "some/backup/dir"
# readarry -t array < <(_listFiles_ g "*.txt")
[[ $# -lt 2 ]] && {
error 'Missing required argument to _listFiles_()!'
return 1
}
local t="${1}"
local p="${2}"
local d="${3:-.}"
local fileMatch e
case "$t" in
glob | Glob | g | G)
while read -r fileMatch; do
echo "${e}"
done < <(find "${d}" -iname "${p}" -type f -maxdepth 1 | sort)
;;
regex | Regex | r | R)
while read -r fileMatch; do
e="$(realpath "${fileMatch}")"
echo "${e}"
done < <(find "${d}" -iregex "${p}" -type f -maxdepth 1 | sort)
;;
*)
echo "Could not determine if search was glob or regex"
return 1
;;
esac
}
_backupFile_() {
# DESC: Creates a backup of a specified file with .bak extension or
# optionally to a specified directory
# ARGS: $1 (Required) - Source file
# $2 (Optional) - Destination dir name used only with -d flag (defaults to ./backup)
# OPTS: -d - Move files to a backup direcory
# -m - Replaces copy (default) with move, effectively removing the
# OUTS: None
# USAGE: _backupFile_ "sourcefile.txt" "some/backup/dir"
# NOTE: dotfiles have their leading '.' removed in their backup
local opt
local OPTIND=1
local useDirectory=false
local moveFile=false
while getopts ":dDmM" opt; do
case ${opt} in
d | D) useDirectory=true ;;
m | M) moveFile=true ;;
*)
{
error "Unrecognized option '$1' passed to _makeSymlink_" "${LINENO}"
return 1
}
;;
esac
done
shift $((OPTIND - 1))
[[ $# -lt 1 ]] && fatal 'Missing required argument to _backupFile_()!'
local s="${1}"
local d="${2:-backup}"
local n # New filename (created by _uniqueFilename_)
# Error handling
[ ! "$(declare -f "_execute_")" ] \
&& {
warning "need function _execute_"
return 1
}
[ ! "$(declare -f "_uniqueFileName_")" ] \
&& {
warning "need function _uniqueFileName_"
return 1
}
[ ! -e "$s" ] \
&& {
warning "Source '${s}' not found"
return 1
}
if [ ${useDirectory} == true ]; then
[ ! -d "${d}" ] \
&& _execute_ "mkdir -p \"${d}\"" "Creating backup directory"
if [ -e "$s" ]; then
n="$(basename "${s}")"
n="$(_uniqueFileName_ "${d}/${s#.}")"
if [ ${moveFile} == true ]; then
_execute_ "mv \"${s}\" \"${d}/${n##*/}\"" "Moving: '${s}' to '${d}/${n##*/}'"
else
_execute_ "cp -R \"${s}\" \"${d}/${n##*/}\"" "Backing up: '${s}' to '${d}/${n##*/}'"
fi
fi
else
n="$(_uniqueFileName_ "${s}.bak")"
if [ ${moveFile} == true ]; then
_execute_ "mv \"${s}\" \"${n}\"" "Moving '${s}' to '${n}'"
else
_execute_ "cp -R \"${s}\" \"${n}\"" "Backing up '${s}' to '${n}'"
fi
fi
}
_cleanFilename_() {
# DESC: Cleans a filename of all non-alphanumeric (or user specified)
# characters and overwrites original
# ARGS: $1 (Required) - File to be cleaned
# $2 (optional) - Additional characters to be cleaned separated by commas
# OUTS: Overwrites file with new new and prints name of new file
# USAGE: _cleanFilename_ "FILENAME.TXT" "^,&,*"
# NOTE: IMPORTANT - This will overwrite the original file
# IMPORTANT - All spaces and underscores will be replaced by dashes (-)
[[ $# -lt 1 ]] && fatal 'Missing required argument to _cleanFilename_()!'
local arrayToClean
local fileToClean="$(realpath "$1")"
local optionalUserInput="${2-}"
IFS=',' read -r -a arrayToClean <<<"$optionalUserInput"
[ ! -f "${fileToClean}" ] \
&& {
warning "_cleanFileName_ ${fileToClean}: File doesn't exist"
return 1
}
local dir="$(realpath -d "${fileToClean}")"
local extension="${fileToClean##*.}"
local baseFileName="$(basename "${fileToClean%.*}")"
for i in "${arrayToClean[@]}"; do
baseFileName="$(echo "${baseFileName}" | sed "s/$i//g")"
done
baseFileName="$(echo "${baseFileName}" | tr -dc '[:alnum:]-_ ' | sed 's/ /-/g')"
local final="${dir}/${baseFileName}.${extension}"
if [ "${fileToClean}" != "${final}" ]; then
final="$(_uniqueFileName_ "${final}")"
if ${VERBOSE}; then
_execute_ "mv \"${fileToClean}\" \"${final}\""
else
_execute_ -q "mv \"${fileToClean}\" \"${final}\""
fi
echo "${final}"
else
echo "${fileToClean}"
fi
}
_parseFilename_() {
# DESC: Break a filename into its component parts which and place them into prefixed
# variables (dir, basename, extension, full path, etc.)
# with _parseFile...
# ARGS: $1 (Required) - A file
# OUTS: $_parsedFileFull - File and its real path (ie, resolve symlinks)
# $_parseFilePath - Path to the file
# $_parseFileName - Name of the file WITH extension
# $_parseFileBase - Name of file WITHOUT extension
# $_parseFileExt - The extension of the file (from _ext_())
[[ $# -lt 1 ]] && fatal 'Missing required argument to _parseFilename_()!'
local fileToParse="${1}"
[[ -f "${fileToParse}" ]] || {
error "Can't locate good file to parse at: ${fileToParse}"
return 1
}
# Ensure we are working with a real file, not a symlink
_parsedFileFull="$(realpath "${fileToParse}")" \
&& debug "${tan}\${_parsedFileFull}: ${_parsedFileFull-}${purple}"
# use the basename of the userFile going forward since the path is now in $filePath
_parseFileName=$(basename "${fileToParse}") \
&& debug "${tan}\$_parseFileName: ${_parseFileName}${purple}"
# Grab the filename without the extension
_parseFileBase="${_parseFileName%.*}" \
&& debug "${tan}\$_parseFileBase: ${_parseFileBase}${purple}"
# Grab the extension
if [[ "${fileToParse}" =~ .*\.[a-zA-Z]{2,4}$ ]]; then
_parseFileExt="$(_ext_ "${_parseFileName}")"
else
_parseFileExt=".${_parseFileName##*.}"
fi
debug "${tan}\$_parseFileExt: ${_parseFileExt}${purple}"
# Grab the directory
_parseFilePath="${_parsedFileFull%/*}" \
&& debug "${tan}\${_parseFilePath}: ${_parseFilePath}${purple}"
}
_decryptFile_() {
# DESC: Decrypts a file with openSSL
# ARGS: $1 (Required) - File to be decrypted
# $2 (Optional) - Name of output file (defaults to $1.decrypt)
# OUTS: None
# USAGE: _decryptFile_ "somefile.txt.enc" "decrypted_somefile.txt"
# NOTE: If a variable '$PASS' has a value, we will use that as the password
# to decrypt the file. Otherwise we will ask
[[ $# -lt 1 ]] && fatal 'Missing required argument to _decryptFile_()!'
local fileToDecrypt decryptedFile defaultName
fileToDecrypt="${1:?_decryptFile_ needs a file}"
defaultName="${fileToDecrypt%.enc}"
decryptedFile="${2:-$defaultName.decrypt}"
[ ! "$(declare -f "_execute_")" ] \
&& {
echo "need function _execute_"
return 1
}
[ ! -f "$fileToDecrypt" ] && return 1
if [ -z "${PASS}" ]; then
_execute_ "openssl enc -aes-256-cbc -d -in \"${fileToDecrypt}\" -out \"${decryptedFile}\"" "Decrypt ${fileToDecrypt}"
else
_execute_ "openssl enc -aes-256-cbc -d -in \"${fileToDecrypt}\" -out \"${decryptedFile}\" -k \"${PASS}\"" "Decrypt ${fileToDecrypt}"
fi
}
_encryptFile_() {
# DESC: Encrypts a file using openSSL
# ARGS: $1 (Required) - Input file
# $2 (Optional) - Name of output file (defaults to $1.enc)
# OUTS: None
# USAGE: _encryptFile_ "somefile.txt" "encrypted_somefile.txt"
# NOTE: If a variable '$PASS' has a value, we will use that as the password
# for the encrypted file. Otherwise we will ask.
local fileToEncrypt encryptedFile defaultName
fileToEncrypt="${1:?_encodeFile_ needs a file}"
defaultName="${fileToEncrypt%.decrypt}"
encryptedFile="${2:-$defaultName.enc}"
[ ! -f "$fileToEncrypt" ] && return 1
[ ! "$(declare -f "_execute_")" ] \
&& {
echo "need function _execute_"
return 1
}
if [ -z "${PASS}" ]; then
_execute_ "openssl enc -aes-256-cbc -salt -in \"${fileToEncrypt}\" -out \"${encryptedFile}\"" "Encrypt ${fileToEncrypt}"
else
_execute_ "openssl enc -aes-256-cbc -salt -in \"${fileToEncrypt}\" -out \"${encryptedFile}\" -k \"${PASS}\"" "Encrypt ${fileToEncrypt}"
fi
}
_ext_() {
# DESC: Extract the extension from a filename
# ARGS: $1 (Required) - Input file
# OPTS: -n - optional flag for number of extension levels (Ex: -n2)
# OUTS: Print extension to STDOUT
# USAGE:
# _ext_ foo.txt #==> txt
# _ext_ -n2 foo.tar.gz #==> tar.gz
# _ext_ foo.tar.gz #==> tar.gz
# _ext_ -n1 foo.tar.gz #==> gz
[[ $# -lt 1 ]] && fatal 'Missing required argument to _ext_()!'
local levels
local option
local filename
local exts
local ext
local fn
local i
unset OPTIND
while getopts ":n:" option; do
case $option in
n) levels=$OPTARG ;;
*) continue ;;
esac
done && shift $((OPTIND - 1))
filename=${1##*/}
[[ $filename == *.* ]] || return
fn=$filename
# Detect some common multi-extensions
if [[ ! ${levels-} ]]; then
case $(tr '[:upper:]' '[:lower:]' <<<"${filename}") in
*.tar.gz | *.tar.bz2) levels=2 ;;
esac
fi
levels=${levels:-1}
for ((i = 0; i < levels; i++)); do
ext=${fn##*.}
exts=${ext}${exts-}
fn=${fn%$ext}
[[ "$exts" == "${filename}" ]] && return
done
echo "$exts"
}
_extract_() {
# DESC: Extract a compressed file
# ARGS: $1 (Required) - Input file
# $2 (optional) - Input 'v' to show verbose output
# OUTS: None
local filename
local foldername
local fullpath
local didfolderexist
local vv
[[ $# -lt 1 ]] && fatal 'Missing required argument to _extract_()!'
[[ "${2-}" == "v" ]] && vv="v"
if [ -f "$1" ]; then
case "$1" in
*.tar.bz2 | *.tbz | *.tbz2) tar "x${vv}jf" "$1" ;;
*.tar.gz | *.tgz) tar "x${vv}zf" "$1" ;;
*.tar.xz)
xz --decompress "$1"
set -- "$@" "${1:0:-3}"
;;
*.tar.Z)
uncompress "$1"
set -- "$@" "${1:0:-2}"
;;
*.bz2) bunzip2 "$1" ;;
*.deb) dpkg-deb -x${vv} "$1" "${1:0:-4}" ;;
*.pax.gz)
gunzip "$1"
set -- "$@" "${1:0:-3}"
;;
*.gz) gunzip "$1" ;;
*.pax) pax -r -f "$1" ;;
*.pkg) pkgutil --expand "$1" "${1:0:-4}" ;;
*.rar) unrar x "$1" ;;
*.rpm) rpm2cpio "$1" | cpio -idm${vv} ;;
*.tar) tar "x${vv}f" "$1" ;;
*.txz)
mv "$1" "${1:0:-4}.tar.xz"
set -- "$@" "${1:0:-4}.tar.xz"
;;
*.xz) xz --decompress "$1" ;;
*.zip | *.war | *.jar) unzip "$1" ;;
*.Z) uncompress "$1" ;;
*.7z) 7za x "$1" ;;
*) return 1 ;;
esac
else
return 1
fi
shift
}
_json2yaml_() {
# DESC: Convert JSON to YAML
# ARGS: $1 (Required) - JSON file
# OUTS: None
python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' <"${1:?_json2yaml_ needs a file}"
}
_makeSymlink_() {
# DESC: Creates a symlink and backs up a file which may be overwritten by the new symlink. If the
# exact same symlink already exists, nothing is done.
# Default behavior will create a backup of a file to be overwritten
# ARGS: $1 (Required) - Source file
# $2 (Required) - Destination
# $3 (Optional) - Backup directory for files which may be overwritten (defaults to 'backup')
# OPTS: -n - Do not create a backup if target already exists
# -s - Use sudo when removing old files to make way for new symlinks
# OUTS: None
# USAGE: _makeSymlink_ "/dir/someExistingFile" "/dir/aNewSymLink" "/dir/backup/location"
# NOTE: This function makes use of the _execute_ function
local opt
local OPTIND=1
local backupOriginal=true
local useSudo=false
while getopts ":nNsS" opt; do
case $opt in
n | N) backupOriginal=false ;;
s | S) useSudo=true ;;
*)
{
error "Unrecognized option '$1' passed to _makeSymlink_" "$LINENO"
return 1
}
;;
esac
done
shift $((OPTIND - 1))
if ! command -v realpath >/dev/null 2>&1; then
error "We must have 'realpath' installed and available in \$PATH to run."
if [[ "$OSTYPE" == "darwin"* ]]; then
notice "Install coreutils using homebrew and rerun this script."
info "\t$ brew install coreutils"
fi
_safeExit_ 1
fi
[[ $# -lt 2 ]] && fatal 'Missing required argument to _makeSymlink_()!'
local s="$1"
local d="$2"
local b="${3-}"
local o
# Fix files where $HOME is written as '~'
d="${d/\~/$HOME}"
s="${s/\~/$HOME}"
b="${b/\~/$HOME}"
[ ! -e "$s" ] \
&& {
error "'$s' not found"
return 1
}
[ -z "$d" ] \
&& {
error "'${d}' not specified"
return 1
}
[ ! "$(declare -f "_execute_")" ] \
&& {
echo "need function _execute_"
return 1
}
[ ! "$(declare -f "_backupFile_")" ] \
&& {
echo "need function _backupFile_"
return 1
}
# Create destination directory if needed
[ ! -d "${d%/*}" ] \
&& _execute_ "mkdir -p \"${d%/*}\""
if [ ! -e "${d}" ]; then
_execute_ "ln -fs \"${s}\" \"${d}\"" "symlink ${s}${d}"
elif [ -h "${d}" ]; then
o="$(realpath "${d}")"
[[ "${o}" == "${s}" ]] && {
if [ "${DRYRUN}" == true ]; then
dryrun "Symlink already exists: ${s}${d}"
else
info "Symlink already exists: ${s}${d}"
fi
return 0
}
if [[ "${backupOriginal}" == true ]]; then
_backupFile_ "${d}" "${b:-backup}"
fi
if [[ "${DRYRUN}" == false ]]; then
if [[ "${useSudo}" == true ]]; then
command rm -rf "${d}"
else
command rm -rf "${d}"
fi
fi
_execute_ "ln -fs \"${s}\" \"${d}\"" "symlink ${s}${d}"
elif [ -e "${d}" ]; then
if [[ "${backupOriginal}" == true ]]; then
_backupFile_ "${d}" "${b:-backup}"
fi
if [[ "${DRYRUN}" == false ]]; then
if [[ "${useSudo}" == true ]]; then
sudo command rm -rf "${d}"
else
command rm -rf "${d}"
fi
fi
_execute_ "ln -fs \"${s}\" \"${d}\"" "symlink ${s}${d}"
else
warning "Error linking: ${s}${d}"
return 1
fi
return 0
}
_parseYAML_() {
# DESC: Convert a YANML file into BASH variables for use in a shell script
# ARGS: $1 (Required) - Source YAML file
# $2 (Required) - Prefix for the variables to avoid namespace collisions
# OUTS: Prints variables and arrays derived from YAML File
# USAGE: To source into a script
# _parseYAML_ "sample.yml" "CONF_" > tmp/variables.txt
# source "tmp/variables.txt"
#
# NOTE: https://gist.github.com/DinoChiesa/3e3c3866b51290f31243
# https://gist.github.com/epiloque/8cf512c6d64641bde388
local yamlFile="${1:?_parseYAML_ needs a file}"
local prefix="${2-}"
[ ! -s "${yamlFile}" ] && return 1
local s='[[:space:]]*'
local w='[a-zA-Z0-9_]*'
local fs="$(echo @ | tr @ '\034')"
sed -ne "s|^\(${s}\)\(${w}\)${s}:${s}\"\(.*\)\"${s}\$|\1${fs}\2${fs}\3|p" \
-e "s|^\(${s}\)\(${w}\)${s}[:-]${s}\(.*\)${s}\$|\1${fs}\2${fs}\3|p" "${yamlFile}" \
| awk -F"${fs}" '{
indent = length($1)/2;
if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";}
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s%s=(\"%s\")\n", "'"${prefix}"'",vn, $2, conj[indent-1],$3);
}
}' | sed 's/_=/+=/g' | sed 's/[[:space:]]*#.*"/"/g'
}
_readFile_() {
# DESC: Prints each line of a file
# ARGS: $1 (Required) - Input file
# OUTS: Prints contents of file
[[ $# -lt 1 ]] && fatal 'Missing required argument to _readFile_()!'
local result
local c="$1"
[ ! -f "$c" ] \
&& {
echo "'$c' not found"
return 1
}
while read -r result; do
echo "${result}"
done <"${c}"
}
_sourceFile_() {
# DESC: Source a file into a script
# ARGS: $1 (Required) - File to be sourced
# OUTS: None
[[ $# -lt 1 ]] && fatal 'Missing required argument to _sourceFile_()!'
local c="$1"
[ ! -f "$c" ] \
&& {
fatal "Attempted to source '$c' Not found"
return 1
}
source "$c"
return 0
}
_uniqueFileName_() {
# DESC: Ensure a file to be created has a unique filename to avoid overwriting other files
# ARGS: $1 (Required) - Name of file to be created
# $2 (Optional) - Separation characted (Defaults to a period '.')
# OUTS: Prints unique filename to STDOUT
# USAGE: _uniqueFileName_ "/some/dir/file.txt" "-"
local fullfile="${1:?_uniqueFileName_ needs a file}"
local spacer="${2:-.}"
local directory
local filename
local extension
local newfile
local n
if ! command -v realpath >/dev/null 2>&1; then
error "We must have 'realpath' installed and available in \$PATH to run."
if [[ "$OSTYPE" == "darwin"* ]]; then
notice "Install coreutils using homebrew and rerun this script."
info "\t$ brew install coreutils"
fi
_safeExit_ 1
fi
# Find directories with realpath if input is an actual file
if [ -e "${fullfile}" ]; then
fullfile="$(realpath "${fullfile}")"
fi
directory="$(dirname "${fullfile}")"
filename="$(basename "${fullfile}")"
# Extract extensions only when they exist
if [[ "${filename}" =~ \.[a-zA-Z]{2,4}$ ]]; then
extension=".${filename##*.}"
filename="${filename%.*}"
fi
newfile="${directory}/${filename}${extension-}"
if [ -e "${newfile}" ]; then
n=1
while [[ -e "${directory}/${filename}${extension-}${spacer}${n}" ]]; do
((n++))
done
newfile="${directory}/${filename}${extension-}${spacer}${n}"
fi
echo "${newfile}"
return 0
}
_yaml2json_() {
# DESC: Convert a YAML file to JSON
# ARGS: $1 (Required) - Input YAML file
# OUTS: None
python -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin), sys.stdout, indent=4)' <"${1:?_yaml2json_ needs a file}"
}

39
utilities/macOS.bash Normal file
View File

@@ -0,0 +1,39 @@
# Functions for use on computers running MacOS
_haveScriptableFinder_() {
# DESC: Determine whether we can script the Finder or not
# ARGS: None
# OUTS: true/false
local finder_pid
finder_pid="$(pgrep -f /System/Library/CoreServices/Finder.app | head -n 1)"
if [[ (${finder_pid} -gt 1) && ("${STY-}" == "") ]]; then
return 0
else
return 1
fi
}
_guiInput_() {
# DESC: Ask for user input using a Mac dialog box
# ARGS: $1 (Optional) - Text in dialogue box (Default: Password)
# OUTS: None
# NOTE: https://github.com/herrbischoff/awesome-osx-command-line/blob/master/functions.md
if _haveScriptableFinder_; then
guiPrompt="${1:-Password:}"
guiInput=$(
osascript &>/dev/null <<EOF
tell application "System Events"
activate
text returned of (display dialog "${guiPrompt}" default answer "" with hidden answer)
end tell
EOF
)
echo -n "${guiInput}"
else
error "No GUI input without macOS"
return 1
fi
}

71
utilities/numbers.bash Normal file
View File

@@ -0,0 +1,71 @@
_fromSeconds_() {
# DESC: Convert seconds to HH:MM:SS
# ARGS: $1 (Required) - Time in seconds
# OUTS: Print HH:MM:SS to STDOUT
# USAGE: _convertSecs_ "SECONDS"
# To compute the time it takes a script to run:
# STARTTIME=$(date +"%s")
# ENDTIME=$(date +"%s")
# TOTALTIME=$(($ENDTIME-$STARTTIME)) # human readable time
# _convertSecs_ "$TOTALTIME"
((h = ${1} / 3600))
((m = (${1} % 3600) / 60))
((s = ${1} % 60))
printf "%02d:%02d:%02d\n" $h $m $s
}
_toSeconds_() {
# DESC: Converts HH:MM:SS to seconds
# ARGS: $1 (Required) - Time in HH:MM:SS
# OUTS: Print seconds to STDOUT
# USAGE: _toSeconds_ "01:00:00"
# NOTE: Acceptable Input Formats
# 24 12 09
# 12,12,09
# 12;12;09
# 12:12:09
# 12-12-09
# 12H12M09S
# 12h12m09s
local saveIFS
if [[ "$1" =~ [0-9]{1,2}(:|,|-|_|,| |[hHmMsS])[0-9]{1,2}(:|,|-|_|,| |[hHmMsS])[0-9]{1,2} ]]; then
saveIFS="$IFS"
IFS=":,;-_, HhMmSs" read -r h m s <<< "$1"
IFS="$saveIFS"
else
h="$1"
m="$2"
s="$3"
fi
echo $(( 10#$h * 3600 + 10#$m * 60 + 10#$s ))
}
_countdown_() {
# DESC: Sleep for a specified amount of time
# ARGS: $1 (Optional) - Total seconds to sleep for(Default is 10)
# $2 (Optional) - Increment to count down
# $3 (Optional) - Message to print at each increment (default is ...)
# OUTS: None
# USAGE: _countdown_ 10 1 "Waiting for cache to invalidate"
local i ii t
local n=${1:-10}
local stime=${2:-1}
local message="${3:-...}"
((t = n + 1))
for ((i = 1; i <= n; i++)); do
((ii = t - i))
if declare -f "info" &>/dev/null 2>&1; then
info "${message} ${ii}"
else
echo "${message} ${ii}"
fi
sleep $stime
done
}

148
utilities/services.bash Normal file
View File

@@ -0,0 +1,148 @@
_haveInternet_() {
# DESC: Tests to see if there is an active Internet connection
# ARGS: None
# OUTS: None
# USAGE: _haveInternet_ && [SOMETHING]
# NOTE: https://stackoverflow.com/questions/929368/
if command -v fping &>/dev/null; then
fping 1.1.1.1 &>/dev/null \
&& return 0 \
|| return 1
elif ping -t 2 -c 1 1 1.1.1.1 &>/dev/null; then
return 0
elif command -v route &>/dev/null; then
local GATEWAY="$(route -n get default | grep gateway)"
ping -t 2 -c 1 "$(echo "${GATEWAY}" | cut -d ':' -f 2)" &>/dev/null \
&& return 0 \
|| return 1
elif command -v ip &>/dev/null; then
ping -t 2 -c 1 "$(ip r | grep default | cut -d ' ' -f 3)" &>/dev/null \
&& return 0 \
|| return 1
else
return 1
fi
}
_httpStatus_() {
# DESC: Report the HTTP status of a specified URL
# ARGS: $1 (Required) - URL (will work fine without https:// prefix)
# $2 (Optional) - Seconds to wait until timeout (Default is 3)
# $3 (Optional) - either '--code' or '--status' (default)
# $4 (optional) - CURL opts separated by spaces (Use -L to follow redirects)
# OUTS: Prints output to STDOUT
# USAGE: _httpStatus_ URL [timeout] [--code or --status] [curl opts]
# NOTE: https://gist.github.com/rsvp/1171304
#
# Example: $ _httpStatus_ bit.ly
# 301 Redirection: Moved Permanently
#
# Example: $ _httpStatus_ www.google.com 100 --code
local code
local status
local saveIFS=${IFS}
IFS=$' \n\t'
local url=${1:?_httpStatus_ needs an url}
local timeout=${2:-'3'}
local flag=${3:-'--status'}
local arg4=${4:-''}
local arg5=${5:-''}
local arg6=${6:-''}
local arg7=${7:-''}
local curlops="${arg4} ${arg5} ${arg6} ${arg7}"
# __________ get the CODE which is numeric:
code=$(echo "$(curl --write-out %{http_code} --silent --connect-timeout "${timeout}" \
--no-keepalive "${curlops}" --output /dev/null "${url}")")
# __________ get the STATUS (from code) which is human interpretable:
case $code in
000) status="Not responding within ${timeout} seconds" ;;
100) status="Informational: Continue" ;;
101) status="Informational: Switching Protocols" ;;
200) status="Successful: OK within ${timeout} seconds" ;;
201) status="Successful: Created" ;;
202) status="Successful: Accepted" ;;
203) status="Successful: Non-Authoritative Information" ;;
204) status="Successful: No Content" ;;
205) status="Successful: Reset Content" ;;
206) status="Successful: Partial Content" ;;
300) status="Redirection: Multiple Choices" ;;
301) status="Redirection: Moved Permanently" ;;
302) status="Redirection: Found residing temporarily under different URI" ;;
303) status="Redirection: See Other" ;;
304) status="Redirection: Not Modified" ;;
305) status="Redirection: Use Proxy" ;;
306) status="Redirection: status not defined" ;;
307) status="Redirection: Temporary Redirect" ;;
400) status="Client Error: Bad Request" ;;
401) status="Client Error: Unauthorized" ;;
402) status="Client Error: Payment Required" ;;
403) status="Client Error: Forbidden" ;;
404) status="Client Error: Not Found" ;;
405) status="Client Error: Method Not Allowed" ;;
406) status="Client Error: Not Acceptable" ;;
407) status="Client Error: Proxy Authentication Required" ;;
408) status="Client Error: Request Timeout within ${timeout} seconds" ;;
409) status="Client Error: Conflict" ;;
410) status="Client Error: Gone" ;;
411) status="Client Error: Length Required" ;;
412) status="Client Error: Precondition Failed" ;;
413) status="Client Error: Request Entity Too Large" ;;
414) status="Client Error: Request-URI Too Long" ;;
415) status="Client Error: Unsupported Media Type" ;;
416) status="Client Error: Requested Range Not Satisfiable" ;;
417) status="Client Error: Expectation Failed" ;;
500) status="Server Error: Internal Server Error" ;;
501) status="Server Error: Not Implemented" ;;
502) status="Server Error: Bad Gateway" ;;
503) status="Server Error: Service Unavailable" ;;
504) status="Server Error: Gateway Timeout within ${timeout} seconds" ;;
505) status="Server Error: HTTP Version Not Supported" ;;
*) die "httpstatus: status not defined." ;;
esac
case ${flag} in
--status) echo "${code} ${status}" ;;
-s) echo "${code} ${status}" ;;
--code) echo "${code}" ;;
-c) echo "${code}" ;;
*) echo " httpstatus: bad flag" && _safeExit_ ;;
esac
IFS="${saveIFS}"
}
_pushover_() {
# DESC: Sends a notification via Pushover
# ARGS: $1 (Required) - Title of notification
# $2 (Required) - Body of notification
# OUTS: None
# USAGE: _pushover_ "Title Goes Here" "Message Goes Here"
# NOTE: The variables for the two API Keys must have valid values
# Credit: http://ryonsherman.blogspot.com/2012/10/shell-script-to-send-pushover.html
local PUSHOVERURL
local API_KEY
local USER_KEY
local DEVICE
local TITLE
local MESSAGE
PUSHOVERURL="https://api.pushover.net/1/messages.json"
API_KEY="${PUSHOVER_API_KEY}"
USER_KEY="${PUSHOVER_USER_KEY}"
DEVICE=""
TITLE="${1}"
MESSAGE="${2}"
curl \
-F "token=${API_KEY}" \
-F "user=${USER_KEY}" \
-F "device=${DEVICE}" \
-F "title=${TITLE}" \
-F "message=${MESSAGE}" \
"${PUSHOVERURL}" >/dev/null 2>&1
}

View File

@@ -0,0 +1,289 @@
# Transform text using these functions
# Some were adapted from https://github.com/jmcantrell/bashful
_cleanString_() {
# DESC: Cleans a string of text
# ARGS: $1 (Required) - String to be cleaned
# $2 (optional) - Specific characters to be cleaned (separated by commas,
# escape regex special chars)
# OPTS: -l Forces all text to lowercase
# -u Forces all text to uppercase
# -a Removes all non-alphanumeric characters except for spaces and dashes
# -p Replace one character with another (separated by commas) (escape regex characters)
# -s In combination with -a, replaces characters with a space
# OUTS: Prints result to STDOUT
# USAGE: _cleanString_ [OPT] [STRING] [CHARS TO REPLACE]
# _cleanString_ -p " ,-" [STRING] [CHARS TO REPLACE]
# NOTES: Always cleaned:
# - leading white space
# - trailing white space
# - multiple spaces become a single space
# - remove spaces before and aftrer -_
local opt
local lc=false
local uc=false
local alphanumeric=false
local replace=false
local us=false
local OPTIND=1
while getopts ":lLuUaAsSpP" opt; do
case $opt in
l | L) lc=true ;;
u | U) uc=true ;;
a | A) alphanumeric=true ;;
s | S) us=true ;;
p | P)
shift
local pairs=()
IFS=',' read -r -a pairs <<<"$1"
replace=true ;;
*)
{
error "Unrecognized option '$1' passed to _execute. Exiting."
return 1
}
;;
esac
done
shift $((OPTIND - 1))
[[ $# -lt 1 ]] && fatal 'Missing required argument to _cleanString_()!'
local string="${1}"
local userChars="${2:-}"
local arrayToClean=()
IFS=',' read -r -a arrayToClean <<<"${userChars}"
# trim trailing/leading white space and duplicate spaces/tabs
string="$(echo "${string}" | awk '{$1=$1};1')"
local i
for i in "${arrayToClean[@]}"; do
debug "cleaning: $i"
string="$(echo "${string}" | sed "s/$i//g")"
done
("${lc}") \
&& string="$(echo "${string}" | tr '[:upper:]' '[:lower:]')"
("${uc}") \
&& string="$(echo "${string}" | tr '[:lower:]' '[:upper:]')"
if "${alphanumeric}" && "${us}"; then
string="$(echo "${string}" | tr -c '[:alnum:] -' ' ')"
elif "${alphanumeric}"; then
string="$(echo "${string}" | sed "s/[^a-zA-Z0-9 -]//g")"
fi
if "${replace}"; then
string="$(echo "${string}" | sed "s/${pairs[0]}/${pairs[1]}/g")"
fi
# trim trailing/leading white space and duplicate dashes
string="$(echo "${string}" | tr -s '-')"
string="$(echo "${string}" | sed -E 's/([-_]) /\1/g' | sed -E 's/ ([-_])/\1/g')"
string="$(echo "${string}" | awk '{$1=$1};1')"
printf "%s\n" "${string}"
}
_stopWords_() {
# DESC: Removes common stopwords from a string
# ARGS: $1 (Required) - String to parse
# $2 (Optional) - Additional stopwords (comma separated)
# OUTS: Prints cleaned string to STDOUT
# USAGE: cleanName="$(_stopWords_ "[STRING]" "[MORE,STOP,WORDS]")"
# NOTE: Requires a stopwords file in sed format (expected at: ~/.sed/stopwords.sed)
[[ $# -lt 1 ]] && {
warning 'Missing required argument to _stripCommonWords_!'
return 1
}
[ "$(command -v gsed)" ] || {
error "Can not continue without gsed. Use '${YELLOW}brew install gnu-sed${reset}'"
return 1
}
local string="${1}"
local sedFile="${HOME}/.sed/stopwords.sed"
if [ -f "${sedFile}" ]; then
string="$(echo "${string}" | gsed -f "${sedFile}")"
else
debug "Missing sedfile in _stopWords_()"
fi
declare -a localStopWords=()
IFS=',' read -r -a localStopWords <<<"${2-}"
if [[ ${#localStopWords[@]} -gt 0 ]]; then
for w in "${localStopWords[@]}"; do
string="$(echo "$string" | gsed -E "s/$w//gI")"
done
fi
# Remove double spaces and trim left/right
string="$(echo "$string" | sed -E 's/[ ]{2,}/ /g' | _ltrim_ | _rtrim_)"
echo "${string}"
}
_escape_() {
# DESC: Escapes a string by adding \ before special chars
# ARGS: $@ (Required) - String to be escaped
# OUTS: Prints output to STDOUT
# USAGE: _escape_ "Some text here"
# shellcheck disable=2001
echo "${@}" | sed 's/[]\.|$[ (){}?+*^]/\\&/g'
}
_htmlDecode_() {
# DESC: Decode HTML characters with sed
# ARGS: $1 (Required) - String to be decoded
# OUTS: Prints output to STDOUT
# USAGE: _htmlDecode_ <string>
# NOTE: Must have a sed file containing replacements
[[ $# -lt 1 ]] && {
error 'Missing required argument to _htmlDecode_()!'
return 1
}
local sedFile
sedFile="${HOME}/.sed/htmlDecode.sed"
[ -f "${sedFile}" ] \
&& { echo "${1}" | sed -f "${sedFile}"; } \
|| return 1
}
_htmlEncode_() {
# DESC: Encode HTML characters with sed
# ARGS: $1 (Required) - String to be encoded
# OUTS: Prints output to STDOUT
# USAGE: _htmlEncode_ <string>
# NOTE: Must have a sed file containing replacements
[[ $# -lt 1 ]] && {
error 'Missing required argument to _htmlEncode_()!'
return 1
}
local sedFile
sedFile="${HOME}/.sed/htmlEncode.sed"
[ -f "${sedFile}" ] \
&& { echo "${1}" | sed -f "${sedFile}"; } \
|| return 1
}
_lower_() {
# DESC: Convert a string to lowercase
# ARGS: None
# OUTS: None
# USAGE: text=$(_lower_ <<<"$1")
# echo "STRING" | _lower_
tr '[:upper:]' '[:lower:]'
}
_upper_() {
# DESC: Convert a string to uppercase
# ARGS: None
# OUTS: None
# USAGE: text=$(_upper_ <<<"$1")
# echo "STRING" | _upper_
tr '[:lower:]' '[:upper:]'
}
_ltrim_() {
# DESC: Removes all leading whitespace (from the left)
# ARGS: None
# OUTS: None
# USAGE: text=$(_ltrim_ <<<"$1")
# echo "STRING" | _ltrim_
local char=${1:-[:space:]}
sed "s%^[${char//%/\\%}]*%%"
}
_regex_() {
# DESC: Use regex to validate and parse strings
# ARGS: $1 (Required) - Input String
# $2 (Required) - Regex pattern
# OUTS: Prints string matching regex
# Returns error if no part of string did not match regex
# USAGE: regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' || echo "no match found"
# NOTE: This example only prints the first matching group. When using multiple capture
# groups some modification is needed.
# https://github.com/dylanaraps/pure-bash-bible
if [[ $1 =~ $2 ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
return 0
else
return 1
fi
}
_rtrim_() {
# DESC: Removes all leading whitespace (from the right)
# ARGS: None
# OUTS: None
# USAGE: text=$(_rtrim_ <<<"$1")
# echo "STRING" | _rtrim_
local char=${1:-[:space:]}
sed "s%[${char//%/\\%}]*$%%"
}
_trim_() {
# DESC: Removes all leading/trailing whitespace
# ARGS: None
# OUTS: None
# USAGE: text=$(_trim_ <<<"$1")
# echo "STRING" | _trim_
awk '{$1=$1;print}'
}
_urlEncode_() {
# DESC: URL encode a string
# ARGS: $1 (Required) - String to be encoded
# OUTS: Prints output to STDOUT
# USAGE: _urlEncode_ <string>
# NOTE: https://gist.github.com/cdown/1163649
[[ $# -lt 1 ]] && {
error 'Missing required argument to _urlEncode_()!'
return 1
}
local LANG=C
local i
for ((i = 0; i < ${#1}; i++)); do
if [[ ${1:$i:1} =~ ^[a-zA-Z0-9\.\~_-]$ ]]; then
printf "${1:$i:1}"
else
printf '%%%02X' "'${1:$i:1}"
fi
done
}
_urlDecode_() {
# DESC: Decode a URL encoded string
# ARGS: $1 (Required) - String to be decoded
# OUTS: Prints output to STDOUT
# USAGE: _urlDecode_ <string>
[[ $# -lt 1 ]] && {
error 'Missing required argument to _urlDecode_()!'
return 1
}
local url_encoded="${1//+/ }"
printf '%b' "${url_encoded//%/\\x}"
}