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 8: Arrays & Dictionaries

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.

So far we have been working with a single piece of data. This was stored in a single variable. For each new piece of information we created a new variable. However, we often come across situations where there is more than one value for a given item. This is where collections like arrays and dictionaries come in.

Arrays

An array is a sequential collection of data. It allows us to store more than one value in a variable. This is good for situations like the contents of a folder, list of users, list of applications. Creating, reading, modifying, and iterating over an array is very easy. Let us have a look.

Creating

To create an array simply declare a variable followed by the '=' operator followed by values within round brackets.

# Declaring an array of items 
items=("ABC" "DEF" "GHI" "JKL" "MNO" "PQR")

Note that if you assign another set of values to the items variable it will replace the original values. We will see how to add values to an existing array a little later in the article.

Reading

We need to use the ${ } to read an array. This expands the array and allows us to read different values. There are different operations possible. I have listed some of those in the code snipper below.

# Getting a specific element from the array
echo "\${items[0]} 		= ${items[0]}"

# Getting all the elements of the array
echo "\${items[@]} 		= ${items[@]}"

# Getting the count of the elements in the array
echo "\${#items[@]} 	= ${#items[@]}"

# Getting a range of values
echo "\${items[@]:3:2} 	= ${items[@]:3:2}"

Modifying

To modify we simply need to use the '+' operator before the equals. This will add the value to the existing array without disturbing the other values.

# Pushing a value into the array
items+=("STU")

# Remove a specific item
unset items[2]

A small point to note. The unset is available with /bin/sh interpreter.

Iterating

for entry in "${items[@]}"
do 
	echo "-> $entry"
done

Here is a nice example of the user of arrays. The output of the list command is stored as an array in the variable named ‘directories’. Then using the for loop we can step through each folder and in turn printing its contents out.

directories=($(ls $HOME))

for folder in "${directories[@]}"
do 
	echo $folder
	eval "ls -l $HOME/$folder"
done

Dictionaries

Dictionaries are also collections just like arrays. However, there is one major difference. While arrays are indexed using integers, dictionaries are indexed using strings.

There is one small thing to note about dictionaries. They only work with bash version 4.0 or later. So if you face issues, make sure you are running bash 4.0 or later. In my example I am using zsh version 5.8.1. To find out which version you are running simply run the following command in terminal:

bash --version

or

zsh --version

Let us look at how to create and use dictionaries.

Creating

Creating a dictionary is very easy. We simply declare an associative array and give the dictionary variable a name.

declare -A contactDetails

Modifying

Editing or adding values to a dictionary is easy too. We use the variable name followed by the '[]' index brackets with the key value inside the brackets. This is followed by the '=' operator and the value to be assigned for that key after that.

contactDetails[name]="Arun"
contactDetails[email]="arun@amaranthine.co.in"
contactDetails[website]="www.amaranthine.in"
contactDetails[blog]="www.arunpatwardhan.com"
contactDetails[phone]="+91-9821000000"
contactDetails[dob]="$(date)"

Reading

We use the ‘@{ }’ operator to expand and read values from the dictionary, just as we did with an array. The only additional detail here is that we are using the key in order to get the specific value.

# Getting the value for a specific key
echo ${contactDetails[name]}

# Getting all the values
echo ${contactDetails[@]}

# Getting all the keys
echo ${(k)contactDetails[@]}

# Getting all the values
echo ${(v)contactDetails[@]}

# Getting all the keys and values
echo ${(kv)contactDetails[@]}

# Getting number of entries
echo ${#contactDetails[@]}

Iterating

There are several different ways of iterating over a dictionary. In the example below, the for loop is iterating over all the keys from the dictionary. Inside the loop we are using each key to extract the corresponding value.

for item in "${(k)contactDetails[@]}"
do 
	printf "%-10s \t%-40s" $item ${contactDetails[$item]}
	echo " "
done

Script

Let us update out script to use arrays.

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

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

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

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

Summary

Both arrays and dictionaries now allow us to store collections of data in a single variable. This enables us to write compact scripts and deal with complex data.

Download

You can download the completed script 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 3 Condition checks

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.

Performing tasks conditionally

So far our script has been performing tasks uninterrupted one after the other. But often times you will come across a situation where you need to perform some checks before going ahead.

The main reason why we would want to perform checks is to make sure that certain criteria are met or if certain resources are present.

Only if these conditions are satisfied will we proceed ahead. Or take an alternative course of action incase the condition isn’t met.

We can find all these checks in the man page for the test command.

Let us look at some of those checks.

Test operations

You can run the following command to view all the operations possible.

man test

There are different comparison operations possible.

  • The – followed by a letter and then the file name allows us to check for different aspects of a file. Such as if it exists, whether it is a directory and more…
  • We can even compare files with each other.
  • We can compare strings.
  • And we can compare numbers.

Conditional code

Now that we have seen the different kinds of condition checks available. Let us explore how we can use the condition checks.

If statement

The if statement has various forms. We will look at the simplest one first.

if [[ -d "$HOME/Applications" ]]; then
	echo "The applications folder exists in the home folder."
fi

If else statement

If-elif-else statement

Switch on case statement

Modify our code

We will be adding checks to make sure that the arguments passed in contain values. We will also check to see if the folders exist before trying to create them.

#!/bin/zsh

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

TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"

if [[ $1 != "" ]]; then
	TOOLS_FOLDER=$1
fi

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

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

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-1.log"

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

cd $HOME

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

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!"

Your completed code should look like.

#!/bin/zsh
echo "$(date) Running script $0 to create folders."
TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"
if [[ $1 != "" ]]; then
TOOLS_FOLDER=$1
fi
if [[ $2 != "" ]]; then
REPORTS_FOLDER=$2
fi
if [[ $3 != "" ]]; then
HELP_FOLDER=$3
fi
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-1.log"
echo "$(date) Starting" >> $PATH_TO_LOG
cd $HOME
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
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!"

Video

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.

Shell scripting in macOS – Part 2: Managing information

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.

Using Variables to store information

First up we will look at variable. Variables are containers that can hold information. The biggest advantage of this is the fact that we can use information in our tasks simply by reusing the variable it is stored in. This means if there is any change at a later date, then we only have to change the value in the variable. 

So, in the future, if there is a need to modify the information, we only have a single point of change to make. This greatly aids  in the ease of maintenance of the code.

It also makes the script more readable.

NOTE: The value of a variable can be changed at a later point of time within the script. 

Creating variables is very easy. You simply declare a name and assign it a value using the = operator. For example, if we are going to be using the path to the logs folder then storing it in a variable called PATH_TO_LOGS makes sense. We would then follow it up with the = sign and follow that up with the path in quotes. 

PATH_TO_LOGS=“/Library/Logs/“

To use this variable in a command we would simple callout the name with the $ symbol prefixed before it. 

echo $PATH_TO_LOGS

The $ symbol is necessary to access the value being held by the container.

While declaring variables try to use names which explain the purpose of the variable.

Built in variables

We can see that it is very easy to define our own variables. However, we are not restricted to creating our own variables. The system provides us with predefined variables. These give us access to useful information such as:

  • Path to the current user’s home folder.
  • The shell interpreter being used.
  • The currently logged in user name. 

We can get the complete list of commands with the help of the printenv command.

printenv

How about using these variables? Well, we will use it the same way we would use our own variables. Just prefix the $ symbol before the variable name. 

echo "The path to the home folder is $HOME"

Let us update the script from the previous article.

#!/bin/zsh

echo "Running script to create folders."

TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"

TOOLS_FOLDER_CREATED=".$TOOLS_FOLDER-FolderCreated"
REPORTS_FOLDER_CREATED=".$REPORTS_FOLDER-FolderCreated"
HELP_FOLDER_CREATED=".$HELP_FOLDER-FolderCreated"

cd $HOME

echo "Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER"
mkdir $TOOLS_FOLDER
mkdir $REPORTS_FOLDER
mkdir $HELP_FOLDER

echo "Creating hidden file for $TOOLS_FOLDER folder."
cd $TOOLS_FOLDER
touch $TOOLS_FOLDER_CREATED
cd ..

echo "Creating hidden file for $REPORTS_FOLDER folder."
cd $REPORTS_FOLDER
touch $REPORTS_FOLDER_CREATED
cd ..

echo "Creating hidden file for $HELP_FOLDER folder."
cd $HELP_FOLDER
touch $HELP_FOLDER_CREATED
cd ..

echo "Task completed. Have a nice day!"

Capturing command output

Now that we have seen how variables can be created and used, then next logical step is to use them to store the outcome of a command. Why would we need to do this? Let us suppose that a command returns the path to a folder and we would like to perform multiple tasks on this folder. We can simply save the path in a variable and then use the variable across the script. 

If storing the result of the command in a variable wasn’t possible then we would have to execute the command over and over again every time we needed the result.

But before we store the outcome of the command we first need to understand how we can capture the output of a command itself. This is done with the help of command substitution. The command to be executed is placed within the $ symbol followed by parentheses.

So to store it in a variable we would just place the command we would just place this on he right hand side of the = sign. For example, if we wanted to store today’s date we would use the date command placed within the $() on the right hand side of the = sign. On the left hand side of the = sign would be the name of our variable.

TODAY=$(date)

There is an older way of doing the same thing, instead of using the $() the command would be placed within 2 back ticks.

TODAY=`date`

Writing to files

While it is useful to store information within variables there are some limitation with this. Sometimes we would like to store our data outside the script for example on some other file. The advantage with this approach is that it allows us to access the information across multiple invocations of the script. 

The way we write to a file is by redirecting the output of the command from standard output to a file. There are 2 operators that help us with this.

The redirect operator with a single angle bracket will write the contents to a file. This will replace the existing content fo the file.

echo "Hello, World!" > /Users/Shared/message.txt

The redirect operator with 2 angle brackets will also write contents to a file. But this will append or add the existing content. 

echo "Hello, World!" >> /Users/Shared/message.txt

Depending on what you want you can use one of the 2 approaches. 

Logging events taking place in the script

A log file is used to note done certain events being performed by an app, script, process, or any task. It is a very useful troubleshooting tool. This would be a nice feature to add to our script. We can log the different events that are taking place. To do this we will use the same redirect operator to write to a file.

Log files are typically stored in one of two locations in macOS:

  • ~/Library/Logs/
  • /Library/Logs

For our demo we will store it in the ~/Library/Logs/ folder. This makes sense because our script will be making changes to a user’s home folder. So ideally, the log file should also stay in the user’s home folder.

The way we will generate our log file is by redirecting the output of the echo command to our file.

echo "Hello, World!" >> ~/Library/Logs/folderCreator_log_v1-1.log

So all the echo statements we have will be modified to redirect to the log. Additionally, we will use command substitution to include the date and time in out message. Let us modify the script above to reflect these new changes.

#!/bin/zsh

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

TOOLS_FOLDER="Tools"
REPORTS_FOLDER="Reports"
HELP_FOLDER="Help"

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-1.log"

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

cd $HOME

echo "$(date) Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER" >> $PATH_TO_LOG
mkdir $TOOLS_FOLDER
mkdir $REPORTS_FOLDER
mkdir $HELP_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!"

Passing information to a script

While storing information and capturing information within a script is useful. It is also useful to have the ability to give information to a script at the time of running the script. This allows the user of the script to have greater control over the end result or outcome. 

The information that is passed into the script is store in predefined variables known as positional variables. They are named $0, $1, $2 and onwards. Let us modify the script to use these variables.

#!/bin/zsh

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

TOOLS_FOLDER=$1
REPORTS_FOLDER=$2
HELP_FOLDER=$3

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-1.log"

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

cd $HOME

echo "$(date) Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER" >> $PATH_TO_LOG
mkdir $TOOLS_FOLDER
mkdir $REPORTS_FOLDER
mkdir $HELP_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!"

The final script should look like:

#!/bin/zsh
echo "$(date) Running script $0 to create folders."
TOOLS_FOLDER=$1
REPORTS_FOLDER=$2
HELP_FOLDER=$3
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-1.log"
echo "$(date) Starting" >> $PATH_TO_LOG
cd $HOME
echo "$(date) Creating folders: $TOOLS_FOLDER, $REPORTS_FOLDER, $HELP_FOLDER" >> $PATH_TO_LOG
mkdir $TOOLS_FOLDER
mkdir $REPORTS_FOLDER
mkdir $HELP_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!"

Script locations

One last thing to talk about now is script locations. So far we have been placing our scripts where ever we wish and running them from there. But it may be a good idea to use a consistent location for the same. There are several candidates for this:

  • ~/Library/Scripts/
  • /Library/Scripts/

These are the more standard locations.

The only decision that needs to be made is whether it is the Library folder in the user’s home folder or the library folder located at root. This affects if the script is available only for a specific user or for all users on a computer.

There are other locations possible too. Developers often have a folder in the home folder called “Developer”. This needs to be manually created, but once created the system recognises it as the folder where files related to development are kept. You can create a scripts folder and place it in there.

Another popular location is the Application Support folder within the library folder. You can create a folder that represents items related to your scripts and then place the script in that folder. Note that these folders will have to be created by manually.

  • ~/Developer/Scripts/
  • ~/Library/Application Support/<your folder>/

These 2 locations would need to be created.

Scripts are not typically exposed to the end user. There is typically some kind of scheduling mechanism that triggers them. However, if a script is designed to be used by the end user you could even place them in:

  • /Applications/Scripts/
  • ~/Applications/Scripts/

Like the developer folder the applications folder in the home folder needs to be created. But once created the system recognises what it is intended for and gives it special privileges. The scripts folder within it will have to be created manually.

While this may not seem like a big deal. Placing your scripts in the correct location can lead to more consistent experiences, make troubleshooting easy, and also hide potential complexity.

Conclusion

The ability to store data within a script, pass data to a script or store data on an external file from within a script has several advantages. This makes the script more power and compact at the same time. It also makes the script less susceptible to errors and mistakes.

Video

Download

You can download the script from the same git repository as the previous one. The script is named folderCreator_v1-1.zsh.

Shell scripting in macOS – Part 1

The scripts in the following articles are written in macOS Big Sur. You can use these concepts to create scripts on UNIX and Linux too.

This is the first part of a multipart series. You can find links to further articles at a table located at the bottom of this article. I will be updating this article with links to new articles as I publish them.

What is shell scripting?

Shell scripts are simply files that list out a series of commands in the sequence in which they are to be executed. By commands we typically mean other shell commands. But these could also be other executables, scripts, or commands from other languages.

Why do we need it?

There are several reasons. The most common reason being automation. For example, If there are steps that we perform on a repeated basis such as checking for the presence or absence of particular files we could easily automate this task with the help of a script. Or If we want to perform certain tasks at scale: such as creating a set of files and folders that should always be there within the user’s home folder.

Scripting also has the added benefit of consistency. By performing the tasks the same way we can ensure that our desired outcome is the same every time. 

What is required for creating shell scripts?

Before we go ahead and look at how to create our own scripts there are a few things we need to keep ready at hand.

  • First we would need to know the commands we would have to execute to achieve our goal. This list is quite large and one would not necessarily know all the commands supported. But overtime your knowledge of these commands will grow. So do not worry!
  • Second, We need to pick a shell interpreter.
  • We also need to decide how we will be accessing the command line interface. This would most likely be via the Terminal application, but there are other ways too.
  • Finally we need to decide on the editor we will use to create our scripts. I will talk about this a little later.

Shell interpreters

The shell interpreter is as the name says the object that will interpret the commands and execute them. The default shell interpreter for macOS is zsh starting macOS Catalina. We can choose to use that or any other interpreter. While most commands we will be using will be common ones that are available across all interpreters be aware that some commands may be unique to certain interpreters only.

Commands

We will need to know some basic commands that will help us compose our scripts.There are several commands available in macOS. We will be learning about quite a few of those over the course of the next few articles. The table below lists some of the commands that we will be using.

CommandDescription
cdChange directory. This command changes the current working directory to the specified path. We use this command to navigate to another folder.
mvThis command moves the contents from the specified folder to another folder.
lsLists the contents of the folder.
rmRemove the specified content.
cpCopy the contents of a folder to another folder.
touchUpdate the timestamp for a file or folder.
pwdPrint the complete path to the present working directory.
mkdirCreate a folder.
echoPrint the string out onto stdout.

Be aware that many commands will create/modify/delete items in the current folder if the absolute path is not specified in the command. This may result in unexpected or unintended behavior.

Editor

I will be using Xcode as the editor for our scripts. However, you can use any editor you wish. You will find the a list of editors at the bottom of the article.

Using Xcode as an editor for scripting may be a bit of an overkill. It is a very heavy application primarily designed for app development. If you are currently developing apps and are already using Xcode then you can go ahead and use it for scripting too. Otherwise it might be a good idea to go in for a different tool.

Building our first shell script

In order to build our script. Let us take a simple scenario. Let us suppose that every user in our organisation must have the following folders:

  • Tools
  • Reports
  • Help

All these folders must be located in the home folder for each user. So let us take it step by step. We will perform these commands manually from the Terminal application.

  1. The first command is the command to navigate to the home folder.
cd ~/

The ~/ represents the path to the current user’s home folder. The cd command is used to change the working directory to the newly specified path.

  1. Now we will create the 3 folders.
mkdir Tools
mkdir Reports
mkdir Help

All the 3 commands are creating a new folder. Since we did not specify the complete path to the folder. These items are created in the working directory.

  1. Now we will step into each folder and create an empty hidden file.
cd Tools
touch .ToolsFolderCreated
cd ..

Let us break down these commands one by one.

First we go into the Tools folder.

Then we use the touch command to update the timestamp of the.ToolsCreated file. Since the file doesn’t exist the touch command creates the file for us. Also as the file starts with the . character it is hidden by default. Creating a hidden file like this is a good way of leaving behind some flag indicating that the script ran successfully. Of course, in our example this can be determined simply by seeing the folders that are created. But in more elaborate situations they are a very useful way of laying down milestones for a script.

The next command takes us back a step outside the enclosing folder. In our case the Tools folder is inside the home folder. So we are going back to the home folder.

We will repeat the steps again for the Reports and Help folders.

cd Reports
touch .ReportsFolderCreated
cd ..

cd Help
touch .HelpFolderCreated
cd ..

Those are the commands we execute to get the desired result. You can switch to the graphical user interface to see if the items have been created. Note that the files created with the touch command will not be visible by default.


Now that we have seen how these commands work. Let us create a script.

  1. Use any editor you like. I will start off with TextEdit. Create a new file. If you are using TextEdit then do not forget to convert the formatting to plain text. Format > Make Plain Text.
  2. Give the file any name you want. I will call it folderCreator.zsh.
  3. Save the file where ever you wish. I will save it on the Desktop folder for now.
  4. On the first line we need to specify our interpreter. This indicates that the commands in our script need to be interpreted by the zsh interpreter.
#!/bin/zsh
  1. One the next line we will type the command to go to the home folder.
#!/bin/zsh

cd ~/
  1. Next we will type the command to create the 3 folders.
#! /bin/zsh

cd ~/

mkdir Tools
mkdir Reports
mkdir Help
  1. Finally we will add the code to create the hidden files.
#! /bin/zsh

cd ~/

mkdir Tools
mkdir Reports
mkdir Help

cd Tools
touch .ToolsFolderCreated
cd ..

cd Reports
touch .ReportsFolderCreated
cd ..

cd Help
touch .HelpFolderCreated
cd ..

  1. A nice addition to the script would be the echo command. This command would let the person who is running the script know about the different events taking place.
#! /bin/zsh

echo "Running script to create folders."

cd ~/

echo "Creating folders: Tools, Reports, Help"
mkdir Tools
mkdir Reports
mkdir Help

echo "Creating hidden file for Tools folder."
cd Tools
touch .ToolsFolderCreated
cd ..

echo "Creating hidden file for Reports folder."
cd Reports
touch .ReportsFolderCreated
cd ..

echo "Creating hidden file for Help folder."
cd Help
touch .HelpFolderCreated
cd ..

echo "Task completed. Have a nice day!"

Your completed script should look like:

#! /bin/zsh

echo "Running script to create folders."

cd ~/

echo "Creating folders: Tools, Reports, Help"
mkdir Tools
mkdir Reports
mkdir Help

echo "Creating hidden file for Tools folder."
cd Tools
touch .ToolsFolderCreated
cd ..

echo "Creating hidden file for Reports folder."
cd Reports
touch .ReportsFolderCreated
cd ..

echo "Creating hidden file for Help folder."
cd Help
touch .HelpFolderCreated
cd ..

echo "Task completed. Have a nice day!"
  1. Save the script.

That’s it. You have just created your first script.

Running our first shell script

The next step would be to run our script. There are 2 ways of doing this. We will look at both the options.

Option 1

We can directly run the script using the zsh command.

zsh ~/Desktop/folderCreator.zsh

Note that we will need to provide the path to the script file.

This is a straightforward way. We simply tell the interpreter to execute the commands in our script.

Option 2

This option requires a few more steps.

  1. First we need to change the permissions on the script. We need to make sure that all 3: Owner, Group, Everyone else have the read and execute permissions. Of course, you are free to change the permissions to whatever you want. But the execute capability is required. We will change the permissions from the command line.
chmod ugo+x ~/Desktop/folderCreator.zsh

There are other ways of writing this command too. But for now we are simply saying that we want to add the execute capability to the Owner, Group, Everyone else. If you look at the file in the GUI, you will see its icon has changed to the executable icon.

  1. Next we will simply run the following command from the terminal application.
./Desktop/folderCreator.zsh

Now we can simply run the script by invoking it from the terminal application. Or we can trigger it from the graphical user interface by simply double clicking on the file.

There you go. You have successfully created and tested your own script. Try to play around with some of the terminal commands and create your own scripts.

Video

You can watch the video I have created in case you wish to see the steps.

Download script

You can download this version of the script from here.

Popular editors for shell scripts

Here are some links for popular editors.

Coderunner

Emacs

Atom

Xcode

Shell scripting topics

Here are the links to more parts in this series. I will add the links as I publish the articles.

Part 2: Managing information

Part 3: Conditional Checks

Part 4: Documentation and help

Part 5: Loops

Part 6: User Interaction

Part 7: Miscellaneous topics

Part 8: Arrays and Dictionaries

Part 9: Automating Scripts

Part 10: Distribution