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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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.
Command
Description
cd
Change directory. This command changes the current working directory to the specified path. We use this command to navigate to another folder.
mv
This command moves the contents from the specified folder to another folder.
ls
Lists the contents of the folder.
rm
Remove the specified content.
cp
Copy the contents of a folder to another folder.
touch
Update the timestamp for a file or folder.
pwd
Print the complete path to the present working directory.
mkdir
Create a folder.
echo
Print 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.
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.
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.
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.
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.
Give the file any name you want. I will call it folderCreator.zsh.
Save the file where ever you wish. I will save it on the Desktop folder for now.
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
One the next line we will type the command to go to the home folder.
#!/bin/zsh
cd ~/
Next we will type the command to create the 3 folders.
#! /bin/zsh
cd ~/
mkdir Tools
mkdir Reports
mkdir Help
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 ..
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!"
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.
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.
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.
Custom operators are operators that are defined by us and are not part of the programming language natively.
We are all aware of the built in operators in the Swift Language.
Operators like: + – * % > == ! to name a few.
These operators are defined by the system. It is also possible for us to overload some of these operators. However there are situations where we would like to create our own operators that perform operations not defined by the system.
Thats exactly what Custom operators are. They are operators defined by the developer. These are not overloaded operators but completely new operators that don’t exist otherwise.
These operators are used within the project that we are working on. Though it is possible for us to share these operators using Swift Packages or XCFrameworks.
These operators are typically associated with a specific type and their behavior is also defined by us.
Why do we need them?
There are many reasons why we would want custom operators:
Allow for more compact and concise syntax.
Using custom operators allows our code to be more compact. Entire function calls can be condensed into a single operator.
Make the code more readable
This also improves the readability of our code. Properly chosen symbols can convey the message immediately and easily.
Allow for consistency in design of code
One of the other things that custom operators help us achieve is consistency. By using standard operations as operators we make our code more familiar and consistent to others who may read it. Programmers are familiar with the concept of operators and using them for different operations. So even if they may not immediately recognise the operator they would understand that there is some task for them to perform.
And finally it encourages reusability.
What do we need to create custom operators?
There are a couple of things that we need to create custom operators:
A logic for the action being performed by the operator
A list of valid symbols
Information about the operators attributes like prefix, postfix, infix.
The precedence of the operator if it is an infix operator
Operator Rules
There are some rules that must be followed when we are constructing the symbol for our operator. Most of the requirements are rather straightforward. However, choosing the right symbol is a very important task. There are a set of symbols that are allowed.
There are rules as far as whitespace around operators is concerned.
And finally there are certain symbols are allowed only in combination with other symbols.
Operator types
Type
Description
Prefix
Operators that appear before a variable or value. These are unary operators.
Postfix
Operators that appear after a variable or value. These are unary operators.
Infix
Operators that appear in between variables or values. These are binary operators.
Allowed Characters
This is the important bit. Which characters are allowed for usage as an operator.
We can have ASCII symbols that are used for builtin operators.
There are also many mathematical symbols that can be used as operators.
Note that the list of symbols show in the slide are not complete.
The next important bit is the whitespace around the operator.
If an operator has a whitespace on both the sides or doesn’t have whitespace on both the sides then it is interpreted as a binary operator. This is what would appear for infix operator.
If an operator has whitespace only on the left then it is a prefix unary operator.
If an operator has whitespace only on the right then it is a postfix unary operator.
If an operator does not have whitespace on the left but is followed by a dot then it is treated as a postfix unary operator.
Finally, any round, brace, square brackets appearing before or after the operator along with comma, colon, & semicolon are treated as whitespace
Making sure that we put the whitespace in the correct place while using these operators is very important.
No.
Rule
Example code
1
If an operator has a whitespace on both the sides or doesn’t have whitespace on both the sides then it is interpreted as a binary operator
a**b or a ** b
2
If an operator has whitespace only on the left then it is a prefix unary operator
**a
3
If an operator has whitespace only on the right then it is a postfix unary operator
a**
4
If an operator does not have whitespace on the left but is followed by a dot then it is treated as a postfix unary operator
a**.b is treated as a** .b
5
(, {, [ before the operator and ), }, ] after the operator along with ,, :, ; are treated as whitespace
There are some exceptions to the rules we just saw. Especially with exclamation mark & question mark.
! & ? which are predefined are always treated as postfix if there is no whitespace on the left
If we wish to use ? In optional chaining then it must not have whitespace on the left
To use it as a ternary conditional operator ?: it must have whitespace on both the sides
Operators with a leading or trailing <, > are split into multiple tokens. For example, in Dictionary<String, Array<Int>> the last 2 arrows are not interpreted as shift operator.
Operator grammar
There are rules for constructing operators. Only certain combinations are allowed.
Each operator contains a symbol which forms the operator head. The head is the first character in the operator.
The head may or may not be followed by 1 or more characters which are operator characters.
The head and the optional characters combined together form the operator.
The head itself can contain a one out of a set of valid symbols. Or it can contain a period.
These are some of the symbols allowed for usage as the head of the operator. You can choose any one of those.
/, =, -, +, !, *, %,<, >, &, |, ^, ?, ~
U+2055–U+205E
U+00A1–U+00A7
U+2190–U+23FF
U+00A9 or U+00AB
U+2500–U+2775
U+00AC or U+00AE
U+2794–U+2BFF
U+00B0–U+00B1
U+2E00–U+2E7F
U+00B6
U+3001–U+3003
U+00BB
U+3008–U+3020
U+00BF
U+3030
U+00D7
U+00F7
U+2016–U+2017
U+2020–U+2027
U+2030–U+203E
U+2041–U+2053
For the successive characters you can use any of the symbols allowed for the head plus some additional allowed symbols. The list above contains all the allowed symbols.
/, =, -, +, !, *, %,<, >, &, |, ^, ?, ~
U+2055–U+205E
U+00A1–U+00A7
U+2190–U+23FF
U+00A9 or U+00AB
U+2500–U+2775
U+00AC or U+00AE
U+2794–U+2BFF
U+00B0–U+00B1
U+2E00–U+2E7F
U+00B6
U+3001–U+3003
U+00BB
U+3008–U+3020
U+00BF
U+3030
U+00D7
U+0300–U+036F
U+00F7
U+1DC0–U+1DFF
U+2016–U+2017
U+20D0–U+20FF
U+2020–U+2027
U+FE00–U+FE0F
U+2030–U+203E
U+FE20–U+FE2F
U+2041–U+2053
U+E0100–U+E01EF
Examples
.+.
≈
√
**
Operator Precedence
As far as infix operators are concerned there is also the question of precedence. Precedence is used to determine the operator priority when there are multiple operators in a single statement.
precedencegroup <#precedence group name#> {
higherThan: <#lower group names#>
lowerThan: <#higher group names#>
associativity: <#associativity#>
assignment: <#assignment#>
}
While the first 2 values are straightforward, they simply help determine the exact position of the newly created precedence as compared to existing precedences, the associativity and assignment are extra items that are not immediately clear.
Type
Description
Values
Associativity
Determines order in which a sequence of operators with the same precedence are evaluated in the absence of grouping brackets
left, right, none
Assignment
Specifies priority when used with optional chaining. TRUE: Same grouping rules as assignment operator from standard libraryFALSE: Same rules as operators that don’t perform assignment
true, false
The assignment of a precedence group specifies the precedence of an operator when used in an operation that includes optional chaining. When set to true, an operator in the corresponding precedence group uses the same grouping rules during optional chaining as the assignment operators from the standard library. Otherwise, when set to false or omitted, operators in the precedence group follows the same optional chaining rules as operators that don’t perform assignment.
Determines order in which a sequence of operators with the same precedence are evaluated in the absence of grouping brackets. so for example 4 – 6 – 7 has the minus sign which has left associativity. The operation 4-6 is grouped and then the – 7 operation is performed.
Nonassociative operators of the same precedence level can’t appear adjacent to each to other.
It is fairly easy to create our own operators. You can try the code in a playground. We will be creating 1 operator of each type: postfix, prefix, infix.
Create a new playground.
Declare the creation of the prefix operator as shown. This will be used as a squaring operator.
prefix operator **
Now we will provide a generic version of the operator implementation.
That’s it. It is that simple to create our own prefix operator. Now let us test it.
Create a variable of type Float and use the operator we have just created.
var lengthOfSideOfSquare : Float = 1.1
var areaOfSquare : Float = **lengthOfSideOfSquare
print("The area of a square whose side is \(lengthOfSideOfSquare) centimeters long is \(areaOfSquare) square centimeters")
Similarly declare a postfix operator. This one will perform conversion to a string.
postfix operator ~>
Now we will implement this operator. To do that let us make a simple type which will have the to string operator capability.
struct Person {
var name : String = ""
var age : Int = 0
}
extension Person {
static postfix func ~> (inputValue : Person) -> String {
return "NAME: \(inputValue.name)\nAGE: \(inputValue.age)"
}
}
Let us try this operator out and see.
var developer : Person = Person(name: "Arun Patwardhan",
age: 35)
var description : String = developer~>
print(#line, description)
Now let us implement an infix operator. The one that we are going to implement is a similarity operator which can be used to determine the degree of similarity between objects of the same type. To do that let us start off by declaring an enum which holds the values for the degree of similarity.
enum DegreeOfSimilarity {
case exactly_the_same
case almost_the_same
case slightly_similar
case completely_different
}
Infix operator can also have a precedence associated with it. Let us declare our own precedence and use it for our operator.
higherThan: This indicates that our precedence has higher priority than the Addition precedence
lowerThan: This indicates that our precedence has lower priority than the Multiplication precedence
Associativity: This indicates that our operator is not associative. So we cannot combine multiple occurrences of our operator in one statement.
assignment: This indicates that out operators has the same behaviour, as other operators that assign, when it comes to optional chaining.
Now we can declare our infix operator.
infix operator ≈ : DegreeOfSimilarityPrecedence
It is useful to save your new operator symbols as code snippets to easily use them. You can read this article if you don’t know how to create a code snippet.
Let us look at the implementation. I am going to use the same person type we used earlier.
var employee1 : Person = Person(name: "Jack",
age: 22)
var employee2 : Person = Person(name: "John",
age: 21)
var employee3 : Person = Person(name: "Jack",
age: 23)
var employee4 : Person = Person(name: "Jack",
age: 23)
print(#line, employee1 ≈ employee2)
print(#line, employee1 ≈ employee3)
print(#line, employee3 ≈ employee4)
Run the code and see the end result.
Feel free to create more operators and play around. You could also package these operators in a swift package and share them around. I have shared links to
Summary the new operator
Creating operators is very easy. Most of the requirements are rather straightforward. However, choosing the right symbol is a very important task.
The one thing that we should keep in mind is not to over use these. It can be tempting to do this. But abstracting everything can make the code look a little too vague.
So that is how you can create operators.
Download the sample project
I have uploaded some of the custom operators, that I have shown above, as a Swift Package. You can download the package as well as a demo project, which shows how to use them, from the links below.
Code snippets are as the name suggests, short pieces of code that can quickly be inserted into your code file. This is done either by dragging the snippet or by typing out the completion. Code snippets are very easy to create and use and can be applied in a wide variety of situations.
We will look at how you can create & use snippets. The following example is done in a playground, but this could be done from anywhere within Xcode.
Note: The example below was performed on Xcode 11.7
How do we create code snippets?
Start off by writing the code or text that you want to convert into a snippet. For example, I have a set of comments that I add at the start of every function. Write it down.
/**
This function performs a comparison of the 2 objects
- important: This function does not perform data validation.
- returns: `Bool`.
- requires: iOS 13 or later
- Since: iOS 13
- parameter lhsValue: This holds the value on the lhs of the operator
- parameter rhsValue: This holds the value on the rhs of the operator
- Example: `var answer = venueAddress == hotelAddress`
- author: Arun Patwardhan
- copyright: Copyright (c) Amaranthine 2020
- date: 14th September 2020
- version: 1.0
*/
2. Select it. 3. From the menu bar select Editor > Create Code Snippet.
This brings up the snippet editor. 4. Give your snippet the following details.
Option
Description
Name
This is the name of your code snippet.
Platform
This determines whether your snippet is available only for certain platforms: say only for iOS.
Availability
This determines the place where the snippet can be added.
Completion
This is the word that we will be typing in the Xcode editor to trigger the implementation of the snippet
Language
This specifies the language for which the snippet will be applied.
Name: Func Documentation
Language: Swift
Platform: All
Availability: All scopes
Completion: doc
Note that the values for Name and Completion can be whatever you want.
This is how the snippet should look.
5. Now we will try to use it in the editor. Start typing the completion word in the Xcode editor.
6. Select the snippet with your name and completion. 7. Hit enter. You should see the comments you want appearing in the editor.
Placeholder
We can make our snippet above even better by using placeholders. Placeholders are pieces of text that can be replaced by the user. They also give information about what is expected in the placeholder.
We can add place holders by simply typing the hint inside placeholder brackets. Placeholder brackets are nothing but open <# and closing #>. For example:
<# some text #>
Which appears as
The user will simply click on the “some text” placeholder.
There are plenty of places in our comments where we can use placeholders. When we use the code snippet it should put comments with place holders in them.
Let us change the comments in our Xcode editor first. We will edit the snippet later on. Make the changes as shown below.
/**
<# put the description of your function here #>
- important: <# mention some important points here #>
- returns: `<# return type #>`.
- requires: iOS <#iOS Version#> or later
- Since: iOS <#iOS Version#>
- parameter <#param 1#>: This holds the value on the lhs of the operator
- parameter <#param2#>: This holds the value on the rhs of the operator
- Example: `<#put some example code here#>`
- author: Arun Patwardhan
- copyright: Copyright (c) Amaranthine 2020
- date: <#day#> <#month#> <#year#>
- version: 1.0
*/
We have made the following items into comments.
Description
OS Version
Return type
Important comments
Parameter 1 & 2 names
Sample code
Day, Month, & Year
Of course, there are other things we could change too. Feel free to make any other changes you can think of.
2. Let us now copy these changes to the code snippet we created. Copy the code from the Xcode editor.
To bring the snippet editor again simply click on the add object button in the upper right hand corner of Xcode.
4. Select the snippet from the list on the left and click edit. 5. Paste the code that you just copied. Your snippet editor should look like this:
6. Click on ‘Done’ once you are finished making changes. Your snippet will now be ready.
7. Try adding the snippet into your editor just like before. Simply type in the completion for your snippet.
Dragging snippets
We can use the autocompletion we saw earlier. But it is also possible for us to drag snippets.
Exporting code snippets
Once created it is possible to export/import code snippets too. All the snippets are located in the following folder.
~/Library/Developer/Xcode/UserData/CodeSnippets/
Any snippets you have created will be located there.
Any new snippets to be added will have to be added there.
Summary
Code snippets are easy to create and have several advantages:
They improve the developers experience
Promote consistent code
Speeds up the process of writing code
Encourages developers to use each others snippets and gain the first 3 advantages.
Creating and using snippets is very very easy and has a lot of benefits. So go ahead and create snippets.
This article lists out different macOS terminal commands you might encounter. You can use this list as a starting point in your search for a command to perform a specific task. This list is by no means exhaustive.
Many of the commands have also been used in the article I wrote some time back. You can have a look at the scripts to see some of the commands being used.
To get more information about the commands simply run the following command from within Terminal Application. For example, to view the manual page for tmutil simply type:
man tmutil
For fdesetup
man fdesetup
Here is a nice command to quickly open the man page in the Preview App.
man -t tmutil | open -f -a /System/Applications/Preview.app
Note
This is not a complete list of commands
Some commands are available through the macOS Recovery Volume only
Some commands required other resources such as the OS installer
Some commands are available with certain versions of the OS only
Please read the documentation for more details. Use the commands with care. Improper use of commands may result in loss of data or damage to the computer.
Commands
Installation
Command
Description
startosinstall
Used to start the installation of macOS from the command line.
createinstallmedia
Used to create an external install disk.
Security
Command
Description
fdesetup
Manage FileVault configuration.
security
Manage keychain and security settings
spctl
Manage security assessment policy
csrutil
Configure System Integrity Protection (SIP) settings
resetpassword
Password reset utility located in the Recovery Partition
File System
Command
Description
hdiutil
Used to manipulate and manage disk images.
diskutil
Used to modify, verify, & repair local disks.
Data Management
Command
Description
tmutil
Used to configure Time Machine settings in macOS
screencapture
Takes screenshot of the specified screen and saves the image at the specified location.
mdls
Used to get metadata attributes for a given file
mdutil
Used to manage metadata stores that are used by Spotlight
Settings
Command
Description
defaults
Used to modify plist files. Typically used to update preference files.
ioreg
Used to view the I/O kit registry
system_profiler
Used to generate system hardware & software reports.
plutil
Used to check syntax of property lists or covert property lists from one format to another
AssetCacheManagerUtil
Used to configure content caching settings.
open
Used to open documents from within the command line.
networksetup
Perform network configuration.
systemsetup
Used to configure machine settings in System Preferences.
launchctl
Used to manage and inspect daemons, agents, & XPC Services
Applications
Command
Description
codesign
Used to create, check, display code signatures.
pkgbuild
Used to build installer packages
productbuild
Builds a product archive
installer
System software and package installer tool
User Account Management
Command
Description
dscl
This is a command line Directory service utility that allows us to create, read, and manage Directory Service data.
sysadminctl
User account management
passwd
Change user password
login
Used to login to another user account.
Server & Device Management
Command
Description
profiles
Used to install, remove, list, or manage Configuration profiles.
serveradmin
Used to manage the services in macOS
mdmclient
Located in /usr/libexec/mdmclient it is used to manage interactions with the MDM.
asr
Apple Software restore: Used to copy volumes.
Scripting
Command
Description
osascript
Used to execute the given AppleScript
Share any commands you may know of in the comments window.
Disclaimer
The information 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 information provided Or The Use Or Other Dealings In The information.
One of the advantages with scripts is the fact that you can easily automate many tasks. Here is an article that walks you through that process.
If you come across a situation where you want to perform a set of tasks on multiple computers then scripts come in very handy.
I will be providing the Shell Script version of the task. Feel free to make changes to the scripts as required. I will try to provide an AppleScript version of the tasks a little later.
This is not the only way to implement the scripts. There may be multiple approaches towards achieving the same result. You will have to explore and examine the correct approach.
This is not a comprehensive list. The scripts should give you some ideas and act as a useful reference when you are creating your own scripts.
I have tested these scripts on macOS Catalina 10.15
The Software Is Provided “As Is”, Without Warranty Of Any Kind, Express Or Implied, Including But Not Limited To The Warranties Of Merchantability, Fitness For A Particular Purpose And Noninfringement. In No Event Shall The Authors Or Copyright Holders Be Liable For Any Claim, Damages Or Other Liability, Whether In An Action Of Contract, Tort Or Otherwise, Arising From, Out Of Or In Connection With The Software Or The Use Or Other Dealings In The Software.
WARNING
Please try these scripts on a test computer. Some of the scripts do make changes to the system. Always test before using these scripts.
Xcode templates are basically pre-created files which we use when we create new projects or project files. So every time you go through the process of creating a new project File > New > Project > iOS > Single View App you are using the Single View App template.
While most of the templates are good enough we can easily create our own templates.
Why do we need custom templates?
The templates available out of the box are good for common situations. But we find that most of the times we end up creating a lot of file in our project. Sometime we implement common design patterns and architectures on a regular basis.
In such situations creating out own custom templates will help us save a lot of time during development.
The other advantage is that this promotes a more consistent development experience in any organisation.
Now that we know what templates are and why we may need custom templates let us look at how we can create them.
Template Types
Before we go ahead and create templates let us examine what a typical template includes.
Notice that there are 2 folders already created out here. File Templates & Project Templates. Let us browse through these folders.
File Templates
These are the templates used when a developer wishes to add a new file to an existing project. Under file templates you should see different folders in there. Each folder represents a certain category of templates. For example, User Interface is one category. Select it.
You should see multiple folders under it. The screenshot above shows the View template. As we can see the template itself is a folder with multiple files inside. The template ends with an extensions xctemplate. Let us look at those files.
___FILEBASENAME___.xib
TemplateIcon.png
TemplateIcon@2x.png
TemplateInfo.plist
The first one is the XIB file which will be generated by this template. The ___FILEBASENAME___ placeholder will be replaced with an actual name when it is created.
The next 2 are simply images that will be used as icons for the template when we bring up the template wizard in Xcode.
The last one is the more important one. The TemplateInfo.plist. This is where we describe how the file creation process works. This is also where we configure options which will be presented to the user. We will look at this file in greater depth later on when we try to create our own templates.
Project Templates
These are the templates that are used when a developer decides to create a new project. Under project templates you should see different folders in there. Each folder represents a certain category of templates. For example, Application is one category. Select it.
I have the single view app template inside it. This is the most commonly used template when starting out with iOS App Development. You should see other familiar project templates. Feel free to examine the files in the folder. Let us have a look inside the Single View App template folder. You should see these items:
ContentView.swift
Main.storyboard
TemplateIcon.png
TemplateIcon@2x.png
Preview Assets.xcassets folder
TemplateInfo.plist
The first 2 files are the UI related files. One of the 2 will be selected based on the users choice between Storyboard and SwiftUI.
The next 2 are simply images that will be used as icons for the template when we bring up the template wizard in Xcode.
The Preview Assets folder is used with SwiftUI for previewing purposes.
Here too we have the TemplateInfo.plist file which configures the template options at the time of creation. We will explore this file in greater depth when we try to create our own project template.
How can we create them?
In this article we will look at creating 2 types of templates.
File Templates
Project Templates
Warning: It may be a good idea to try this out on a test computer so that you do not break anything on the computer you use everyday.
Preparation
Before we get started let us prepare the folders where we will be storing our custom templates.
Navigate to the following folder.
~/Library/Developer/Xcode/Templates/
Note, you may have to create this folder.
There should be 2 folders inside: File Templates, Project Templates. If these folders are not there then go ahead and create them.
We will be placing our templates in these folders.
This article continues from the previous article. Earlier we saw how we can make iOS Apps without using the storyboard file. In this article we will explore how to implement Autolayout programmatically. We will continue from the previous article.
The code that I will be showing in the article will not be covering all the possible cases. The point of this article is to give you an idea on how to implement the different Autolayout solutions. Feel free to play around with the code to cover all the cases & situations.
Programmatic Constraints
We have 3 options when it comes to applying constraints programmatically:
StackViews
Layout Anchors
NSLayoutConstraints class
Visual Format Language (VFL)
Handling Size Classes in code
Handling Size classes in code is fairly easy. It is a simple question of overriding the correct function. We will look at this in greater detail when we cover the topic later in the article.