COMP(2041|9044) Shell Style Guide
Contents
Shell Language & Shell Files
Language Features
Shell scripts must be written in POSIX compliant shell syntax.
The POSIX definition of the "Shell Command Language" can be found in IEEE Std 1003.1-2017.
for simplicity, while not entirely true, all shell features available in the dash(1) shell are considered to be POSIX compliant.
Therefor all shell scripts must be able to execute using the dash(1) shell.
Shell scripts must not contain shell feature from bash(1), zsh(1), or other shells.
Language Features
The goal if this course is to teach you how to write shell scripts that are portable.
This means that we want your shell scripts to be able to run on as many platforms as possible with minimum to no modification.
In order to achieve this we want to make sure that your shell scripts are written in the shared subset other shell languages.
This is what POSIX provides.
Other shell languages like bash(1) and zsh(1) implement extensions on top of POSIX.
Writing shell scripts that do not rely on these extensions allows the scripts to be used in both bash(1) and zsh(1) as well as other shells.
File Names
Unless otherwise stated, the names of shell scripts must end with a .sh file extension.
File Names
Some activities might ask for a different (or no) file extension.
If this is not explicitly stated, the file extension must be .sh.
File Names
hello.sh program.sh isPrime.sh
File Names
hello.shell program.dash isPrime
Hashbang
Shell scripts must start with a Hashbang line.
Hashbang
Sometimes also called a shebang line, the hashbang line is used to specify the shell interpreter to use.
A Hashbang line is a line that starts with a #! and contains the path to the shell interpreter.
As we are using the dash(1) shell, we need to specify the path to the dash(1) interpreter.
The Hashbang can directly specify the path to the dash(1) interpreter
Or it can specify the path to the env(1) utility which will then run the dash(1) interpreter.Using the env(1) utility is recommended.
Specifying sh(1) as the interpreter might run dash(1) or bash(1), depending on the platform, and thus is not allowed.
The Hashbang characters #! must be the first two characters in the file.
The Hashbang is not valid if there is whitespace before the #! characters.The Hashbang is valid if there is whitespace after the #! characters and before the path.
This is recommended as it is easier to read the path.
Hashbang
#! /usr/bin/env dash <- recommended
#!/bin/dash <- also acceptable
Hashbang
#!/usr/bin/env bash <- not allowed, as it specifies the bash interpreter
#! /bin/sh <- not allowed, as it is ambiguous what shell will be used
Permissions
Shell scripts must have read and execute permissions enabled for all users.
Permissions
In order to execute a shell script with
./file.sh
, the file must have read and execute permissions enabled.To enable read and execute permissions, use the
chmod
command:chmod a+rx file.shOr:
chmod 755 file.sh
Without the execute permission, the file can still be executed, with:
dash file.shBut the read permission is still required.
Permissions
$ ls -l isPrime.sh -rwxr-xr-x 1 cs2041 cs2041 2.2K May 30 02:37 isPrime.sh <- correct permissions
Permissions
$ ls -l isEven.sh isOdd.sh -rw-r--r-- 1 cs2041 cs2041 2.2K May 30 02:37 isEven.sh <- missing execute permission -rwxr-x--- 1 cs2041 cs2041 2.2K May 30 02:37 isOdd.sh <- missing other permissions
Recommended Code Layout
Header Comment
All programs must start with a header comment.
Header Comment
The header comment must be at the top of the file,
immediately after the hashbang line,
with a single empty line between the hashbang line and the header comment.There must be a single empty line between the header comment and the first line of your program.
The header comment should contain at least:
- The name of the activity
- The name of the file
- The name, zID, and email address of the author.
- The date it was written.
- A description of what the program does.
The preferred style for a header comment is:
# ACTIVITY-NAME-HERE # FILE-NAME-HERE # # This program was written by YOUR-NAME-HERE (YOUR-ZID-HERE) <YOUR-EMAIL-HERE> # on DATE-HERE # # DESCRIPTION-HEREFor programs that are being updated/maintained over a long period of time, the header comment should also contain a changelog/updatelog.
# ACTIVITY-NAME-HERE # FILE-NAME-HERE # # DESCRIPTION-HERE # # DATE-HERE # YOUR-NAME-HERE (YOUR-ZID-HERE) <YOUR-EMAIL-HERE> # - CHANGE-HERE # # DATE-HERE # YOUR-NAME-HERE (YOUR-ZID-HERE) <YOUR-EMAIL-HERE> # - CHANGE-HERE
Header Comment
#! /usr/bin/env dash # COMP2041/9044 Lab06 - A Shell Script that Prints Itself # quine.sh # # This program was written by Dylan Brotherston (z5115658) <d.brotherston@unsw.edu.au> # on May 30, 2022 # # This program is a quine. # A program prints its own source code (minus comments). b=\' c=\\ a='echo b=$c$b c=$c$c a=$b$a$b; echo $a' echo b=$c$b c=$c$c a=$b$a$b; echo $a
#! /usr/bin/env dash # COMP2041/9044 Assignment 1 - My Big Shell Project # meaning_of_life.sh # # This program calculates and prints the meaning of life. # # 2024-02-05 # Dylan Brotherston (z5115658) <d.brotherston@unsw.edu.au> # - Update the meaning of life to 42 # # 2023-09-17 # Dylan Brotherston (z5115658) <d.brotherston@unsw.edu.au> # - Initial version echo 42
Function Comments
Any function that is not both obvious and short must be commented.
Function Comments
Function Comments
Implementation Comments
All programs must have comments.
Implementation Comments
Where your code may be unclear to a reader, or require further explanation,
you should leave brief comments describing the purpose of the code you have written.Make sure that Comments are descriptive of why your code is doing what it does.
And not just stating what the code does.Comments should be on the line before the code they are describing - rather than on the same line
This helps to improve readability.Comments should be indented to the same level as the code they are describing.
Very small or simple programs might not need comments.
But most programs should have some commenting comments.
Implementation Comments
#! /usr/bin/env dash # COMP2041/9044 Lab06 - Hello World Again # hello.sh # # This program was written by Dylan Brotherston (z5115658)# on May 30, 2022 # # This program simply says hello. # for each name in the command line for name in $@; do # Use grep to match a name that looks like a zID if grep -E 'z[0-9]{7}'; then # If the name is a zID, then use `acc` to find the corresponding name # This allows us to say hello with the person's full name even if they only give use a zID # This means that even on a CSE server `./hello.sh $(whoami)` will work echo "Hello, $(acc format='$NAME')" else # If the name is not a zID, then just say hello to the name echo "Hello, $name!" fi done
Command Line Arguments
When using a command with options, you should use long options whenever possible.
Command Line Arguments
Short options are great when using the command line interactivly, but not when writing shell scripts.
Short options are not descriptive, and when using uncommon commands and/or options can be confusing to readers.
Long options are self descriptive, and make it easier to understand what the program is doing.
Not all commands support long options.
If you are using a command that does not support long options, you should use short options.When using short options, you should use the combining syntax whenever possible
When using short options, you should add extra comments to explain what the option does.
Command Line Arguments
jq --compact-output --slurp # non-standard command so use long options instead of the equivalent `-c -s` grep -E # common command, and common option, so using the short options is fine cut -d":" -f"1,2" --only-delimited # `--only-delimited` can be written as `-s` but is uncommon so use long option mutt -nRy # `mutt` does not support long options, so use short options instead # use the combining syntax to avoid writing `-n -R -y`
Controle Flow - Keywords
The keywords used to define a controle flow block should be on the same line as controle flow keyword
Controle Flow - Keywords
The keywords
then
, anddo
, should be on the same line asif
,while
,until
, andfor
.The code structure of
; then
and; do
act the same as an open braces in C.The code structure of
fi
,elif
anddone
act the same as an close braces in CThe code structure of
else
, acts as both an open braces and close braces in C.The alternative syntax with
then
anddo
on their own line
is allowed but not recommended
fi
,elif
,done
, andelse
must be inedented to the same level as theif
,while
,until
, orfor
.If the alternative syntax is used then
then
, anddo
must be inedented to the same level as theif
,while
,until
, orfor
.Otherwise, they should be separated from the condition by a single semicolon and a single space.
Controle Flow - Keywords
if [ $a -gt $b ]; then echo "a is greater than b" elif [ $a -lt $b ]; then echo "a is less than b" else echo "a is equal to b" fi while [ $a -lt $b ]; do isPrime $a a=$((a-1)) done until [ $a -ge $b ]; do isPrime $a a=$((a-1)) done for file in $files; do rm "$files" done
Controle Flow - Keywords
# OK, but not recommended if [ $a -gt $b ] then echo "a is greater than b" elif [ $a -lt $b ] then echo "a is less than b" else echo "a is equal to b" fi # Too many spaces after the semicolon while [ $a -lt $b ]; do isPrime $a a=$((a-1)) done # `do` and `done` should be indented in line with `until` until [ $a -ge $b ] do isPrime $a a=$((a-1)) done # Too many empty lines before the `do` for file in $files do rm "$files" done
Controle Flow - Indentation
The body of controle flow statements must be inedented.
Controle Flow - Indentation
while
loops,until
loops,for
loops,if
statments, must be inedented.Shell doesn't use braces for controle flow like C.
Instead, shell uses matching keywords to indicate the start and end of controle flow.
Everything between the start and end keyword must be inedented.
Between any pair of keywords, the indentation level should increase by one level.
It is recommended that you use 2 spaces as one level of indentation.
Other acceptable indentation levels are: 4 spaces or 1 tab
Whatever indentation level you use, it should always be consistent throughout your code.
Alaways indent be one level, and alawys use the same number of spaces or tabs to represent one level.
Never use a mixture of spaces and tabs.
Controle Flow - Indentation
while [ "$i" -lt 10 ]; do echo "$i" i=$((i+1)) n=$((n+i)) done for arg in $@; do echo "$arg" done if [ ! -e "$file_name" ]; then echo "File does not exist" fi
Controle Flow - Indentation
# inconsistent indentation while [ "$i" -lt 10 ]; do echo "$i" i=$((i+1)) n=$((n+i)) done # no indentation for arg in $@; do echo "$arg" done # way too much indentation if [ ! -e "$file_name" ]; then echo "File does not exist" fi # mixing tabs and spaces if [ "$1" = "$2" ]; then echo "arg[1] and arg[2] are equal" echo "that's a little redundant don't you think?" fi
Controle Flow - case
The body and alternatives of case statements must be inedented.
Controle Flow - case
case
statements are used to select a single case from a list of cases.
Controle Flow - case
case $command in login) token="$(login_user "$user" "$password")" if [ -z "$token" ]; then echo "Login failed" exit 1 else echo "Login successful" fi ;; logout) logout_user "$token" ;; stats) cat /proc/self/stat cat /proc/self/status cat /proc/self/sched cat /proc/self/schedstat cat /proc/self/limits cat /proc/self/io ;; cmd) cat /proc/self/cmdline cat /proc/self/environ ;; *) echo "Unknown command: $command" >&2 exit 1 ;; esac case $level in 0) echo "Tutorial level" ;; [1-9]) echo "Level $level" ;; 10) echo "Last level" ;; *) echo "Unknown level: $level" >&2 exit 1 ;; esac
Spaces
Use a space after keywords such as:
if, while, for, returnUse a space on each side of binary operators such as
= + - < > * / % <= >= == !=Do not use space after prefix unary operators such as
& * !Do not use space before postfix unary operators such as
++ --Do not use space on each side of member access operators such as. ->
Spaces
// This is correct: if (a + b > c) { i++; curr = curr->next; } // This is *not* correct: if(a+b>c) { i ++; curr = curr -> next; }
Vertical Whitespace
Use vertical whitespace (blank lines between code) occasionally to indicate where the related parts of your code are.
- Using no blank lines between lines of code indicates the lines should be understood together, or are closely related.
- Using exactly one blank line indicates two sections of code are distinct -- they split your code into "paragraphs".
- Using exactly two blank line indicates two sections of code are entirely unrelated.
- Using more than two blank lines is (almost) never appropriate.
The exact use of vertical whitespace is subjective, and your tutor will be able to give you feedback on your style throughout the term. Vertical whitespace is like chocolate -- some of it significantly improves your life, but too much can also be it's own problem; and used in the wrong place it can be very confusing.
Vertical Whitespace
// The two variables here are closely related, and should not be seperated. int x_position = 0; int y_position = 0; // The two if statements here are seperate ideas, and could be seperated. if (x_position > 0) { printf("X position is positive.") } if (instruction == INCREASE_X_POSITION) { x_position += 1; } // We have used two empty lines here to indicate the the topic has changed. prinf("Please choose an item to use...\n")
Statements
IO redirection
Pipelines
Line Width
Whenever possible keep lines under 80 characters.
Line Width
Break long lines up to keep them under 80 characters,
unless keeping it as a longer line is significantly more readable.At 120 characters, serious effort should be made to split or shorten the line.