Shell scripting in macOS – Part 10: Distribution

This is the last article in the series of scripts. We will take the script from Part 8 and continue with it. We will be taking our folderCreator script and using it in our distribution process.

Distribution is the process of placing the script along with any supporting resources on a destination computer making it easily available for use. There are many aspects to this process, but we are going to look at a few of them:

  • Where should our script be installed?
  • How will the end user be invoking the script?
  • Does the script have its own man page?

Creating our own commands from other shell scripts

You may have noticed that whenever we use commands like defaults, echo, ls, cd, … we do not have to specify the path to the command. There is a reason for that. When we run our script the shell interpreter ‘knows’ where to go and look for these commands. There are some predefined locations available. We can get these predefined locations from the environment.

printenv PATH

This gives us an output that looks like:

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

The output you see will vary from computer to computer. As we can see it is a list of folders separated by a ‘:’. These are the folders the interpreter will go through to locate the commands we use.

While we do not specify the path to a command in our script it is actually considered good practice. In fact, we will be making the same change to our script.

So if we want our own commands to be located easily it should be placed in one of these folders.

Installing commands

While installing commands there is one feature of macOS that we need to keep in mind: SIP (System Integrity Protection). Introduced in OS X 10.11 El Capitan, SIP prevents the modification of certain system files or folders even if you have root privileges. So this means that we do not have a lot of choice when it comes to installing our command. We will place it in /usr/local/bin folder.

Here is the final version of the folderCreator script. I have added paths to some commands and have kept the file name as folderCreator. The version number has been removed from the file name.

#!/bin/zsh

#-------------------------------------------------------------------------------------------------
#NAME:		Folder creator
#AUTHOR:	Arun Patwardhan
#CONTACT:	arun@amaranthine.co.in
#DATE:		15th September 2022
#WEBSITE:	https://github.com/AmaranthineTech/ShellScripts
#-------------------------------------------------------------------------------------------------

#LEGAL DISCLAIMER --------------------------------------------------------------------------------
#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.
#-------------------------------------------------------------------------------------------------

#LICENSE/TERMS AND CONDITIONS --------------------------------------------------------------------
#MIT License

#Copyright (c) Amaranthine 2021.

#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.
#-------------------------------------------------------------------------------------------------

#ABOUT -------------------------------------------------------------------------------------------
# fileCreator.zsh
# 1.8
#-------------------------------------------------------------------------------------------------

#DESCRIPTION ------------------------------------------------------------------------------------- 
# - THis script is intended for creating the custom folders that are required on all corporate computers. 
# - Run this script on a new computer or a computer being reassigned to another employee.
# - This script can run on all computers.
#-------------------------------------------------------------------------------------------------

#USAGE -------------------------------------------------------------------------------------------
# - To create folders with default names run the command: ./folderCreator.zsh
# - To define your own folder names: ./folderCreator.zsh <folder1> <folder2> <folder3>
# - Available options  : Only the help option is available
# - Getting help       : Use the -h or the -help options to get more information. Or you can use the man command to view the man page.
#-------------------------------------------------------------------------------------------------

#WARNING/CAUTION ---------------------------------------------------------------------------------
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
# This script doesn't perform any validation of the folder names being passed in by the user. 
# If the script does not see the -h or the -help options then it will assume that the data being passed in is the name of the folder.
# The user of the script must ensure that the desired folder names are passed in.
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#-------------------------------------------------------------------------------------------------

#INSTALLATION ------------------------------------------------------------------------------------
# Instructions for placing the script in the correct place are listed here. 
# Location:		/Library/Scripts/
# Permissions:	rwx r-x r-x
#-------------------------------------------------------------------------------------------------

#REQUIREMENTS ------------------------------------------------------------------------------------
# Shell:		/bin/zsh
# OS:			macOS Big Sur 11.4 or later
# Dependencies:	None
#-------------------------------------------------------------------------------------------------

#HELP/SUPPORT ------------------------------------------------------------------------------------
# You can get help by running the following commands.
# ./folderCreator.zsh -h
# ./folderCreator.zsh -help
# OR
# man folderCreator.zsh
# You can also view the log file for the same at: ~/Library/Logs/folderCreator_log_v1-8.log
#-------------------------------------------------------------------------------------------------

#HISTORY -----------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
# Version 1.0: Basic script which creates the folders
# Version 1.1: Gives user the ability to specify the folder names at run time.
# Version 1.2: Adds safety checks to the scripts
# Version 1.3: Includes documentation as well as ability to get help.
# Version 1.4: Includes optimisation using for loop
# Version 1.5: Prompts the user in the GUI for names for the different folders.
# Version 1.6: Updated the log mechanism with the help of a function and here document.
# Version 1.7: Replaced the folder variables with an array
# Version 1.8: Added absolute path for commands. Final version ready for deployment.
#-------------------------------------------------------------------------------------------------

#-------------------------------------------------------------------------------------------------
# ------------------------------ SCRIPT STARTS HERE ----------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------

#These are the default values used for the folder names incase the user doesn't provide any.
FOLDERS=("Tools" "Reports" "Help")

#Script version number
VERSION_NUMBER="1.8"

#Command name
COMMAND_NAME="folderCreator.zsh"

#1. Check to see if the user is asking for help. In which case we will have to provide information about the command.
if [[ $1 == "-h" ]] || [[ $1 == "-help" ]]; then
	echo "ABOUT 
-----
fileCreator.zsh
Version $VERSION_NUMBER

NAME 
----
$COMMAND_NAME — Folder creation utility SYNOPSIS
$COMMAND_NAME folder names [ verbs ]

DESCRIPTION 
-----------
$COMMAND_NAME creates 3 folders in the home folder. In case the folder names are not provided then the command will create folders with default names 'Tools', 'Reports', \"Help\".

There is also the option of getting help via the help verb.
- This script is intended for creating the custom folders that are required on all corporate computers. 
- Run this script on a new computer or a computer being reassigned to another employee.
- This script can run on all computers.

VERBS 
-----
[ −h −help] Both the options are used to invoke the help documentation.
[ −v −version] Both the options are used to get the version number of the folderCreator command.

REQUIREMENTS 
------------
The following are the minimum requirements to get the script running.
Shell:			zsh
OS:				macOS Big Sur 11.4 or later
Dependencies:	None

INSTALLATION 
------------
$COMMAND_NAME can be installed anywhere you wish. However, there are certain locations that are recommended.
Location:		/Library/Scripts/ 
Permissions:		rwxr-xr-x

USAGE  
-----
$COMMAND_NAME folder1 folder2 folder3 
Will create folders with your own names. 

$COMMAND_NAME -h OR $COMMAND_NAME -help 
Will invoke the help utility.

$COMMAND_NAME -v OR $COMMAND_NAME -version 
will print the version number in stdout.

WARNING/CAUTION  
---------------
$COMMAND_NAME does not perform any validation of names. The only options that folderCreator accepts are -h and -help verbs or the -v and 
-version verbs. If the script does not see the -h , -help or the -v , -version options then it will assume that the data being passed in is 
the name of the folder. The user of the folderCreator command must ensure that the desired folder names are passed in. The user will also be 
prompted, via the graphical user interface, if he/she wishes to provide the names for the folders. If yes, then there will be subsequent 
prompts asking for the folder names.

EXAMPLES 
--------
$COMMAND_NAME Resources Results Assistant
This will create 3 folders Resources , Results , Assistant , in the user’s home folder. 

$COMMAND_NAME
This will create 3 folders with the default names

$COMMAND_NAME Apps
This will use the Apps name for the first folder but the default names for the last 2 folders. 

NOTE
----
The user will be asked if he/she wishes to provide custom names in all the examples mentioned above. The user's value will always override 
whatever is being provided to the script or defaults.

DIAGNOSTICS 
-----------
The script produces a log file called ~/Library/Logs/folderCreator_log_v1-x.log
This file is typically located in the user’s home folder log folder. The x represents the version number of $COMMAND_NAME
You can view the logs for each respective version.

COPYRIGHT  
---------
Copyright (c) Amaranthine 2015-2021. All rights reserved. https://amaranthine.in

EXIT STATUS  
-----------
In most situations, $COMMAND_NAME exits 0 on success"
	exit 0
fi

PATH_TO_LOG="$HOME/Library/Logs/folderCreator_log_v1-8.log"

# Function to log activity
function recordActivity() {
	/bin/cat << EOF >> $PATH_TO_LOG
[$(date)] $1
EOF
}


echo "$(date) Running script $0 to create folders."
echo ""

TODAY=$(date)

recordActivity "Starting"

#2. Check to see if the version number is 
if [[ $1 == "-version" ]] || [[ $1 == "-v" ]]; then
	echo "Version: $VERSION_NUMBER"
	exit 0
fi

#3. The following if statements check to see if the script is receiving any arguments. It then picks those arguments and assigns them to the respective variables for use in the script.
if [[ $1 != "" ]]; then
	FOLDERS[0]=$1
fi

if [[ $2 != "" ]]; then
	FOLDERS[1]=$2
fi

if [[ $3 != "" ]]; then
	FOLDERS[2]=$3
fi

#4. We can prompt the user to see if they wish to provide folder names themselves. This will override any values provided as arguments.
userClicked=$(/usr/bin/osascript -e 'button returned of (display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns")')
	
# if the user decides to provide custom names then go ahead and ask the user via GUI prompts. Otherwise use the values sent as arguments or defaults.	
if [[ $userClicked == "Custom" ]]; then
	recordActivity "The user decided to provide custom names."
	
	FOLDERS[0]=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 1" default answer "Utilities" buttons {"OK"} default button 1 with title "Folder that will hold the utilities" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	FOLDERS[1]=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 2" default answer "Tools" buttons {"OK"} default button 1 with title "Folder that will hold the tools" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	FOLDERS[2]=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 3" default answer "Help" buttons {"OK"} default button 1 with title "Folder that will hold the support documents" with icon POSIX file "/Users/Shared/Finder.icns")')
		
	recordActivity "User provided: ${FOLDER[@]}"
else
	recordActivity "User decided to use default values: ${FOLDER[@]}"
fi

#5. Go to the home folder.
cd $HOME

#6. Check to see if each of the folders exists. If it exists then do not create it. Else create the folder. 
recordActivity "Creating folders: ${FOLDER[@]}"

for item in ${FOLDER[@]}; do
	if [[ -d $item ]]; then
		recordActivity "Not creating $item as it already exists."
	else
		recordActivity "Creating $item"
		/bin/mkdir $item
	fi
	
	#7. Create the task completion file inside each folder.
	recordActivity "Creating hidden file for $item folder."
	cd $item
	#8. Generate the file names based on the folder names.
	/usr/bin/touch ".$item-FolderCreated"
	cd ..
done

echo "$(date) Task completed. Have a nice day!"
	
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ END OF SCRIPT ---------------------------------------------------

In order to run this command we will have to give it execute capabilities.

chmod 755 folderCreator

This should change the icon of the script to that of an executable. If it doesn’t then go ahead and remove the extension from the file.

Copy the file to the /usr/local/bin folder. You will need to authenticate as admin to do this. There may be other executables in this folder.

Using commands

Now let us test to see if this has worked. Run the command:

folderCreator -h

We can use the which command to confirm that the correct binary is being used.

which folderCreator

Adding path to the environment

Now, it is not necessary that the executables we create should be placed in one of the standard PATH folders. We could place it anywhere else. All we would have to do is to export this new folder path to the PATH environment variable. There are a few ways of doing this.

  1. Use the export command
  2. Source another script file into your script
  3. Automatically configure bash/zsh to source the export command
Using the export command

The export command temporarily adds a value to the PATH environment variable. We could do this at the start of the script. Let us look at this as an example.

  1. Create a script, message.bash and save it in the /Users/Shared/Scripts/.
#!/bin/bash

echo "$(date) This is a random script $RANDOM"
  1. Create another script called test.bash and save it where ever you want.
#!/bin/bash

export DEVELOPER_PATH=/Users/Shared/Scripts/
export PATH=$DEVELOPER_PATH:$PATH

bash welcome.bash

which welcome.bash
  1. Run the script with the command: bash test.bash. Don’t forget to put the path when using the script.

The 2 export commands are setting the value of the PATH environment variable to the new path. Note that while doing that we still keep the existing PATH value. So the environment will contain all the existing PATHS as well as the new path.

We can immediately see the benefit of adding another folder to our PATH component. The commands on line 6 and 8 do not require the path to the welcome.bash script to be explicitly mentioned. Even though they are not in the standard search paths. In fact, any script/executable that is placed in that folder will now be directly accessible without having to specify the absolute path. Having said that it is still a good idea to put the absolute path to a command.

An important thing to keep in mind is that this change only applies to the script that we are running. This will not impact other shells or other scripts running in the same shell. The next 2 options will show us how we could possibly do that.

Source the export commands from another script

If the export commands are needed in more than one script then it might be a good idea to source them instead of rewriting them over and over again.

Start off by creating a new file called newPath.bash. It should only have the 2 export commands in them.

#!/bin/bash

export DEVELOPER_PATH=/Users/Shared/Scripts/
export PATH=$DEVELOPER_PATH:$PATH

Now we will modify our test.bash script from the previous example as shown below.

#!/bin/bash
#Using source

source /Users/Shared/newPath.bash

bash welcome.bash

which welcome.bash

You can see that we simply source the original script in here. This is extremely useful if there are multiple paths that need to be added to multiple scripts. Any change in the path only has to be made in one place making this approach far more convenient and scalable. There is still the same catch. Any change to the path is only applicable to the scope of the script. Other scripts and the shell environment itself does not get affected.

Configure bash/zsh to source our export commands.

While the pervious 2 approaches are good, they have the limitation that the changes are only applicable to the script where the sourcing is done. All the other scripts and the shell itself are not affected by it. Now this maybe a desired outcome. There are situations where you would want this to be applicable globally to all the scripts and the shell itself without having to change the PATH value manually by ourselves. This can be done by changing the scripts that are invoked when the shell is loaded.

The file that is to be invoked is located in the /etc folder. It is called zshrc. If you want to make a change to bash shell then you need to modify bashrc. Copy this file to your desktop. Change it as shown below.

# System-wide profile for interactive zsh(1) shells.

# Setup user specific overrides for this in ~/.zshrc. See zshbuiltins(1)
# and zshoptions(1) for more details.

# Correctly display UTF-8 with combining characters.
if [[ "$(locale LC_CTYPE)" == "UTF-8" ]]; then
    setopt COMBINING_CHARS
fi

# Disable the log builtin, so we don't conflict with /usr/bin/log
disable log

# Save command history
HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history
HISTSIZE=2000
SAVEHIST=1000

# Beep on error
setopt BEEP

# Use keycodes (generated via zkbd) if present, otherwise fallback on
# values from terminfo
if [[ -r ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR} ]] ; then
    source ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR}
else
    typeset -g -A key

    [[ -n "$terminfo[kf1]" ]] && key[F1]=$terminfo[kf1]
    [[ -n "$terminfo[kf2]" ]] && key[F2]=$terminfo[kf2]
    [[ -n "$terminfo[kf3]" ]] && key[F3]=$terminfo[kf3]
    [[ -n "$terminfo[kf4]" ]] && key[F4]=$terminfo[kf4]
    [[ -n "$terminfo[kf5]" ]] && key[F5]=$terminfo[kf5]
    [[ -n "$terminfo[kf6]" ]] && key[F6]=$terminfo[kf6]
    [[ -n "$terminfo[kf7]" ]] && key[F7]=$terminfo[kf7]
    [[ -n "$terminfo[kf8]" ]] && key[F8]=$terminfo[kf8]
    [[ -n "$terminfo[kf9]" ]] && key[F9]=$terminfo[kf9]
    [[ -n "$terminfo[kf10]" ]] && key[F10]=$terminfo[kf10]
    [[ -n "$terminfo[kf11]" ]] && key[F11]=$terminfo[kf11]
    [[ -n "$terminfo[kf12]" ]] && key[F12]=$terminfo[kf12]
    [[ -n "$terminfo[kf13]" ]] && key[F13]=$terminfo[kf13]
    [[ -n "$terminfo[kf14]" ]] && key[F14]=$terminfo[kf14]
    [[ -n "$terminfo[kf15]" ]] && key[F15]=$terminfo[kf15]
    [[ -n "$terminfo[kf16]" ]] && key[F16]=$terminfo[kf16]
    [[ -n "$terminfo[kf17]" ]] && key[F17]=$terminfo[kf17]
    [[ -n "$terminfo[kf18]" ]] && key[F18]=$terminfo[kf18]
    [[ -n "$terminfo[kf19]" ]] && key[F19]=$terminfo[kf19]
    [[ -n "$terminfo[kf20]" ]] && key[F20]=$terminfo[kf20]
    [[ -n "$terminfo[kbs]" ]] && key[Backspace]=$terminfo[kbs]
    [[ -n "$terminfo[kich1]" ]] && key[Insert]=$terminfo[kich1]
    [[ -n "$terminfo[kdch1]" ]] && key[Delete]=$terminfo[kdch1]
    [[ -n "$terminfo[khome]" ]] && key[Home]=$terminfo[khome]
    [[ -n "$terminfo[kend]" ]] && key[End]=$terminfo[kend]
    [[ -n "$terminfo[kpp]" ]] && key[PageUp]=$terminfo[kpp]
    [[ -n "$terminfo[knp]" ]] && key[PageDown]=$terminfo[knp]
    [[ -n "$terminfo[kcuu1]" ]] && key[Up]=$terminfo[kcuu1]
    [[ -n "$terminfo[kcub1]" ]] && key[Left]=$terminfo[kcub1]
    [[ -n "$terminfo[kcud1]" ]] && key[Down]=$terminfo[kcud1]
    [[ -n "$terminfo[kcuf1]" ]] && key[Right]=$terminfo[kcuf1]
fi

# Default key bindings
[[ -n ${key[Delete]} ]] && bindkey "${key[Delete]}" delete-char
[[ -n ${key[Home]} ]] && bindkey "${key[Home]}" beginning-of-line
[[ -n ${key[End]} ]] && bindkey "${key[End]}" end-of-line
[[ -n ${key[Up]} ]] && bindkey "${key[Up]}" up-line-or-search
[[ -n ${key[Down]} ]] && bindkey "${key[Down]}" down-line-or-search

# Default prompt
PS1="%n@%m %1~ %# "

# Useful support for interacting with Terminal.app or other terminal programs
[ -r "/etc/zshrc_$TERM_PROGRAM" ] && . "/etc/zshrc_$TERM_PROGRAM"

if [[ -f /Users/Shared/newPath.bash ]]; then
    source /Users/Shared/newPath.bash
fi

Save this file back to the etc folder. It might be a good idea to take a backup of the original file in case we need to restore it back to undo any errors we might introduce.

Open the terminal app. Run the welcome.bash script without providing the path. See if it works.

Run the command to print the path variable:

printenv PATH

As we can see, there is no need to run the export command repeatedly. There is no need to source the file that contains those commands either.

Standard location or custom path

This approach does give us a lot of flexibility. However, we can see that there are several things we need to do before we can get everything working well. It might be better for us to place our commands in the standard /usr/local folder. That would make the deployment a lot easier.


Man pages

What are man pages?

If you have used the command line interface on macOS/Unix/Linux then you would be familiar with the man command. In case you aren’t then the man command is the command that loads the manual for the binary specified. It is a quick easy way to access the documentation and help for the command. However, man pages aren’t restricted to only binaries. They could be applied on normal files too.

There is one thing to keep in mind. It is not necessary that a man page exists for a given command. Try running the command man folderCreator. What do you get?

No manual entry for folderCreator

We get a message saying that there is no manual entry for our binary. So we need to go ahead and create one.

Before I talk about how to create them I will first address the question of whether we need to create one in the first place. Especially since we are already providing help view the -h -help options. Actually we don’t have to. However, keep in mind that most users are already familiar with the man command and their instinctive reaction is to look for the man page of your command. It would be very nice to offer them that ability.

How do we create them?

In order to create our man page we need to use certain macros that render the document for us. More information can be available via the mandoc, groff, mdoc, and man commands. I would highly recommend going through the man pages of these commands.

man pages are simple files that contain information which is formatted with the help of different macros. A typical man page contains the following sections in the specified sequence:

  1. NAME
  2. SYNOPSIS
  3. DESCRIPTION
  4. VERBS
  5. REQUIREMENTS
  6. INSTALLATION
  7. USAGE
  8. WARNINGS
  9. EXIT STATUS
  10. EXAMPLES
  11. DIAGNOSTICS
  12. COPYRIGHT
  13. CONTACT DETAILS

There are other sections available too: the man page for groff command contains excellent information about that. Armed with the information about which sections are there within the man page we need to start gathering all our details together.

One piece that we need is the manual section.If we run the man command on man:

man man

It gives us some information about the manual sections. The sections describe the kinds of commands and potentially the actions they perform. Our command would fall under the user commands section or section 1.

Where are they located?

These files are located in the /usr/local/share/man/man1 folder. Where man1 represents the section number.

Let us try to create the file. Before that we will look at some macros that we would need.

MacroDescription
.DdThis is used to specify the date when the man page was created/published.
.DtThis is used to specify the title for the man page. Its value should always be in all caps.
.OsThe name of the operating system.
.ShSection header name,
.NmThe name of the command. This is the name that will be used throughout the document.
.NdThe description of the command.
.ArArguments being passed to a command
.OpOptions being passed to a command
.PpNew paragraph
.BlStart a list
.ElEnd the list
.Ev For environment variables
.ItItalics
.SsSubsection
.AnName of the author
.SySymbolic font mode
.\"Comment
Macros used to create the man page file

You can get more details and information about these macros by running the following command:

man mdoc

We will now use these macros to render our man page. The easiest way to do this would be to use an existing man page file as a template. The idea is to use the macros to do the rendering for us. Copy paste the contents of any existing man page into your file and start replacing the content with your own content. You can always test your page by using the man command directly on your file.

man /path/to/your/manpage/file.1

A good thing to do would be to add 1 item at a time and run the above command repeatedly till you get comfortable with how everything fits together.

If you need help correcting the formatting of the file run the following command:

mandoc -T lint folderCreator.1

This is how the man page file looks. Name it folderCreator.1 where the ‘1’ indicates the section number.

.\"Copyright (c) 2015-2022 Amaranthine. All Rights Reserved.
.\"
.\"
.Dd August 10, 2021
.Dt FOLDERCREATOR 1
.Os macOS 11
.Sh NAME
.Nm folderCreator
.Nd Folder creation utility
.\"
.\" ============================================================================
.\" ========================== BEGIN SYNOPSIS SECTION ==========================
.Sh SYNOPSIS
.Nm
.Ar "folder names"
.Op verbs
.\" =========================== END SYNOPSIS SECTION ===========================
.\" ============================================================================
.\"
.\" ============================================================================
.\" ======================== BEGIN DESCRIPTION SECTION =========================
.Sh DESCRIPTION
.Nm
creates 3 folders in the home folder.
In case the folder names are not provided then the command
will create folders with default names "Tools", "Reports", "Help".
.Pp
The user is also prompted via the graphical user interface for names that should be used for the folders.
This is optional and the user can cancel it.
.Pp
There is also the option of getting help via the help verb.
.Pp
- This script is intended for creating the custom folders that are required on all corporate computers.
.Pp
- Run this script on a new computer or a computer being reassigned to another employee.
.Pp
- This script can run on all computers.
.\" ----------------------------------------------------------------------------
.\" ------------------------- BEGIN TERMINOLOGY LIST ---------------------------
.Sh VERBS
.Bl -hang
.It Op Fl h help
Both the options are used to invoke the help documentation.
.It Op Fl v version
Both the options are used to get the version number of the
.Nm
command.
.El
.\" --------------------------- END TERMINOLOGY LIST ---------------------------
.\" ----------------------------------------------------------------------------
.\" ============================================================================
.\" ======================== BEGIN REQUIREMENTS SECTION ========================
.Sh REQUIREMENTS
The following are the minimum requirements to get the script running.
.Bl -hang -offset 4n -width "xxxxxxxxxxxx" -compact
.It Shell:
zsh
.It OS:
macOS Big Sur 11.4 or later
.It Dependencies:
None
.El
.Ev HOME
.\" ============================================================================
.\" ======================== BEGIN INSTALLATION SECTION ========================
.Sh INSTALLATION
.Nm
can be installed anywhere you wish.
However, there are certain locations that are recommended.
.Bl -hang -offset 4n -width "xxxxxxxxxxxx" -compact
.It Location:
/Library/Scripts/
.It Permissions:
rwx r-x r-x
.El
.\" ============================================================================
.\" ======================== BEGIN USAGE SECTION ========================
.Sh USAGE
.Nm
.Ar folder1
.Ar folder2
.Ar folder3
.Pp
Will create folders with your own names.
.Pp
.Nm
.Ar -h
OR
.Nm
.Ar -help
.Pp
Will invoke the help utility.
.Pp
.Nm
.Ar -v
OR
.Nm
.Ar -version
.Pp
Will print the version number in stdout.
.Ss GUI Interaction
In all cases the user is always prompted for entering folder names via the graphical user interface.
Therefore this script triggers a gui popup.
In case this is not the desired behavior then the appropriate lines of code will need to be commented out.
.\" ============================================================================
.\" ======================== BEGIN WARNING/CAUTION SECTION ========================
.Sh WARNING/CAUTION
.Nm
does not perform any validation of names.
The only options that
.Nm
accepts are
.Ar -h
and
.Ar -help
verbs or the
.Ar -v
and
.Ar -version
verbs.
If the script does not see the
.Ar -h
,
.Ar -help
or the
.Ar -v
,
.Ar -version
options then it will assume that the data being passed in is the name of the folder.
The user of the
.Nm
command must ensure that the desired folder names are passed in.
The user will also be prompted, via the graphical user interface, if he/she wishes to provide the names for the folders.
If yes, then there will be subsequent
prompts asking for the folder names.
.\" ============================================================================
.\" ======================== BEGIN EXIT STATUS SECTION =========================
.Sh EXIT STATUS
In most situations,
.Nm
exits 0 on success
.\" ============================================================================
.\" ======================== BEGIN EXAMPLES SECTION ========================
.Sh EXAMPLES
.Nm
.Ar Resources
.Ar Results
.Ar Assistant
.Pp
This will create 3 folders
.Sy Resources
,
.Sy Results
,
.Sy Assistant
,
in the user's home folder.
.Pp
.Nm
.Pp
This will create 3 folders with the default names
.Pp
.Nm
.Ar Apps
.Pp
This will use the
.Sy Apps
name for the first folder but the default names for the last 2 folders.
.\" ============================================================================
.\" ======================== BEGIN DIAGNOSTICS SECTION ========================
.Sh DIAGNOSTICS
The script produces a log file called
.Sy ~/Library/Logs/folderCreator_log_v1-x.log
.Pp
This file is typically located in the user's home folder log folder.
The x represents the version number of
.Nm
.Pp
You can view the logs for each respective version.
.\" ============================================================================
.\" ======================== BEGIN COPYRIGHT SECTION ========================
.Sh COPYRIGHT
Copyright (c) Amaranthine 2015-2021.
All rights reserved.
https://amaranthine.in
.\" ============================================================================
.\" ======================== BEGIN CONTACT SECTION ========================
.Sh CONTACT DETAILS
.An Author: Arun Patwardhan
.Pp
Website: https://amaranthine.in
.Pp
Email: arun@amaranthine.co.in
view raw folderCreator.1 hosted with ❤ by GitHub

Place the man page in the following folder.

/usr/local/share/man/man1/folderCreator.1

This is how it would look if we run the man command on our folderCreator script.

This is how it would look like if we opened it in the Preview application.


Video

Download

You can download the final script from here.

You can download the man page file from here.

Summary

Our script has evolved quite a bit from the first blog. We started off with a very simple script and have ended with a larger script. It still does the same thing it did originally but is now a lot better. Here are some of the key points:

  • Writing event updates to log files
  • User interaction
  • Flexibility in terms of folder names via user interaction from the GUI as well as the command line arguments
  • Easy to maintain thanks to functions
  • Arrays make it scalable
  • Loops help make the script compact
  • Variables enhance scalability
  • Periodic checks ensure the script is safe and stable
  • User can get help using
    • the -h or the -help options
    • From the comments in the script
    • by viewing the man page for the script

Of course, there is room for improvement. Some of you might come up with ways of achieving this solution differently. Which is perfectly fine. There is no such thing as a universally perfect script. The point behind the script above was to illustrate how the different features could work together.

Final thoughts

Scripting is a continuous learning process. There are so many things in it. Over time you will find that you are faced with similar challenges. One thing that a lot of script writers and programmers do is to refer to perviously written scripts to get a head start. Every time you write a script it would be a good idea to archive it and keep a copy elsewhere. This will come in handy.

Happy scripting!

Advertisement

Shell scripting in macOS – Part 9: Automating scripts

This article is NOT a continuation of the previous article. The topics covered in this article won’t be used in our folder creation script as the features covered are not required or can’t be appropriately used. We will use a different script instead. This article is a continuation of the Shell scripting series though.

Why automate?

Most of the times we or someone else will be running the desired script. However there is one major drawback with this. It assumes that there is ‘someone’ who can run the script and they have access to the computer that the script is supposed to run on. This is not always a given.

The above approach also has the problem of efficiency. If the script is to be run periodically or at regular intervals it can get very inefficient and is likely to affect the workflows of the organisation.

Automation solves these problems for us by allowing scripts to run by themselves.

How to automate?

There are 2 options that we will look at in this article:

  • Expect utility
  • Launch Daemons/Launch Agents

Expect utility

The expect utility isn’t strictly an automation solution. It’s a tool that can answer stdout prompts from another script/program/application. Let us see how it works with an example.

The script below is asking a series of questions from the user and is expecting a response. The response is stored in a variable which is echo’d out. To make it interesting the last question is a choice based questions. Based on some random value the script will ask question ‘a’ or question ‘b’.

#!/bin/zsh

# Questions
# --------------------------------------------------
echo "1) What is your name?"
read -s NAME
echo $NAME 

echo "2) What is your email address?"
read -s EMAIL
echo $EMAIL 

echo "3) Could you tell me your company's website address?"
read -s WEBSITE
echo $WEBSITE 

echo "4) Please provide your mobile number?"
read -s MOBILE
echo $MOBILE 

# Choice
# --------------------------------------------------
if [[ $RANDOM -gt 90000 ]]; then
	echo "Do you like option A?"
else 
	echo "Would you prefer option B?"
fi

read -s OPTION
echo $OPTION 

Now if we just run the above script we would see prompts on stdout for the different questions and simply answer them. But what if this script is going to be called by another script? That is where the expect utility comes in. Let us have a look at out script and then analyse it

#!/bin/zsh
#!/usr/bin/expect 

#Variables
ANS1="Arun"
ANS2="arun@amaranthine.co.in"
ARG1=$1

export ANS1
export ANS2
export ARG1 

/usr/bin/expect << "EOF"
set myValue1 $env(ANS1);
set myValue2 $env(ANS2);
set arg1 $env(ARG1);

spawn /Users/arunpatwardhan/Developer/questions.zsh
expect "1) What is your name?\r" 
send -- "$myValue1\r"

expect "2) What is your email address?"
send -- "$myValue2\r"

expect "3) Could you tell me your company website address?"
send -- "www.amaranthine.in\r"

expect "4) Please provide your mobile number?"
send -- "$arg1\r"

expect {
	"Do you like option A?" { send -- "Of course!\r" }
	"Would you prefer option B?" { send -- "Definitely!\r" }
}

expect eof
EOF

Let us analyse this file line by line.

The first line is our interpreter declaration. The second line is where we specify which expect we will be using.

#!/bin/zsh
#!/usr/bin/expect 

Next we declare some variables. We will use them in our expect utility soon.

#Variables
ANS1="Arun"
ANS2="arun@amaranthine.co.in"
ARG1=$1

In order to use them in our utility we need to export these variables. We will then access them via the environment.

export ANS1
export ANS2
export ARG1 

This is where we start writing our expect command. Notice that we are using the ‘here’ document that we learnt in the previous script. This allows use to send multiple expect commands to the expect utility.

/usr/bin/expect << "EOF"

The first thing that we do is create variables for the expect utility from the variables that we already have.

set myValue1 $env(ANS1);
set myValue2 $env(ANS2);
set arg1 $env(ARG1);

Then we use the spawn command to start the script whose questions we wish to answer.

spawn /Users/arunpatwardhan/Developer/questions.zsh

Next we tell the expect utility to ‘expect’ a question. We specify the question to expect in the string after the expect command.

expect "1) What is your name?\r"

Once the expectation has been met, we send out our response to the question using the send command.

send -- "$myValue1\r"

We can answer the rest of the questions the same way. We can use variables to pass in data or directly write our answers in there.

In case of a choice we write an expect block command with the help of braces.

expect {
	"Do you like option A?" { send -- "Of course!\r" }
	"Would you prefer option B?" { send -- "Definitely!\r" }
}

Finally we end with an eof being passed to expect.

expect eof

The last EOF is for the ‘here’ document.

EOF

That’s it! It’s that simple to write expect scripts. Note that there are more commands available for the expect utility. I would strongly recommend going through the man page for details about expect.

man expect

There is also a utility called autoexpect that builds the expect script for you. As of this article (tested on macOS 12.6) it is not installed by default.

One last thing I would like to mention. The expect utility should ideally be used in a larger script which is invoking other scripts. It is not meant to replace human interaction. Use caution when invoking scripts/binaries/tools that you cannot read as you are not fully aware of everything that is done by these scripts.


Launch Daemons/Launch Agents

The other and option is to use launch daemons/launch agents. I will be referring to them as daemons through the rest of this article unless something specific has to be mentioned.

Daemons solve a different problem as compared to the expect utility.

The expect utility allows for a script/binary/tool/program to complete execution by answering questions expected from a human.

Daemons on the other hand allow us to schedule and automatically run scripts and custom intervals. These are background processes that are started on specific events. Daemons and agents are exactly the same, but they have some key differences:

Launch DaemonsLaunch Agents
These are owned by the systemThese are owned by the currently logged in user
Start as soon as the system boots upStarts only after the user logs in. Stops as soon as the user logs out
Is system wideCan apply to all user accounts or to a specific user account

These are some of the points that you have to keep in mind while deciding whether to create a daemon or an agent. The process for creating them is exactly the same. It is plist file that provides all the information that is necessary for the system to schedule and run the processes.

I will be talking about launch daemons and agents as well as plist files in separate articles at a later point in time.

Example

Let us look at how to create a daemon with the help of an example.

We would like to show an informational popup that appears as soon as the user logs in and at 5 minutes interval. We would also like to show a notification of the same.

Now this could also be achieved using other approaches but it gives us an opportunity to explore how to create agents. Also the scheduling is chosen so that we can see how it is done. It is not strictly required for this situation.

So why have we decided to create it as an agent?

The main reason is because it is to run only after the user logs in. Something that a Launch Agent is perfectly suited for.

We also want this to run for every user account on the computer. So this will need to be installed in the /Library folder. Specifically:

/Library/LaunchAgents/

Now that we have decided how and where let us focus first on the script that we plan to run.

#!/bin/zsh

/usr/bin/osascript << EOF 
display notification "By using this computer you agree to terms and conditions of use set down by the company." \
					  with title "Terms & Conditions" \
					  subtitle "Acknowledge" \
					  sound name "Alert"

set theAlertText to "Terms and conditions of use"
set theAlertMessage to "By using this computer you agree to terms and conditions of use set down by the company."
display alert theAlertText \
	    message theAlertMessage \
		as informational \
		buttons {"Accept"} default button "Accept"
EOF

The script simply uses the osascript command to run a few AppleScript commands. These commands show a notification as well as an alert message to the user. Let us place this script in the /Library/Scripts/ folder. This will make it easily accessible.

Next we will focus on creating the launch agent plist. Let us first list out the items we will need to put in the plist.

KeyDescription
LabelThis is the name of our Launch Agent
ProgramArgumentsThis is the action to be performed. In our case the action is to invoke the script.
StartIntervalThis is the time interval after which the program arguments must be executed.
RunAtLoadThis is a flag that indicates that the agent must be run as soon as it is loaded.

Armed with this information we will go ahead and create our plist. There are several tools available for this:

  • Xcode
  • TextEdit
  • defaults command
  • Third party tools like CodeRunner

We will use the defaults command for this. It is a built in command that allows us to work with plist files. Here is a little script that creates our plist.

#!/bin/zsh

defaults write ~/Desktop/in.amaranthine.welcomeMessage.plist Label -string "in.amaranthine.welcomeMessage"
defaults write ~/Desktop/in.amaranthine.welcomeMessage.plist ProgramArguments -array-add "/bin/zsh" "/Library/Scripts/welcome.zsh"
defaults write ~/Desktop/in.amaranthine.welcomeMessage.plist RunAtLoad -boolean true
defaults write ~/Desktop/in.amaranthine.welcomeMessage.plist StartInterval -integer 300

In the script, the defaults command is writing several key value pairs into a plist file at the specified path. Notice the key names: Label, ProgramArguments, RunAtLoad, StartInterval. These are the values we need to specify. The above commands need not be written in a specific sequence as they are being written to an XML file.

The resulting plist file should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>in.amaranthine.welcomeMessage</string>
	<key>ProgramArguments</key>
	<array>
		<string>/bin/zsh</string>
		<string>/Library/Scripts/welcome.zsh</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>StartInterval</key>
	<integer>300</integer>
</dict>
</plist>

Lets explore the different parts of this plist file. It contains a dictionary with several key value pairs.

<key>Label</key>
<string>in.amaranthine.welcomeMessage</string>

The first key value pair we encounter is ‘Label’. This is the name of our agent.

<key>ProgramArguments</key>
<array>
	<string>/bin/zsh</string>
	<string>/Library/Scripts/welcome.zsh</string>
</array>

The next key value pair is extremely important. It is telling our agent the task that needs to be performed. In this case use the zsh shell to run the welcome.zsh script. This is the same as running the following command in terminal:

/bin/zsh /Library/Scripts/welcome.zsh
<key>RunAtLoad</key>
<true/>

The second key value pair indicates that the agent must run as soon as it is loaded and not only when the user logs in. This is not strictly required but is very handy when we try to test our agent.

<key>StartInterval</key>
<integer>300</integer>

The last key value pair will schedule the agent to run at an interval of 5 minutes or 300 seconds.

You can check if the plist if properly formatted using the command:

plutil -lint /path/to/plist

Also, if you try to open the plist file in an editor it is likely that the formatting may not appear correctly. Run the following command to make it readable:

plutil -convert xml1 /path/to/plist

This does not affect the plist file in any way. So it is safe to perform.

The next bit is to place the file in correct location. Copy the plist file to the path:

/Library/LaunchAgents/

Next we will look at loading the agent

While the agent will be loaded automatically every time the user logs in. However, we would like to test the results immediately. We will use the launchctl command to load the agent.

launchctl bootstrap gui/501 /Library/LaunchAgents/in.amaranthine.welcomeMessage.plist

Let us explore the launchctl command.

The bootstrap option informs the system that an agent is being loaded.

The next bit tells launchctl that its for gui user 501. We can get the user number using the command id -u.

This is followed by the path to the plist file.

Run this command.

To find out if the launch agent has loaded correctly we will use the list option on launchctl to list out all the current agents.

launchctl list

To get more specific results:

launchctl list | grep "in.ama"

This will show a list of agents whose name starts with “in.ama”.

And that’s it. We have now scheduled the agent. If you have run the command to bootstrap the agent then you should see the popup appearing on the screen.

You should also see the notification:

If you wish to stop the agent run:

launchctl bootout gui/501 /Library/LaunchAgents/in.amaranthine.welcomeMessage.plist

It’s the same command as before. Except with the bootout option instead of the bootstrap.

NOTE: The agent will start running again the next time the user logs in.

Summary

As you can see there are several options available when it comes to automating scripts. This opens up more possibilities when it comes to performing management tasks on the device.

Video

Download

You can download the scripts from here:

Shell scripting in macOS – Part 6: User interaction

This article is a continuation of the previous article. We will be taking the previous script and using it to build on the concepts we will learning in this article.

Why would we need to do this?

So far our scripts have run without any communication between the script and any user. In many situations this might be enough. Often times we would need to ask the user for some information before proceeding. This could be for confirmation before performing a certain task, or to customise the script actions as they are being performed.

Ways of interacting with the user

We have a few options to do this.

  • read utility
  • GUI pop via AppleScript

Let us look at the read utility first.

Reading from stdin

The read commands captures data entered via stdin. This is typically the terminal prompt from where the user enters the data. This captured value is read into a variable.

#!/bin/bash

read -p " What is your name?": NAME 

echo $NAME 

This is a simple example. The user is prompted (on stdout) to enter their name. The user types the name and it is stored in a variable. The contents of the variable are then echoed out to stdout again.

Of course, this assumes the user is manually running the script. Later in this article we will go over how we can automate such scripts with the help of ‘expect’ utility.

Reading from a file

Reading from a file is possible thanks to the read command as well as the loop operations we learnt earlier.

Asking question via GUI

The easiest thing to do would be to prompt the user for information via a GUI popup. This makes the experience a lot better. However, we will need something else for that. We will leverage AppleScript (which is a macOS specific scripting language) to show the popup. This will be invoked from the shell script using osascript.

I will be covering the basics of AppleScript in a later article.

First we will look at how to get the dialog to appear using AppleScript. This requires the use of AppleScript display dialog command.

display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2

This will simply show a popup to the user with a message and 2 buttons. We can customise it further:

display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns"

This adds a custom icon. If we run this script the user would see something like:

Make sure that the path to the custom icon is what you would want it to be.

If we wanted to find out which button the user has selected then:

button returned of (display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns")

The button returned of tells us which button was clicked. It also adds a nice title for the popup.

The really nice thing to do would be to somehow run this from terminal. To do that we will use the osascript command. This command needs the AppleScript command to be passed is as a statement.

osascript -e '<AppleScript statement>'

Let us add our command here.

osascript -e 'button returned of (display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns")'

Finally to get this value into a script variable we will use command substitution.

userClicked=$(/usr/bin/osascript -e 'button returned of (display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns")')

We can now echo the variable or use it in a condition check.

Example

Let us update our code to ask the user for names they would like to give for their folder.

  1. Just after the line where we finish checking the 3 positional parameters, add this line.
#4. We can prompt the user to see if they wish to provide folder names themselves. This will override any values provided as arguments.
userClicked=$(/usr/bin/osascript -e 'button returned of (display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns")')

This will first ask the user if they wish to provide the folder names or use the defaults. The response is captured in the shell variable userClicked.

  1. Below that we will put an if check to see what the user has selected.
# if the user decides to provide custom names then go ahead and ask the user via GUI prompts. Otherwise use the values sent as arguments or defaults.	
if [[ $userClicked == "Custom" ]]; then
	
else

fi
  1. If the condition is true we have to show dialog prompts to ask the user for the name. This is going to be similar to our first check. However, this one will also have the ability for the user to enter a value. Add 3 prompts, one for each folder. Also add an echo statement to write to the log file. The if should now look like:
if [[ $userClicked == "Custom" ]]; then
	echo "$(date) The user decided to provide custom names." >> $PATH_TO_LOG
	
	TOOLS_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 1" default answer "Utilities" buttons {"OK"} default button 1 with title "Folder that will hold the utilities" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	REPORTS_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 2" default answer "Tools" buttons {"OK"} default button 1 with title "Folder that will hold the tools" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	HELP_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 3" default answer "Help" buttons {"OK"} default button 1 with title "Folder that will hold the support documents" with icon POSIX file "/Users/Shared/Finder.icns")')
		
	echo "$(date) User provided: $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER" >> $PATH_TO_LOG
else
	echo "$(date) User decided to use default values: $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER" >> $PATH_TO_LOG
fi

The script is now ready to accept user input. The completed script should look like:

#!/bin/zsh

#-------------------------------------------------------------------------------------------------
#NAME:		Folder creator
#AUTHOR:	Arun Patwardhan
#CONTACT:	arun@amaranthine.co.in
#DATE:		10th August 2021
#WEBSITE:	https://github.com/AmaranthineTech/ShellScripts
#-------------------------------------------------------------------------------------------------

#LEGAL DISCLAIMER --------------------------------------------------------------------------------
#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.
#-------------------------------------------------------------------------------------------------

#LICENSE/TERMS AND CONDITIONS --------------------------------------------------------------------
#MIT License

#Copyright (c) Amaranthine 2021.

#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.
#-------------------------------------------------------------------------------------------------

#ABOUT -------------------------------------------------------------------------------------------
# fileCreator.zsh
# 1.5
#-------------------------------------------------------------------------------------------------

#DESCRIPTION ------------------------------------------------------------------------------------- 
# - THis script is intended for creating the custom folders that are required on all corporate computers. 
# - Run this script on a new computer or a computer being reassigned to another employee.
# - This script can run on all computers.
#-------------------------------------------------------------------------------------------------

#USAGE -------------------------------------------------------------------------------------------
# - To create folders with default names run the command: ./folderCreator.zsh
# - To define your own folder names: ./folderCreator.zsh <folder1> <folder2> <folder3>
# - Available options  : Only the help option is available
# - Getting help       : Use the -h or the -help options to get more information. Or you can use the man command to view the man page.
#-------------------------------------------------------------------------------------------------

#WARNING/CAUTION ---------------------------------------------------------------------------------
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
# This script doesn't perform any validation of the folder names being passed in by the user. 
# If the script does not see the -h or the -help options then it will assume that the data being passed in is the name of the folder.
# The user of the script must ensure that the desired folder names are passed in.
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#-------------------------------------------------------------------------------------------------

#INSTALLATION ------------------------------------------------------------------------------------
# Instructions for placing the script in the correct place are listed here. 
# Location:		/Library/Scripts/
# Permissions:	rwx r-x r-x
#-------------------------------------------------------------------------------------------------

#REQUIREMENTS ------------------------------------------------------------------------------------
# Shell:		/bin/zsh
# OS:			macOS Big Sur 11.4 or later
# Dependencies:	None
#-------------------------------------------------------------------------------------------------

#HELP/SUPPORT ------------------------------------------------------------------------------------
# You can get help by running the following commands.
# ./folderCreator.zsh -h
# ./folderCreator.zsh -help
# OR
# man folderCreator.zsh
# You can also view the log file for the same at: ~/Library/Logs/folderCreator_log_v1-5.log
#-------------------------------------------------------------------------------------------------

#HISTORY -----------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
# Version 1.0: Basic script which creates the folders
# Version 1.1: Gives user the ability to specify the folder names at run time.
# Version 1.2: Adds safety checks to the scripts
# Version 1.3: Includes documentation as well as ability to get help.
# Version 1.4: Includes optimisation using for loop
# Version 1.5: Prompts the user in the GUI for names for the different folders.
#-------------------------------------------------------------------------------------------------

#-------------------------------------------------------------------------------------------------
# ------------------------------ SCRIPT STARTS HERE ----------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------

#These are the default values used for the folder names incase the user doesn't provide any.
TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"

#Script version number
VERSION_NUMBER="1.5"

#Command name
COMMAND_NAME="folderCreator.zsh"

#1. Check to see if the user is asking for help. In which case we will have to provide information about the command.
if [[ $1 == "-h" ]] || [[ $1 == "-help" ]]; then
	echo "ABOUT 
-----
fileCreator_v1-5.zsh
Version $VERSION_NUMBER

NAME 
----
$COMMAND_NAME — Folder creation utility SYNOPSIS
$COMMAND_NAME folder names [ verbs ]

DESCRIPTION 
-----------
$COMMAND_NAME creates 3 folders in the home folder. In case the folder names are not provided then the command will create folders with default names 'Tools', 'Reports', \"Help\".

There is also the option of getting help via the help verb.
- This script is intended for creating the custom folders that are required on all corporate computers. 
- Run this script on a new computer or a computer being reassigned to another employee.
- This script can run on all computers.

VERBS 
-----
[ −h −help] Both the options are used to invoke the help documentation.
[ −v −version] Both the options are used to get the version number of the folderCreator command.

REQUIREMENTS 
------------
The following are the minimum requirements to get the script running.
Shell:\t\t zsh
OS:\t\t macOS Big Sur 11.4 or later
Dependencies:\t None

INSTALLATION 
------------
$COMMAND_NAME can be installed anywhere you wish. However, there are certain locations that are recommended.
Location:\t /Library/Scripts/ 
Permissions: \t rwxr-xr-x

USAGE  
-----
$COMMAND_NAME folder1 folder2 folder3 
Will create folders with your own names. 

$COMMAND_NAME -h OR $COMMAND_NAME -help 
Will invoke the help utility.

$COMMAND_NAME -v OR $COMMAND_NAME -version 
will print the version number in stdout.

WARNING/CAUTION  
---------------
$COMMAND_NAME does not perform any validation of names. The only options that folderCreator accepts are -h and -help verbs or the -v and 
-version verbs. If the script does not see the -h , -help or the -v , -version options then it will assume that the data being passed in is 
the name of the folder. The user of the folderCreator command must ensure that the desired folder names are passed in. The user will also be 
prompted, via the graphical user interface, if he/she wishes to provide the names for the folders. If yes, then there will be subsequent 
prompts asking for the folder names.

EXAMPLES 
--------
$COMMAND_NAME Resources Results Assistant
This will create 3 folders Resources , Results , Assistant , in the user’s home folder. 

$COMMAND_NAME
This will create 3 folders with the default names

$COMMAND_NAME Apps
This will use the Apps name for the first folder but the default names for the last 2 folders. 

NOTE
----
The user will be asked if he/she wishes to provide custom names in all the examples mentioned above. The user's value will always override 
whatever is being provided to the script or defaults.

DIAGNOSTICS 
-----------
The script produces a log file called ~/Library/Logs/folderCreator_log_v1-x.log
This file is typically located in the user’s home folder log folder. The x represents the version number of $COMMAND_NAME
You can view the logs for each respective version.

COPYRIGHT  
---------
Copyright (c) Amaranthine 2015-2021. All rights reserved. https://amaranthine.in

EXIT STATUS  
-----------
In most situations, $COMMAND_NAME exits 0 on success"
	exit 0
fi

echo "$(date) Running script $0 to create folders."
echo ""

TODAY=$(date)
PATH_TO_LOG="$HOME/Library/Logs/folderCreator_log_v1-5.log"

echo "$(date) Starting" >> $PATH_TO_LOG

#2. Check to see if the version number is 
if [[ $1 == "-version" ]] || [[ $1 == "-v" ]]; then
	echo "Version: $VERSION_NUMBER"
	exit 0
fi

#3. The following if statements check to see if the script is receiving any arguments. It then picks those arguments and assigns them to the respective variables for use in the script.
if [[ $1 != "" ]]; then
	TOOLS_FOLDER=$1
fi

if [[ $2 != "" ]]; then
	REPORTS_FOLDER=$2
fi

if [[ $3 != "" ]]; then
	HELP_FOLDER=$3
fi

#4. We can prompt the user to see if they wish to provide folder names themselves. This will override any values provided as arguments.
userClicked=$(/usr/bin/osascript -e 'button returned of (display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns")')
	
# if the user decides to provide custom names then go ahead and ask the user via GUI prompts. Otherwise use the values sent as arguments or defaults.	
if [[ $userClicked == "Custom" ]]; then
	echo "$(date) The user decided to provide custom names." >> $PATH_TO_LOG
	
	TOOLS_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 1" default answer "Utilities" buttons {"OK"} default button 1 with title "Folder that will hold the utilities" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	REPORTS_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 2" default answer "Tools" buttons {"OK"} default button 1 with title "Folder that will hold the tools" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	HELP_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 3" default answer "Help" buttons {"OK"} default button 1 with title "Folder that will hold the support documents" with icon POSIX file "/Users/Shared/Finder.icns")')
		
	echo "$(date) User provided: $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER" >> $PATH_TO_LOG
else
	echo "$(date) User decided to use default values: $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER" >> $PATH_TO_LOG
fi

#5. Go to the home folder.
cd $HOME

#6. Check to see if each of the folders exists. If it exists then do not create it. Else create the folder. 
echo "$(date) Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER" >> $PATH_TO_LOG

for item in $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER; do
	if [[ -d $item ]]; then
		echo "$(date) Not creating $item as it already exists." >> $PATH_TO_LOG
	else
		echo "$(date) Creating $item" >> $PATH_TO_LOG
		mkdir $item
	fi
	
	#7. Create the task completion file inside each folder.
	echo "$(date) Creating hidden file for $item folder." >> $PATH_TO_LOG
	cd $item
	#8. Generate the file names based on the folder names.
	touch ".$item-FolderCreated"
	cd ..
done

echo "$(date) Task completed. Have a nice day!"
	
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ END OF SCRIPT ---------------------------------------------------

I have also performed some additional cleanup. That has been highlighted. The comments and the help have also been updated to reflect the new changes. I have also moved a couple of the echo statements further up the script. These have also been highlighted.

Video

Download

The completed script can be downloaded from here.

Shell scripting in macOS – Part 5: Loops

This article is a continuation of the previous article. We will be taking the previous script and using it to build on the concepts we will learning in this article.

Loops

Often times we will come across a situation where we want to perform a task repeatedly with different pieces of data.

For example, here is a snippet of our shell script:

if [[ -d $TOOLS_FOLDER ]]; then
	echo "$(date) Not creating $TOOLS_FOLDER as it already exists." >> $PATH_TO_LOG
else
	echo "$(date) Creating $TOOLS_FOLDER" >> $PATH_TO_LOG
	mkdir $TOOLS_FOLDER
fi

if [[ -d $REPORTS_FOLDER ]]; then
	echo "$(date) Not creating $REPORTS_FOLDER as it already exists." >> $PATH_TO_LOG
else
	echo "$(date) Creating $REPORTS_FOLDER" >> $PATH_TO_LOG
	mkdir $REPORTS_FOLDER
fi

if [[ -d $HELP_FOLDER ]]; then
	echo "$(date) Not creating $HELP_FOLDER as it already exists." >> $PATH_TO_LOG
else
	echo "$(date) Creating $HELP_FOLDER" >> $PATH_TO_LOG
	mkdir $HELP_FOLDER
fi

You can see that we are performing the same task repeatedly. Only the name of the folder is changing. This is the kind of situation that we will commonly encounter. Loops can help us make the script a little more efficient.

Handling loops

We are going to look at 2 solutions for handling loops:

  1. For loop
  2. While loop

For loops

A for loop works on a collection of data. It steps through the collection in the sequence in which it is present and picks one item from it at a time. The loop then performs the tasks specified in it using the item collected. Once finished it proceeds to pick up the next item. This is done till all the items in the collection are used.

Here is an example of a simple for loop. It contains 4 pieces of data in its collection: “Applications”, “Documents”, “Downloads”, & “Library”.

#!/bin/bash

for folder in Applications Documents Downloads Library
do	
	echo "The folder is $folder"
done

The output would be:

The folder is Applications
The folder is Documents
The folder is Downloads
The folder is Library

The collection could be data coming from somewhere else; such as the output of a command.

#!/bin/bash

for details in $(ls)
do
	echo "-> $details"
done

The output would be:

-> Desktop
-> Documents
-> Downloads
-> Library
-> Movies
-> Music
-> Pictures
-> Public

While loops

A while loop also performs tasks repeatedly. However it does this till a certain condition is satisfied. So we could use the test operations we learnt in an earlier article to achieve the check.

#!/bin/bash

while [[ ! -f /Users/Shared/exit.txt ]]; do
	echo "File not found"
done

echo "Found"

The script is checking to see if the file at the given path exists. If the file doesn’t exist then it prints out a message and performs the check again.

This will be done till the file is found.

NOTE: If you are testing the above code then you will need to create the file manually. You can do that by executing the following command in terminal:
touch /Users/Shared/exit.txt

Example

Let us go ahead and update our script.

  1. First we will remove the variables that store the names for the hidden files.
  2. Next we will remove the code for creating the folders. This will be replaced by a single block of code inside a for loop. Just remove the code for the reports folder and the help folder. We will use the code for the tools folder inside the for loop.
  3. Right below the line where we echo out the statement that we are creating the folder, add the code for the for loop. Move the remaining folder creation logic inside it and rename the variable.
for item in $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER; do
	if [[ -d $item ]]; then
		echo "$(date) Not creating $item as it already exists." >> $PATH_TO_LOG
	else
		echo "$(date) Creating $item" >> $PATH_TO_LOG
		mkdir $item
	fi

done
  1. Next, remove the code to create the hidden file. As before, only remove the code for creating the hidden file for the reports and help folders.
  2. Copy the remaining lines of code into the for loop. Place it right after the fi statement.
for item in $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER; do
	if [[ -d $item ]]; then
		echo "$(date) Not creating $item as it already exists." >> $PATH_TO_LOG
	else
		echo "$(date) Creating $item" >> $PATH_TO_LOG
		mkdir $item
	fi
	
	#6. Create the task completion file inside each folder.
	echo "$(date) Creating hidden file for $item folder." >> $PATH_TO_LOG
	cd $item
	#7. Generate the file names based on the folder names.
	touch ".$item-FolderCreated"
	cd ..
done

Your final script should look like this:

#!/bin/zsh

#-------------------------------------------------------------------------------------------------
#NAME:		Folder creator
#AUTHOR:	Arun Patwardhan
#CONTACT:	arun@amaranthine.co.in
#DATE:		10th August 2021
#WEBSITE:	https://github.com/AmaranthineTech/ShellScripts
#-------------------------------------------------------------------------------------------------

#LEGAL DISCLAIMER --------------------------------------------------------------------------------
#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.
#-------------------------------------------------------------------------------------------------

#LICENSE/TERMS AND CONDITIONS --------------------------------------------------------------------
#MIT License

#Copyright (c) Amaranthine 2021.

#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.
#-------------------------------------------------------------------------------------------------

#ABOUT -------------------------------------------------------------------------------------------
# fileCreator.zsh
# 1.4
#-------------------------------------------------------------------------------------------------

#DESCRIPTION ------------------------------------------------------------------------------------- 
# - THis script is intended for creating the custom folders that are required on all corporate computers. 
# - Run this script on a new computer or a computer being reassigned to another employee.
# - This script can run on all computers.
#-------------------------------------------------------------------------------------------------

#USAGE -------------------------------------------------------------------------------------------
# - To create folders with default names run the command: ./folderCreator.zsh
# - To define your own folder names: ./folderCreator.zsh <folder1> <folder2> <folder3>
# - Available options  : Only the help option is available
# - Getting help       : Use the -h or the -help options to get more information. Or you can use the man command to view the man page.
#-------------------------------------------------------------------------------------------------

#WARNING/CAUTION ---------------------------------------------------------------------------------
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
# This script doesn't perform any validation of the folder names being passed in by the user. 
# If the script does not see the -h or the -help options then it will assume that the data being passed in is the name of the folder.
# The user of the script must ensure that the desired folder names are passed in.
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#-------------------------------------------------------------------------------------------------

#INSTALLATION ------------------------------------------------------------------------------------
# Instructions for placing the script in the correct place are listed here. 
# Location:		/Library/Scripts/
# Permissions:	rwx r-x r-x
#-------------------------------------------------------------------------------------------------

#REQUIREMENTS ------------------------------------------------------------------------------------
# Shell:		/bin/zsh
# OS:			macOS Big Sur 11.4 or later
# Dependencies:	None
#-------------------------------------------------------------------------------------------------

#HELP/SUPPORT ------------------------------------------------------------------------------------
# You can get help by running the following commands.
# ./folderCreator.zsh -h
# ./folderCreator.zsh -help
# OR
# man folderCreator.zsh
# You can also view the log file for the same at: ~/Library/Logs/folderCreator_log_v1-4.log
#-------------------------------------------------------------------------------------------------

#HISTORY -----------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
# Version 1.0: Basic script which creates the folders
# Version 1.1: Gives user the ability to specify the folder names at run time.
# Version 1.2: Adds safety checks to the scripts
# Version 1.3: Includes documentation as well as ability to get help.
# Version 1.4: Includes optimisation using for loop
#-------------------------------------------------------------------------------------------------

#-------------------------------------------------------------------------------------------------
# ------------------------------ SCRIPT STARTS HERE ----------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------

#These are the default values used for the folder names incase the user doesn't provide any.
TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"

#Script version number
VERSION_NUMBER="1.4"

#Command name
COMMAND_NAME="folderCreator.zsh"

#1. Check to see if the user is asking for help. In which case we will have to provide information about the command.
if [[ $1 == "-h" ]] || [[ $1 == "-help" ]]; then
	echo "ABOUT 
-----
fileCreator_v1-4.zsh
Version $VERSION_NUMBER

NAME 
----
$COMMAND_NAME — Folder creation utility SYNOPSIS
$COMMAND_NAME folder names [ verbs ]

DESCRIPTION 
-----------
$COMMAND_NAME creates 3 folders in the home folder. In case the folder names are not provided then the command will create folders with default names 'Tools', 'Reports', \"Help\".

There is also the option of getting help via the help verb.
- This script is intended for creating the custom folders that are required on all corporate computers. 
- Run this script on a new computer or a computer being reassigned to another employee.
- This script can run on all computers.

VERBS 
-----
[ −h −help] Both the options are used to invoke the help documentation.
[ −v −version] Both the options are used to get the version number of the folderCreator command.

REQUIREMENTS 
------------
The following are the minimum requirements to get the script running.
Shell:\t\t zsh
OS:\t\t macOS Big Sur 11.4 or later
Dependencies:\t None

INSTALLATION 
------------
$COMMAND_NAME can be installed anywhere you wish. However, there are certain locations that are recommended.
Location:\t /Library/Scripts/ 
Permissions: \t rwxr-xr-x

USAGE  
-----
$COMMAND_NAME folder1 folder2 folder3 
Will create folders with your own names. 

$COMMAND_NAME -h OR $COMMAND_NAME -help 
Will invoke the help utility.

$COMMAND_NAME -v OR $COMMAND_NAME -version 
will print the version number in stdout.

WARNING/CAUTION  
---------------
$COMMAND_NAME does not perform any validation of names. The only options that folderCreator accepts are -h and -help verbs or the -v and -version verbs. If the script does not see the -h , -help or the -v , -version options then it will assume that the data being passed in is the name of the folder. The user of the folderCreator command must ensure that the desired folder names are passed in.

EXAMPLES 
--------
$COMMAND_NAME Resources Results Assistant
This will create 3 folders Resources , Results , Assistant , in the user’s home folder. 

$COMMAND_NAME
This will create 3 folders with the default names

$COMMAND_NAME Apps
This will use the Apps name for the first folder but the default names for the last 2 folders. 

DIAGNOSTICS 
-----------
The script produces a log file called ~/Library/Logs/folderCreator_log_v1-x.log
This file is typically located in the user’s home folder log folder. The x represents the version number of $COMMAND_NAME
You can view the logs for each respective version.

COPYRIGHT  
---------
Copyright (c) Amaranthine 2015-2021. All rights reserved. https://amaranthine.in

EXIT STATUS  
-----------
In most situations, $COMMAND_NAME exits 0 on success"
	exit 0
fi

echo "$(date) Running script $0 to create folders."
echo ""

#2. Check to see if the version number is 
if [[ $1 == "-version" ]] || [[ $1 == "-v" ]]; then
	echo "Version: $VERSION_NUMBER"
	exit 0
fi

#3. The following if statements check to see if the script is receiving any arguments. It then picks those arguments and assigns them to the respective variables for use in the script.
if [[ $1 != "" ]]; then
	TOOLS_FOLDER=$1
fi

if [[ $2 != "" ]]; then
	REPORTS_FOLDER=$2
fi

if [[ $3 != "" ]]; then
	HELP_FOLDER=$3
fi

TODAY=$(date)
PATH_TO_LOG="$HOME/Library/Logs/folderCreator_log_v1-4.log"

echo "$(date) Starting" >> $PATH_TO_LOG

#4. Go to the home folder.
cd $HOME

#5. Check to see if each of the folders exists. If it exists then do not create it. Else create the folder. 
echo "$(date) Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER" >> $PATH_TO_LOG

for item in $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER; do
	if [[ -d $item ]]; then
		echo "$(date) Not creating $item as it already exists." >> $PATH_TO_LOG
	else
		echo "$(date) Creating $item" >> $PATH_TO_LOG
		mkdir $item
	fi
	
	#6. Create the task completion file inside each folder.
	echo "$(date) Creating hidden file for $item folder." >> $PATH_TO_LOG
	cd $item
	#7. Generate the file names based on the folder names.
	touch ".$item-FolderCreated"
	cd ..
done

echo "$(date) Task completed. Have a nice day!"
	
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ END OF SCRIPT --------------------------------------------------

Video

Download

You can download the completed script from here.

Shell scripting in macOS – Part 7: Miscellaneous

This article is a continuation of the previous article. We will be taking the previous script and using it to build on the concepts we will learning in this article.

We will be covering a few different features, available in shell scripting, in this article.

Functions

Often times, you will find that there are some operations that you perform repeatedly across different points in the script. It would be extremely useful to write this logic once and reuse it over and over in a quick and efficient manner. Functions allow us to do just that.

#!/bin/bash

#function syntx ----------
repeat() {
	echo "Function without the function keyword"
}

repeat 

#function with function keyword ----------
function message() {
	echo "The argument is $1"
}

message "Arun"

#function with a local variable
#--------------------------------------------------
function localVar() {
	local value="ABC"
	echo $value
}

localVar 

#function with an argument being passed in
#--------------------------------------------------
function report() {
	echo "Argument passed in: $1"
}

report "Value 1"

function argsParameters() {
	echo "\$# -> Number of arguments"
	echo "\$* -> All positional arguments as a single word"
	echo "\$@ -> All positional arguments as separate strings"
	echo "\$1 -> First argument"
	echo "\$_ -> last argument of previous command"
}

argsParameters 

#function returning value
#--------------------------------------------------
function operation() {
	echo "XYZ"
}

answer="$(operation)"
echo $answer

function retCode() {
	echo "Return code"
	return 10
}

retCode 
echo $?

Environment

#!/bin/bash

#list environment variables
echo "Print environment variables"
echo "--------------------------------------------------"
printenv 
echo ""


#print specific environment variable value
echo "Print specific environment variables"
echo "--------------------------------------------------"
printenv SHELL
printenv USER
printenv LOGNAME
printenv HOME
echo ""

#path to the printenv command
echo "Print path to printenv command"
echo "--------------------------------------------------"
which printenv

The output would look like:

Print environment variables
--------------------------------------------------
CR_RUNID=19455
TERM_PROGRAM=CodeRunner
CR_SANDBOXED=1
TERM=dumb
SHELL=/bin/zsh
TMPDIR=/var/folders/ts/gm470rbx4xv0t507dt7xmj7c0000gn/T/com.coderunnerapp.CodeRunner/
CR_INPUT=
CR_SCRIPTS_DIR=/Users/arunpatwardhan/Library/Containers/com.coderunnerapp.CodeRunner/Data/Library/Application Support/CodeRunner/Languages/Shell Script.crLanguage/Scripts
USER=arunpatwardhan
COMMAND_MODE=unix2003
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.P9J71uVoN9/Listeners
filename=envDemo.sh
__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0
CR_DEVELOPER_DIR=/Applications/CodeRunner.app/Contents/SharedSupport/Developer
CR_UNSAVED_DIR=/Users/arunpatwardhan/Library/Containers/com.coderunnerapp.CodeRunner/Data/Library/Application Support/CodeRunner/Unsaved
CR_LANGUAGE_DIR=/Users/arunpatwardhan/Library/Containers/com.coderunnerapp.CodeRunner/Data/Library/Application Support/CodeRunner/Languages/Shell Script.crLanguage
CR_ENCODING_NAME=utf-8
CR_FILENAME=envDemo.sh
PATH=/Applications/Xcode.app/Contents/Developer:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/Library/Apple/usr/bin:/Applications/CodeRunner.app/Contents/SharedSupport/Developer/bin
__CFBundleIdentifier=com.coderunnerapp.CodeRunner
PWD=/Users/arunpatwardhan/Developer
APP_SANDBOX_CONTAINER_ID=com.coderunnerapp.CodeRunner
CFFIXED_USER_HOME=/Users/arunpatwardhan/Library/Containers/com.coderunnerapp.CodeRunner/Data
CR_FILE=/Users/arunpatwardhan/Developer/envDemo.sh
XPC_FLAGS=0x0
CR_TMPDIR=/var/folders/ts/gm470rbx4xv0t507dt7xmj7c0000gn/T/com.coderunnerapp.CodeRunner/CodeRunner
XPC_SERVICE_NAME=0
SHLVL=2
HOME=/Users/arunpatwardhan/Library/Containers/com.coderunnerapp.CodeRunner/Data
CR_SUGGESTED_OUTPUT_FILE=/var/folders/ts/gm470rbx4xv0t507dt7xmj7c0000gn/T/com.coderunnerapp.CodeRunner/CodeRunner/envDemo
CR_VERSION=62959
LOGNAME=arunpatwardhan
LC_CTYPE=UTF-8
CR_RUN_COMMAND=bash "$filename"
compiler=
CR_ENCODING=4
_=/usr/bin/printenv

Print specific environment variables
--------------------------------------------------
/bin/zsh
arunpatwardhan
arunpatwardhan
/Users/arunpatwardhan/Library/Containers/com.coderunnerapp.CodeRunner/Data

Print path to printenv command
--------------------------------------------------
/usr/bin/printenv

Redirection

We have already covered a little bit of redirection in an earlier article. There are some more redirection options available that we will look at out here.

OperatorDescriptionExample
>Writes the output of the preceding command to the fileecho "ABC" > file
>>Appends information to the file being pointed to another fileecho "ABC" >> file
|Passes the output of the preceding command to the next commandls -l | grep "*.sh"

Using the above redirections there are some interesting actions that we can perform.

ActionDescription
command >> /dev/nullThis will completely discard the output of the command.
command 2>&1This will redirect stderr to stdout and show both together on stdout.
command 1>&2This will redirect stdout to stderr and show both together on stderr.

Here document

One interesting application fo the redirection operator is the concept of here documents. A here document is used to send multiple lines of input to a command. The general structure is:

command << endOfMessageFlag
message
message
message
endOfMessageFlag

In this case the endOfMessageFlag is used to inform the command that the message has come to an end. A popular example is ‘EOF’ but any text can be used. Here are some examples of here documents.

#Writing to a file
cat << EOF >> /Users/Shared/temp.log
"This is a demo "
$(date)
EOF

The above script write the message within the ‘EOF’ to the file: /Users/Shared/temp.log. The message being:

This is a demo. 
Mon Sep 25 12:31:07 IST 2022

Here is another example:

#Multiple statements to a command
osascript << EOF
display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns"
text returned of (display dialog "Enter the name of folder 1" default answer "Utilities" buttons {"OK"} default button 1 with title "Folder that will hold the utilities" with icon POSIX file "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns")
EOF

The ‘here’ document allows us to send multiple AppleScript statements to ‘osascript‘.

Folder creator script update

Let us try to use some of these features in our folder creator script.

#!/bin/zsh

#-------------------------------------------------------------------------------------------------
#NAME:		Folder creator
#AUTHOR:	Arun Patwardhan
#CONTACT:	arun@amaranthine.co.in
#DATE:		15th September 2022
#WEBSITE:	https://github.com/AmaranthineTech/ShellScripts
#-------------------------------------------------------------------------------------------------

#LEGAL DISCLAIMER --------------------------------------------------------------------------------
#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.
#-------------------------------------------------------------------------------------------------

#LICENSE/TERMS AND CONDITIONS --------------------------------------------------------------------
#MIT License

#Copyright (c) Amaranthine 2021.

#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.
#-------------------------------------------------------------------------------------------------

#ABOUT -------------------------------------------------------------------------------------------
# fileCreator.zsh
# 1.6
#-------------------------------------------------------------------------------------------------

#DESCRIPTION ------------------------------------------------------------------------------------- 
# - THis script is intended for creating the custom folders that are required on all corporate computers. 
# - Run this script on a new computer or a computer being reassigned to another employee.
# - This script can run on all computers.
#-------------------------------------------------------------------------------------------------

#USAGE -------------------------------------------------------------------------------------------
# - To create folders with default names run the command: ./folderCreator.zsh
# - To define your own folder names: ./folderCreator.zsh <folder1> <folder2> <folder3>
# - Available options  : Only the help option is available
# - Getting help       : Use the -h or the -help options to get more information. Or you can use the man command to view the man page.
#-------------------------------------------------------------------------------------------------

#WARNING/CAUTION ---------------------------------------------------------------------------------
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
# This script doesn't perform any validation of the folder names being passed in by the user. 
# If the script does not see the -h or the -help options then it will assume that the data being passed in is the name of the folder.
# The user of the script must ensure that the desired folder names are passed in.
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#-------------------------------------------------------------------------------------------------

#INSTALLATION ------------------------------------------------------------------------------------
# Instructions for placing the script in the correct place are listed here. 
# Location:		/Library/Scripts/
# Permissions:	rwx r-x r-x
#-------------------------------------------------------------------------------------------------

#REQUIREMENTS ------------------------------------------------------------------------------------
# Shell:		/bin/zsh
# OS:			macOS Big Sur 11.4 or later
# Dependencies:	None
#-------------------------------------------------------------------------------------------------

#HELP/SUPPORT ------------------------------------------------------------------------------------
# You can get help by running the following commands.
# ./folderCreator.zsh -h
# ./folderCreator.zsh -help
# OR
# man folderCreator.zsh
# You can also view the log file for the same at: ~/Library/Logs/folderCreator_log_v1-6.log
#-------------------------------------------------------------------------------------------------

#HISTORY -----------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
# Version 1.0: Basic script which creates the folders
# Version 1.1: Gives user the ability to specify the folder names at run time.
# Version 1.2: Adds safety checks to the scripts
# Version 1.3: Includes documentation as well as ability to get help.
# Version 1.4: Includes optimisation using for loop
# Version 1.5: Prompts the user in the GUI for names for the different folders.
# Version 1.6: Updated the log mechanism with the help of a function and here document.
#-------------------------------------------------------------------------------------------------

#-------------------------------------------------------------------------------------------------
# ------------------------------ SCRIPT STARTS HERE ----------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------

#These are the default values used for the folder names incase the user doesn't provide any.
TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"

#Script version number
VERSION_NUMBER="1.6"

#Command name
COMMAND_NAME="folderCreator.zsh"

#1. Check to see if the user is asking for help. In which case we will have to provide information about the command.
if [[ $1 == "-h" ]] || [[ $1 == "-help" ]]; then
	echo "ABOUT 
-----
fileCreator_v1-6.zsh
Version $VERSION_NUMBER

NAME 
----
$COMMAND_NAME — Folder creation utility SYNOPSIS
$COMMAND_NAME folder names [ verbs ]

DESCRIPTION 
-----------
$COMMAND_NAME creates 3 folders in the home folder. In case the folder names are not provided then the command will create folders with default names 'Tools', 'Reports', \"Help\".

There is also the option of getting help via the help verb.
- This script is intended for creating the custom folders that are required on all corporate computers. 
- Run this script on a new computer or a computer being reassigned to another employee.
- This script can run on all computers.

VERBS 
-----
[ −h −help] Both the options are used to invoke the help documentation.
[ −v −version] Both the options are used to get the version number of the folderCreator command.

REQUIREMENTS 
------------
The following are the minimum requirements to get the script running.
Shell:\t\t zsh
OS:\t\t macOS Big Sur 11.4 or later
Dependencies:\t None

INSTALLATION 
------------
$COMMAND_NAME can be installed anywhere you wish. However, there are certain locations that are recommended.
Location:\t /Library/Scripts/ 
Permissions: \t rwxr-xr-x

USAGE  
-----
$COMMAND_NAME folder1 folder2 folder3 
Will create folders with your own names. 

$COMMAND_NAME -h OR $COMMAND_NAME -help 
Will invoke the help utility.

$COMMAND_NAME -v OR $COMMAND_NAME -version 
will print the version number in stdout.

WARNING/CAUTION  
---------------
$COMMAND_NAME does not perform any validation of names. The only options that folderCreator accepts are -h and -help verbs or the -v and 
-version verbs. If the script does not see the -h , -help or the -v , -version options then it will assume that the data being passed in is 
the name of the folder. The user of the folderCreator command must ensure that the desired folder names are passed in. The user will also be 
prompted, via the graphical user interface, if he/she wishes to provide the names for the folders. If yes, then there will be subsequent 
prompts asking for the folder names.

EXAMPLES 
--------
$COMMAND_NAME Resources Results Assistant
This will create 3 folders Resources , Results , Assistant , in the user’s home folder. 

$COMMAND_NAME
This will create 3 folders with the default names

$COMMAND_NAME Apps
This will use the Apps name for the first folder but the default names for the last 2 folders. 

NOTE
----
The user will be asked if he/she wishes to provide custom names in all the examples mentioned above. The user's value will always override 
whatever is being provided to the script or defaults.

DIAGNOSTICS 
-----------
The script produces a log file called ~/Library/Logs/folderCreator_log_v1-x.log
This file is typically located in the user’s home folder log folder. The x represents the version number of $COMMAND_NAME
You can view the logs for each respective version.

COPYRIGHT  
---------
Copyright (c) Amaranthine 2015-2021. All rights reserved. https://amaranthine.in

EXIT STATUS  
-----------
In most situations, $COMMAND_NAME exits 0 on success"
	exit 0
fi

PATH_TO_LOG="$HOME/Library/Logs/folderCreator_log_v1-6.log"

# Function to log activity
function recordActivity() {
	cat << EOF >> $PATH_TO_LOG
[$(date)] $1
EOF
}


echo "$(date) Running script $0 to create folders."
echo ""

TODAY=$(date)

recordActivity "Starting"

#2. Check to see if the version number is 
if [[ $1 == "-version" ]] || [[ $1 == "-v" ]]; then
	echo "Version: $VERSION_NUMBER"
	exit 0
fi

#3. The following if statements check to see if the script is receiving any arguments. It then picks those arguments and assigns them to the respective variables for use in the script.
if [[ $1 != "" ]]; then
	TOOLS_FOLDER=$1
fi

if [[ $2 != "" ]]; then
	REPORTS_FOLDER=$2
fi

if [[ $3 != "" ]]; then
	HELP_FOLDER=$3
fi

#4. We can prompt the user to see if they wish to provide folder names themselves. This will override any values provided as arguments.
userClicked=$(/usr/bin/osascript -e 'button returned of (display dialog "Would you like to provide names for the folders or use the defaults instead?" buttons {"Custom", "Default"} default button 2 with icon POSIX file "/System/Library/CoreServices/HelpViewer.app/Contents/Resources/AppIcon.icns")')
	
# if the user decides to provide custom names then go ahead and ask the user via GUI prompts. Otherwise use the values sent as arguments or defaults.	
if [[ $userClicked == "Custom" ]]; then
	recordActivity "The user decided to provide custom names."
	
	TOOLS_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 1" default answer "Utilities" buttons {"OK"} default button 1 with title "Folder that will hold the utilities" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	REPORTS_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 2" default answer "Tools" buttons {"OK"} default button 1 with title "Folder that will hold the tools" with icon POSIX file "/Users/Shared/Finder.icns")')
	
	HELP_FOLDER=$(/usr/bin/osascript -e 'text returned of (display dialog "Enter the name of folder 3" default answer "Help" buttons {"OK"} default button 1 with title "Folder that will hold the support documents" with icon POSIX file "/Users/Shared/Finder.icns")')
		
	recordActivity "User provided: $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER"
else
	recordActivity "User decided to use default values: $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER"
fi

#5. Go to the home folder.
cd $HOME

#6. Check to see if each of the folders exists. If it exists then do not create it. Else create the folder. 
recordActivity "Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER"

for item in $TOOLS_FOLDER $REPORTS_FOLDER $HELP_FOLDER; do
	if [[ -d $item ]]; then
		recordActivity "Not creating $item as it already exists."
	else
		recordActivity "Creating $item"
		mkdir $item
	fi
	
	#7. Create the task completion file inside each folder.
	recordActivity "Creating hidden file for $item folder."
	cd $item
	#8. Generate the file names based on the folder names.
	touch ".$item-FolderCreated"
	cd ..
done

echo "$(date) Task completed. Have a nice day!"
	
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ END OF SCRIPT ---------------------------------------------------

One of the big advantages with using a function and a here document to generate log files is that we can change the format and structure simply by modifying the function. The message itself remains unique.

We have seen some really interesting features in this article. In the next article we will take scripting a little further by exploring Arrays and dictionaries

Download

You can download the completed script from here.

Shell scripting in macOS – Part 4: Documentation and help

This article is a continuation of the previous article. We will be taking the previous script and using it to build on the concepts we will learning in this article.

Documentation in scripts

As you must have observed, our script has already grown to a fairly large one. It still performs the same task we intended it to perform, but there are many more features and capabilities in it. As our script grows it becomes more and more difficult for us to understand what is happening and why. Of course, since we have written the script it is a lot easier for us to understand. But let’s suppose there is a colleague of yours that is reading this script. He/she might have a more difficult time trying to understand the purpose of the script. In fact, even if you attempted to read your own script, say after a break of 6 months, you will take some time understanding why exactly you wrote a piece of code.

A good way to address these issues is to document our code. Documentation gives the creator/editor of the script a chance to explain in simple terms what he/she intendeds to do and potentially why they did a particular thing.

What should be documented?

This is how the documentation template looks.

#!/bin/bash
#-------------------------------------------------------------------------------------------------
#NAME: <#documentation#>
#AUTHOR: <#author name#>
#CONTACT: <#email#>
#DATE: <#date#>
#WEBSITE: <#url#>
#-------------------------------------------------------------------------------------------------
#LEGAL DISCLAIMER --------------------------------------------------------------------------------
#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.
#-------------------------------------------------------------------------------------------------
#LICENSE/TERMS AND CONDITIONS --------------------------------------------------------------------
#MIT License
#Copyright (c) <#2021#> <#company name#>
#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.
#-------------------------------------------------------------------------------------------------
#ABOUT -------------------------------------------------------------------------------------------
#This give basic information about the file such as
# <#filename#>
# <#version#>
#-------------------------------------------------------------------------------------------------
#DESCRIPTION -------------------------------------------------------------------------------------
# Here you will give a brief decription of what your script does. Here are some of the things that you can mention.
# <#- Intended purpose#>
# <#- Situations when you can use this#>
# <#- The target computers where this should ideally run on.#>
#-------------------------------------------------------------------------------------------------
#USAGE -------------------------------------------------------------------------------------------
# This is where you give instructions on how to run the script. These could include:
# <#- Command#>
# <#- Available options#>
# <#- Getting help#>
#-------------------------------------------------------------------------------------------------
#WARNING/CAUTION ---------------------------------------------------------------------------------
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
# SOME SCRIPTS MAY MAKE SIGNIFICANT AND/OR IRREVERSIBLE CHANGES TO THE SYSTEM.
# THIS IS A GOOD PLACE TO MENTION THOSE ACTIONS. TRY TO DAW THE READERS ATTENTION OUT HERE.
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#-------------------------------------------------------------------------------------------------
#INSTALLATION ------------------------------------------------------------------------------------
# Instructions for placing the script in the correct place are listed here.
# Location: <#/Library/Scripts/#>
# Permissions: <#rwx r-x r-x#>
#-------------------------------------------------------------------------------------------------
#REQUIREMENTS ------------------------------------------------------------------------------------
# Specify what is required
# Shell: <#/bin/zsh#>
# OS: <#macOS Big Sur 11.x#>
# Dependencies: <#Other tools#>
#-------------------------------------------------------------------------------------------------
#HELP/SUPPORT ------------------------------------------------------------------------------------
# Give information about how users can get help about your script. This can onclude the use of help options or even man pages.
# Mention how they could access the same.
# For example:
# You can get help by running the following commands.
# <#<command> -h#>
# <#<command> -help#>
# OR
# <#man <command>#>
#-------------------------------------------------------------------------------------------------
#HISTORY -----------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
# This is a good place to give version history. This helps the user of the script or any future modifiers of the script understand how the script evolved.
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ SCRIPT STARTS HERE ----------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ END OF SCRIPT ---------------------------------------------------

Let us look at some of the things that can be offered.

Item
Description
Ownership
This is where we provide details about the person who originally created the script. There might be more than one author involved. Also, it would be nice to maintain a history of people who have modified the script in any way.
Legal disclaimers and License termsThis is where you specify any disclaimers or warning that you wish to provide to anyone who wishes to use the script. You may also include license terms incase you wish to share the script with a wider audience.
Description
Of all the pieces this is one of the most important components. This section actually describes the scope and purpose of the script. Anyone who is using the script will probable be looking at this section first.
Usage
Use this section to provide information about how this script should be used. You could also provide information about the location of the script.
Warning
The idea behind this section is to provide the user information about possible issues with the script. Some examples of this would include: bugs, edge cases, expected supporting assets. Anything that is necessary for the script to run without issues.
Installation
This is where information for placing the script in the correct place with the correct permissions are provided.
Requirements
Any assets, resources or other items that may be required to run the script.
Help and Support
Information about where the user of the script can get more help or how they can get more help is provided here.
History
This section gives information about who modified the script, when they did it, along with the version number. This information may also be available via a version control tool such as Git. However, it may not be the case with every user of the script. In any case it is a good idea to also keep the history in the script.

Offering help

In most situations documentation is enough. But in some situations it may not be possible to read the script. This could be because the user may not have permissions to read the script. Another good way to provide information would be by providing a mechanism to get help for this command.

This is most commonly achieved by providing an option to out script either as -h or -help.

The script has now been modified to support this. It essentially prints a message on the screen if it sees the -h or the -help option.

This is how your final code should look.

#!/bin/zsh
#-------------------------------------------------------------------------------------------------
#NAME: Folder creator
#AUTHOR: Arun Patwardhan
#CONTACT: arun@amaranthine.co.in
#DATE: 10th August 2021
#WEBSITE: https://github.com/AmaranthineTech/ShellScripts
#-------------------------------------------------------------------------------------------------
#LEGAL DISCLAIMER --------------------------------------------------------------------------------
#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.
#-------------------------------------------------------------------------------------------------
#LICENSE/TERMS AND CONDITIONS --------------------------------------------------------------------
#MIT License
#Copyright (c) Amaranthine 2021.
#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.
#-------------------------------------------------------------------------------------------------
#ABOUT -------------------------------------------------------------------------------------------
# fileCreator.zsh
# 1.3
#-------------------------------------------------------------------------------------------------
#DESCRIPTION -------------------------------------------------------------------------------------
# - THis script is intended for creating the custom folders that are required on all corporate computers.
# - Run this script on a new computer or a computer being reassigned to another employee.
# - This script can run on all computers.
#-------------------------------------------------------------------------------------------------
#USAGE -------------------------------------------------------------------------------------------
# - To create folders with default names run the command: ./folderCreator.zsh
# - To define your own folder names: ./folderCreator.zsh <folder1> <folder2> <folder3>
# - Available options : Only the help option is available
# - Getting help : Use the -h or the -help options to get more information. Or you can use the man command to view the man page.
#-------------------------------------------------------------------------------------------------
#WARNING/CAUTION ---------------------------------------------------------------------------------
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
# This script doesn't perform any validation of the folder names being passed in by the user.
# If the script does not see the -h or the -help options then it will assume that the data being passed in is the name of the folder.
# The user of the script must ensure that the desired folder names are passed in.
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#******************************************************************************************************************
#-------------------------------------------------------------------------------------------------
#INSTALLATION ------------------------------------------------------------------------------------
# Instructions for placing the script in the correct place are listed here.
# Location: /Library/Scripts/
# Permissions: rwx r-x r-x
#-------------------------------------------------------------------------------------------------
#REQUIREMENTS ------------------------------------------------------------------------------------
# Shell: /bin/zsh
# OS: macOS Big Sur 11.4 or later
# Dependencies: None
#-------------------------------------------------------------------------------------------------
#HELP/SUPPORT ------------------------------------------------------------------------------------
# You can get help by running the following commands.
# ./folderCreator.zsh -h
# ./folderCreator.zsh -help
# OR
# man folderCreator.zsh
# You can also view the log file for the same at: ~/Library/Logs/folderCreator_log_v1-3.log
#-------------------------------------------------------------------------------------------------
#HISTORY -----------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------
# Version 1.0: Basic script which creates the folders
# Version 1.1: Gives user the ability to specify the folder names at run time.
# Version 1.2: Adds safety checks to the scripts
# Version 1.3: Includes documentation as well as ability to get help.
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ SCRIPT STARTS HERE ----------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#These are the default values used for the folder names incase the user doesn't provide any.
TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"
#Script version number
VERSION_NUMBER="1.3"
#Command name
COMMAND_NAME="folderCreator.zsh"
#1. Check to see if the user is asking for help. In which case we will have to provide information about the command.
if [[ $1 == "-h" ]] || [[ $1 == "-help" ]]; then
echo "ABOUT
-----
fileCreator_v1-3.zsh
Version $VERSION_NUMBER
NAME
----
$COMMAND_NAME — Folder creation utility SYNOPSIS
$COMMAND_NAME folder names [ verbs ]
DESCRIPTION
-----------
$COMMAND_NAME creates 3 folders in the home folder. In case the folder names are not provided then the command will create folders with default names 'Tools', 'Reports', \"Help\".
There is also the option of getting help via the help verb.
- This script is intended for creating the custom folders that are required on all corporate computers.
- Run this script on a new computer or a computer being reassigned to another employee.
- This script can run on all computers.
VERBS
-----
[ −h −help] Both the options are used to invoke the help documentation.
[ −v −version] Both the options are used to get the version number of the folderCreator command.
REQUIREMENTS
------------
The following are the minimum requirements to get the script running.
Shell:\t\t zsh
OS:\t\t macOS Big Sur 11.4 or later
Dependencies:\t None
INSTALLATION
------------
$COMMAND_NAME can be installed anywhere you wish. However, there are certain locations that are recommended.
Location:\t /Library/Scripts/
Permissions: \t rwxr-xr-x
USAGE
-----
$COMMAND_NAME folder1 folder2 folder3
Will create folders with your own names.
$COMMAND_NAME -h OR $COMMAND_NAME -help
Will invoke the help utility.
$COMMAND_NAME -v OR $COMMAND_NAME -version
will print the version number in stdout.
WARNING/CAUTION
---------------
$COMMAND_NAME does not perform any validation of names. The only options that folderCreator accepts are -h and -help verbs or the -v and -version verbs. If the script does not see the -h , -help or the -v , -version options then it will assume that the data being passed in is the name of the folder. The user of the folderCreator command must ensure that the desired folder names are passed in.
EXAMPLES
--------
$COMMAND_NAME Resources Results Assistant
This will create 3 folders Resources , Results , Assistant , in the user’s home folder.
$COMMAND_NAME
This will create 3 folders with the default names
$COMMAND_NAME Apps
This will use the Apps name for the first folder but the default names for the last 2 folders.
DIAGNOSTICS
-----------
The script produces a log file called ~/Library/Logs/folderCreator_log_v1-x.log
This file is typically located in the user’s home folder log folder. The x represents the version number of $COMMAND_NAME
You can view the logs for each respective version.
COPYRIGHT
---------
Copyright (c) Amaranthine 2015-2021. All rights reserved. https://amaranthine.in
EXIT STATUS
-----------
In most situations, $COMMAND_NAME exits 0 on success"
exit 0
fi
echo "$(date) Running script $0 to create folders."
echo ""
#2. Check to see if the version number is
if [[ $1 == "-version" ]] || [[ $1 == "-v" ]]; then
echo "Version: $VERSION_NUMBER"
exit 0
fi
#3. The following if statements check to see if the script is receiving any arguments. It then picks those arguments and assigns them to the respective variables for use in the script.
if [[ $1 != "" ]]; then
TOOLS_FOLDER=$1
fi
if [[ $2 != "" ]]; then
REPORTS_FOLDER=$2
fi
if [[ $3 != "" ]]; then
HELP_FOLDER=$3
fi
#4. Generate the file names based on the folder names.
TOOLS_FOLDER_CREATED=".$TOOLS_FOLDER-FolderCreated"
REPORTS_FOLDER_CREATED=".$REPORTS_FOLDER-FolderCreated"
HELP_FOLDER_CREATED=".$HELP_FOLDER-FolderCreated"
TODAY=$(date)
PATH_TO_LOG="$HOME/Library/Logs/folderCreator_log_v1-3.log"
echo "$(date) Starting" >> $PATH_TO_LOG
#5. Go to the home folder.
cd $HOME
#6. Check to see if each of the folders exists. If it exists then do not create it. Else create the folder.
echo "$(date) Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER" >> $PATH_TO_LOG
if [[ -d $TOOLS_FOLDER ]]; then
echo "$(date) Not creating $TOOLS_FOLDER as it already exists." >> $PATH_TO_LOG
else
echo "$(date) Creating $TOOLS_FOLDER" >> $PATH_TO_LOG
mkdir $TOOLS_FOLDER
fi
if [[ -d $REPORTS_FOLDER ]]; then
echo "$(date) Not creating $REPORTS_FOLDER as it already exists." >> $PATH_TO_LOG
else
echo "$(date) Creating $REPORTS_FOLDER" >> $PATH_TO_LOG
mkdir $REPORTS_FOLDER
fi
if [[ -d $HELP_FOLDER ]]; then
echo "$(date) Not creating $HELP_FOLDER as it already exists." >> $PATH_TO_LOG
else
echo "$(date) Creating $HELP_FOLDER" >> $PATH_TO_LOG
mkdir $HELP_FOLDER
fi
#7. Create the task completion file inside each folder.
echo "$(date) Creating hidden file for $TOOLS_FOLDER folder." >> $PATH_TO_LOG
cd $TOOLS_FOLDER
touch $TOOLS_FOLDER_CREATED
cd ..
echo "$(date) Creating hidden file for $REPORTS_FOLDER folder." >> $PATH_TO_LOG
cd $REPORTS_FOLDER
touch $REPORTS_FOLDER_CREATED
cd ..
echo "$(date) Creating hidden file for $HELP_FOLDER folder." >> $PATH_TO_LOG
cd $HELP_FOLDER
touch $HELP_FOLDER_CREATED
cd ..
echo "$(date) Task completed. Have a nice day!"
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------------------
# ------------------------------ END OF SCRIPT ---------------------------------------------------

Other options

There is one more way of providing information.

  • man pages
  • Other documents such as: pdf, txt…
  • Web based blog: This could be a dedicated page or part of the version control tool page.
  • Video based tutorial. This is used in cases where the script has a lot of capabilities and is used very often.

In fact, our blog has made use of the GitHub webpage all this while. To this we have added documentation and help tools too.

Downloads

You can download the completed file from here. You can download a template for the documentation from here.

Useful scripts for macOS

Getting Started

You might find these articles useful

One of the advantages with scripts is the fact that you can easily automate many tasks. Here is an article that walks you through that process.

If you come across a situation where you want to perform a set of tasks on multiple computers then scripts come in very handy.

I will be providing the Shell Script version of the task. Feel free to make changes to the scripts as required. I will try to provide an AppleScript version of the tasks a little later.

This is not the only way to implement the scripts. There may be multiple approaches towards achieving the same result. You will have to explore and examine the correct approach.

This is not a comprehensive list. The scripts should give you some ideas and act as a useful reference when you are creating your own scripts.

I have tested these scripts on macOS Catalina 10.15

Download

You can download all the scripts from here.

Script CategoryPage Number
Settings and Accounts1
Security2
Data3
Information Collection4
File System5

Disclaimer

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.


WARNING

Please try these scripts on a test computer. Some of the scripts do make changes to the system. Always test before using these scripts.

Automation on the Mac

Automating tasks on the Mac is very useful for a wide variety of reasons. In this article we are going to look at the different technologies available for automating tasks.

TOOLS

Automator

The simplest way of achieving automation. Automator which is a built in application allows you to create task workflows by simply dragging in a set of predefined routines into a specified sequence. Let us explore how it works by creating a watermarking print plugin

Let us look at how we can create a print plugin that automatically adds a watermark to the pdf file.

  1. First get hold of an image that you will use as a watermark.
  2. Open Automator.
  3. Click on “New Document”
  4. Choose Print Plugin as the type of task to createScreen Shot 2018-03-21 at 11.58.26 AM
  5. From the left hand side drag the “Watermark PDF Documents” option. You will be able to locate this from the PDF library on the extreme right.1
  6. Add the image that will be used as a watermark. Customise the settings to your desired level. You may have to use trial and error till you get the desired output.
  7. Similarly drag the Move finder Items to the right. You will be able to locate this from the Files & Folders library.2
  8. Save the task as WatermarkCreator.
  9. Open a text file.
  10. Select File > Print
  11. Click on the PDF drop down in the print dialog.3.4
  12. Select the newly created task.
    3
  13. You have now successfully setup your own watermark creator.

Shell Scripting

For those coming from a Linux/Unix background this might be a familiar option. Very often users need to run a series of terminal commands repeatedly. While it is not difficult to do this, wouldn’t it be nice if we could write all the commands in a single file? Shell Scripts help users do just that.

To create a shell script:

  1. Open TextEdit
  2. Write the following code in there (We will write code to create a series of files and folders in our home folder for a user called admin):
    #! /bin/sh
    cd /Users/admin/
    if [ -d "/Users/admin/Applications/" ]; then
    echo "Applications Folder Exists"
    else
    mkdir Applications
    fi
    if [ -d "/Users/admin/Sites/" ]; then
    echo "Sites Folder Exists"
    else
    mkdir Sites
    fi
    if [ -d "/Users/admin/Developer/" ]; then
    echo "Developer Folder Exists"
    else
    mkdir Developer
    fi
    cd Developer
    if [ -d "/Users/admin/Developer/iOSProjects/" ]; then
    echo "iOSProjects Folder Exists"
    else
    mkdir iOSProjects
    fi
    if [ -d "/Users/admin/Developer/macOSProjects/" ]; then
    echo "macOSProjects Folder Exists"
    else
    mkdir macOSProjects
    fi
    
  3. Save the file with the name FolderCreator on the Desktop.
  4. Open the Terminal Application
  5. Let us make the script executable. To do that, run the commands:
    cd ~/Desktop
    chmod 777 FolderCreator
    
  6. Now run the command:
    ./FolderCreator

You have now easily created your own shell script. For more information about terminal commands you can read the following articles: Terminal Commands for OS X – BasicTerminal Commands for OS X – Part 2Terminal Commands – Part 3, & Configuring/Troubleshooting OS X Using Command Line

AppleScript

AppleScript is Apple’s proprietary scripting technology. It comes bundled as a part of macOS. To create AppleScript tasks we need to use the built in AppleScript editor.

Here is an example of a small AppleScript

tell application “Finder” to set the view for all Finder Windows as column view
tell application “Finder” to close every Finder Window
tell application “Safari”
open location “<a href="http://www.arunpatwardhan.com">http://www.arunpatwardhan.com</a>
open location “<a href="http://www.amaranthine.in/feedback">http://www.amaranthine.in/feedback</a>
open location “<a href="http://www.amaranthine.in/gallery">http://www.amaranthine.in/gallery</a>
end tell

Copy that block of commands in your AppleScript editor and see what comes up.

There are many more things that can be done with AppleScript. You can have popup windows asking users for commands, turn off the computer. Change the settings for different parts of the OS and for different applications. All this with commands written in a single file. All the user has to do is double click the file.

For more information about AppleScript visit Apple’s Developer site.

Launch Agents, Launch Daemons

NOTE: Scheduling Launch Agents/Launch Daemons improperly may leave your computer in an unusable state. Always test this on a computer that does not contain important data. If you are unsure, please consult someone with knowledge of the same before proceeding ahead.

Launch Agents/Launch Daemons allow you to schedule tasks which are to be performed at intervals. You can also use them to ensure that tasks are kept running and that the user does not have the possibility to quit them. To setup a launch daemon:

  1. First create a Plist file that looks like the one below. I have created a script called echoer and placed it in the /Users/admin/Applications folder where admin is the user.Screen Shot 2018-03-22 at 10.34.18 AM
  2. Place the file in the ~/Library/LaunchAgents folder. Name it in.amaranthine.demod.plist
  3. Run the command in terminal to load the Launch Agent.
    launchctl load ~/Library/LaunchAgent/in.amaranthine.demod.plist

That’s it you have just setup a simple launch agent which will ensure that your script runs every 6 seconds.

For more information or to create detailed Launch Agents/Launch Daemons visit:Creating Launch Agents & Launch Daemons

Login Items

An easy way to automatically load, Applications/Files/Folder, as soon as well login is to use Login Items. This is very easy to do.

  1. Open System Preferences > Users & Groups
  2. Switch to the Login Items tab.IMG_1560
  3. Click on the ‘+’ sign at the bottom to add new Applications. Let’s add Maps so that it launches as soon as we login. You should see it appear in the list.IMG_1561

That’s it. You have setup login items. You can repeat this process for as many applications as you wish.

Others

PHP, Perl, Python, Javascript, Swift allow you to create custom automated tasks and routines. These require knowledge of programming.

Choosing the right approach

Which one to choose depends on a lot of factors but we can break it down to 2:

  • You are a technically qualified person and understand things like programming, scripting and command line
  • You are an end user working either at home or in office.

End User

If you are an End user then you should really stick to Automator and Login Items. These are the ones that are the easiest to implement and least likely to cause any issues. You could venture and explore other options if you have a good understanding of them. Or you can ask the IT or Tech Support teams to help you with scripting and other technologies.

Tech Support or IT Person

Any of the tools mentioned above can be used by you. Make sure that you have a good command over the tools and are able to troubleshoot issues arising out of their usage.

Note: The programs/applications/tools and languages mentioned in this article may not cover all the available options. Also, anyone who uses or implements the items mentioned in the article does so at their own risk. The author does not take responsibility for any loss or damage that may arise from the use of the programs/applications/tools and languages mentioned above.