Skip to content

RetroPie-Setup Script Code Style Guide Snippets

Introduction

This document describes the style guide that has to be used for the development of the RetroPie-Setup Script sources. This guide is adapted from the Google Shell Style Guide.

Features and Bugs

Command Substitution

Use $(command) instead of backticks.

Nested backticks require escaping the inner ones with \. The $(command) format doesn't change when nested and is easier to read.

Example:

# This is preferred:
var="$(command "$(command1)")"

# This is not:
var="`command \`command1\``"

Test and Square Braces

[[ ... ]] is preferred over [, test and /usr/bin/[.

[[ ... ]] reduces errors as no pathname expansion or word splitting takes place between [[ and ]] and [[ ... ]] allows for regular expression matching where [ ... ] does not.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at http://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
    echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
    echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
    echo "Match"
fi

Testing Strings

Use quotes rather than filler characters where possible.

Bash is smart enough to deal with an empty string in a test. So, given that the code is much easier to read, use tests for empty/non-empty strings or empty strings rather than filler characters.

# Do this:
if [[ "${my_var}" == "some_string" ]]; then
    do_something
fi

# -z (string length is zero) and -n (string length is not zero) are
# preferred over testing for an empty string
if [[ -z "${my_var}" ]]; then
    do_something
fi

# This is OK (ensure quotes on the empty side), but not preferred:
if [[ "${my_var}" == "" ]]; then
    do_something
fi

# Not this:
if [[ "${my_var}X" == "some_stringX" ]]; then
    do_something
fi

To avoid confusion about what you're testing for, explicitly use -z or -n.

# Use this
if [[ -n "${my_var}" ]]; then
    do_something
fi

# Instead of this as errors can occur if ${my_var} expands to a test
# flag
if [[ "${my_var}" ]]; then
    do_something
fi

Naming Conventions

Function Names

Functions should start with a non-capital letter and have a capital letter for each new word. No underscores. Parentheses are required after the function name.

The only exception to this rule is when defining interface functions for the RetroPie-Setup Script modules.

For example:

function depends_retroarch() {  % OK, module interface function
    ...
}

function sources_retroarch() {  % OK, module interface function
    ...
}

function build_retroarch() {  % OK, module interface function
    ...
}

function install_retroarch() {  % OK, module interface function
    ...
}

function ensureSystemretroconfig {  % OK
    ...
}

function ensure_another_Systemretroconfig {  % NOT OK, no module interface function
    ...
}

function configure_retroarch() {  % OK, module interface function
    ...
}

Variable Names

The names of variables are generally all lowercase and can use underscores if needed.

Constants and Environment Variable Names

All caps, separated with underscores, declared at the top of the file. Constants and anything exported to the environment should be capitalized.

# Constant
readonly PATH_TO_FILES='/some/path'

# Both constant and environment
declare -xr ORACLE_SID='PROD'

Some things become constant at their first setting (for example, via getopts). Thus, it's OK to set a constant in getopts or based on a condition, but it should be made readonly immediately afterwards. Note that declare doesn't operate on global variables within functions, so readonly or export is recommended instead.

VERBOSE='false'
while getopts 'v' flag; do
    case "${flag}" in
        v) VERBOSE='true' ;;
    esac
done
readonly VERBOSE

Read-only variables

Use readonly or declare -r to ensure they're read only.

As globals are widely used in shell, it's important to catch errors when working with them. When you declare a variable that is meant to be read-only, make this explicit.

zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
    error_message
else
    readonly zip_version
fi

Use Local Variables

Declare function-specific variables with local. Declaration and assignment should be on different lines. Ensure that local variables are only seen inside a function and its children by using local when declaring them. This avoids polluting the global name space and inadvertently setting variables that may have significance outside the function.

Declaration and assignment must be separate statements when the assignment value is provided by a command substitution, as the local builtin does not propagate the exit code from the command substitution.

my_func2() {
    local name="$1"

    # Separate lines for declaration and assignment:
    local my_var
    my_var="$(my_func)" || return

    # DO NOT do this: $? contains the exit code of 'local', not my_func
    local my_var="$(my_func)"
    [[ $? -eq 0 ]] || return

    ...
}

Comments

Function Comments

Any function that is not both obvious and short must be commented. Any function in a library must be commented regardless of length or complexity.

It should be possible for someone else to learn how to use your program or to use a function in your library by reading the comments (and self-help, if provided) without reading the code.

All function comments should contain:

  • Description of the function
  • Global variables used and modified
  • Arguments taken
  • Returned values other than the default exit status of the last command run

Example:

#!/bin/bash
#
# Perform hot backups of Oracle databases.

export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin'

#######################################
# Cleanup files from the backup dir
# Globals:
#   BACKUP_DIR
#   ORACLE_SID
# Arguments:
#   None
# Returns:
#   None
#######################################
cleanup() {
    ...
}

Implementation Comments

Comment tricky, non-obvious, interesting or important parts of your code.

Formatting

Indentation

Indent 4 spaces. No tabs.

Use blank lines between blocks to improve readability. Indentation is four spaces. Whatever you do, don't use tabs. For existing files, stay faithful to the existing indentation.

Pipelines

Pipelines should be split one per line if they don't all fit on one line.

If a pipeline all fits on one line, it should be on one line.

If not, it should be split at one pipe segment per line with the pipe on the newline and a 2 space indent for the next section of the pipe. This applies to a chain of commands combined using | as well as to logical compounds using || and &&.

Example:

# All fits on one line
command1 | command2

# Long commands
command1 \
  | command2 \
  | command3 \
  | command4

Braces

The open parenthesis is always on the same line as the function name. The closing parenthesis is always on a separate line.

For example:

function ensureSystemretroconfig {  % OK
    ...
}

function ensureSystemretroconfig
{  % NOT OK
    ...
}

Conclusion

Use common sense and BE CONSISTENT.