This series is an excerpt from the first chapter of my upcoming book “Scripting macOS” which will teach you to use and create shell scripts on macOS.
- Part 1: First Script
- Part 2: The Script File
- Part 3: The Code
- Part 4: Running the Script
- Part 5: Lists of Commands
- Part 6: Turning it off and on again
- Part 7: Download and Install Firefox
I will publish one part every week over the summer. Enjoy!
The Code
A script file is a text file with the executable bit set.
You can set the executable bit on any file. That alone does not turn it into a working script file. To get a working script file, the contents need to have the right structure or ‘syntax.’
Our example above is close to the minimum you need to have a working script file. We will look at the contents one line at a time.
#!/bin/zsh
# Greetings
echo "Hello, World!"
Shebang
The first line in our script is:
#!/bin/zsh
The first two characters #!
form a special file signature or ‘magic number.’ They tell the shell and the system that this is not just any file, but a script file that should be interpreted.
These two characters have special names. The number sign is called the ‘hash’ and the exclamation mark is called the ‘bang.’ Together they are the ‘hashbang’ or the ‘shebang.’
The shebang characters have to be followed by the full path to the interpreter binary. There are no spaces in between the shebang characters and the interpreter path.
In this file, we have designated the zsh binary /bin/zsh
.
When you type ./hello.sh
to execute your script in Terminal, the shell will check if the file is executable. If it is executable, the shell will look at the first characters of the file. It ‘sees’ the shebang #!
characters, and determines from them that this is a script file. Then it will read the rest of the first line to determine the interpreter. It will then hand over the entire script file to that interpreter binary.
This will have the same effect as running your script like this:
> /bin/zsh hello.sh
A new zsh process starts which reads and interprets the script file.
It does not matter which shell you use as your interactive shell. The shebang determines the interpreter for the script. That means you can run bash and sh scripts from zsh and vice versa.
When you are writing your scripts against sh or bash, you will have a shebang line of #!/bin/sh
or #!/bin/bash
, respectively.
The three shells are pre-installed as part of macOS and protected by System Integrity Protection and the read-only system volume. When you install newer versions of the interpreters, such as bash v5, then that binary will be in a different location. Most commonly /usr/local/bin
.
If, for example, you have bash v5 installed as /usr/local/bin/bash
, the shebang lines in the scripts that should use bash v5 will be:
#!/usr/local/bin/bash
When you then try to run these scripts on a mac that does not have bash v5 installed (or installed in a different location) then the scripts will fail.
> ./hello.sh
zsh: ./hello.sh: bad interpreter: /usr/local/bin/bash: no such file or directory
This can also happen when you run your script on a different platform. Even though the other platform may have the bash or zsh binary installed, it could be at a different path than on macOS. And the interpreter binary may be a different version than on macOS (especially with bash) which can lead to the script being interpreted differently.
For scripts that need to work in many different environments you will often see a shebang that uses the env
command:
#!/usr/bin/env bash
The env
command will use the current user’s environment, especially the PATH
, to determine where the bash binary is and passes the script on to that. This will make your shebang more flexible, but you also give up some control as the environment will differ from system to system and from user to user.
For situations where you need control—such as management scripts—a fixed path for the shebang should be preferred, but then you also have ensure that the interpreter binary will be available at that path.
Whitespace
The line right after the shebang is empty. In shell scripts (and most other programming languages) empty lines are just ignored. Adding empty lines will make your code more readable.
Comments
The third line in our script starts with a number character or ‘hash.’
# Greetings
In shell scripts, lines that start with the hash character are ignored by the interpreter. You can and should use this to leave explanations and comments in your code.
Use comments in your code to leave explanations and notes.
echo
Command
Now, finally, we will get to the actual code. The part of the script that does something.
echo "Hello, World!"
The echo
command is one command that you rarely use in the interactive shell, but very frequently within scripts. It tells the shell to print (repeat or ‘echo’) text to the terminal’s output.
As usual in shells, the text we pass in to echo as an argument is ‘held together’ by quotes. This kind of text used in code is also called a ‘string’ because it is a ‘string of characters.’
Next: Running the Script