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.
Pingback: Shell scripting in macOS – Part 1 | Arun Patwardhan's Blog
Pingback: Shell scripting in macOS – Part 3 Condition checks | Arun Patwardhan's Blog