Please do not post a support request without first reading and following the advice in https://retropie.org.uk/forum/topic/3/read-this-first

[SCRIPT] RetroPie Shell Script Boilerplate



  • I'm starting to like creating scripts for RetroPie so I created a RetroPie Shell Script Boilerplate for myself and now I'd like to present it to you and get some feedback.

    Basically, it's a starting point with:

    • Some variables and functions that I found myself using in every script I created.
    • A config file, because I use it quite often.
    • It also includes a README.md and a CHANGELOG.md to document the script.
    • Some stuff for GitHub: a LICENSE and a CONTRIBUTING.md.

    There a lot of comments and examples on how to use the functions and how to write the documentation.
    Everything that's encapsulated in [] is editable and the comments that should be removed when the script is in 'production' are marked.

    UPDATE 1:

    • Merged #2 from @meleu .
    • Added check_dependencies().
    • Added reset_config() to use it in conjunction with set_config() and get_config().
    • Added --version (because I think everyone should use it :P)
    • Added 2 ways to find /home.
    • Added more useful global variables.

    Well, here it is:

    #!/usr/bin/env bash
    # [SCRIPT_NAME] (e.g.: script_template.sh)
    #
    # [SCRIPT_TITLE] (e.g.: RetroPie Shell Script Boilerplate)
    # [SCRIPT_DESCRIPTION] (e.g. A template for building shell scripts for RetroPie.)
    #
    # Author: [AUTHOR] (e.g. hiulit)
    # Repository: [REPO_URL] (e.g. https://github.com/hiulit/RetroPie-Shell-Script-Boilerplate)
    # License: [LICENSE] [LICENSE_URL] (e.g. MIT https://github.com/hiulit/RetroPie-Shell-Script-Boilerplate/blob/master/LICENSE)
    #
    # Requirements:
    # - RetroPie x.x.x (e.g. RetroPie 4.x.x)
    # - [PACKAGE_NAME] (e.g. libav-tools)
    
    # Globals ####################################################################
    
    # If the script is called via sudo, detect the user who called it and the homedir.
    user="$SUDO_USER"
    [[ -z "$user" ]] && user="$(id -un)"
    
    home="$(eval echo ~$user)"
    # If you really need that the script is run by root user (e.g. script called
    # from '/etc/rc.local') the approach below can work better to get the homedir
    # of the RetroPie user.
    # Comment the code above and uncomment the code below.
    #home="$(find /home -type d -name RetroPie -print -quit 2>/dev/null)"
    #home="${home%/RetroPie}"
    
    readonly RP_DIR="$home/RetroPie"
    readonly RP_CONFIG_DIR="/opt/retropie/configs"
    
    readonly SCRIPT_VERSION="0.0.0" # Use Semantinc Versioning https://semver.org/
    readonly SCRIPT_DIR="$(cd "$(dirname $0)" && pwd)"
    readonly SCRIPT_NAME="$(basename "$0")"
    readonly SCRIPT_FULL="$SCRIPT_DIR/$SCRIPT_NAME"
    #readonly SCRIPT_CFG="$SCRIPT_DIR/[CONFIG_FILE]" # Uncomment if you want/need to use a config file.
    readonly SCRIPT_TITLE="[SCRIPT_TITLE]"
    readonly SCRIPT_DESCRIPTION="[SCRIPT_DESCRIPTION]"
    #readonly SCRIPTMODULE_DIR="/opt/retropie/supplementary/[SCRIPTMODULE_NAME]" # Uncomment if you want/need to use a scriptmoodule.
    
    # Other variables that can be useful.
    #readonly DEPENDENCIES=("[PACKAGE_1]" "[PACKAGE_2]" "[PACKAGE_N]")
    #readonly ROMS_DIR="$RP_DIR/roms"
    #readonly ES_THEMES_DIR="/etc/emulationstation/themes"
    #readonly RCLOCAL="/etc/rc.local"
    #readonly GIT_REPO_URL="[REPO_URL]"
    #readonly GIT_SCRIPT_URL="[REPO_URL]/[path/to/script].sh
    
    
    # Variables ##################################################################
    
    # Add your own variables here.
    
    
    # Functions ##################################################################
    
    function is_retropie() {
        [[ -d "$RP_DIR" && -d "$home/.emulationstation" && -d "/opt/retropie" ]]
    }
    
    
    function is_sudo() {
        [[ "$(id -u)" -eq 0 ]]
    }
    
    
    # If your script has dependencies, just use the DEPENDENCIES variable on the definitions above.
    # Otherwise, leave it as is.
    function check_dependencies() {
        local pkg
        for pkg in "${DEPENDENCIES[@]}";do
            if ! dpkg-query -W -f='${Status}' "$pkg" | grep -qwo "installed"; then
                echo "ERROR: The '$pkg' package is not installed!" >&2
                echo "Would you like to install it now?"
                local options=("Yes" "No")
                local option
                select option in "${options[@]}"; do
                    case "$option" in
                        Yes)
                            if ! which apt-get > /dev/null; then
                                echo "ERROR: Can't install '$pkg' automatically. Try to install it manually." >&2
                                exit 1
                            else
                                sudo apt-get install "$pkg"
                                break
                            fi
                            ;;
                        No)
                            echo "ERROR: Can't launch the script if the '$pkg' package is not installed." >&2
                            exit 1
                            ;;
                        *)
                            echo "Invalid option. Choose a number between 1 and ${#options[@]}."
                            ;;
                    esac
                done
            fi
        done
    }
    
    
    function check_argument() {
        # This method doesn't accept arguments starting with '-'.
        if [[ -z "$2" || "$2" =~ ^- ]]; then
            echo >&2
            echo "ERROR: '$1' is missing an argument." >&2
            echo >&2
            echo "Try '$0 --help' for more info." >&2
            echo >&2
            return 1
        fi
    }
    
    
    # If you are using the config file, uncomment set_config() and get_config().
    # In addition, you can also uncomment reset_config() if you need it.
    # USAGE:
    # set_config "[KEY]" "[VALUE]" - Sets the VALUE to the KEY in $SCRIPT_CFG.
    # get_config "[KEY]" - Returns the KEY's VALUE in $SCRIPT_CFG.
    # reset_config - Resets all VALUES in $SCRIPT_CFG.
    #
    # function set_config() {
    #     sed -i "s|^\($1\s*=\s*\).*|\1\"$2\"|" "$SCRIPT_CFG"
    #     echo "\"$1\" set to \"$2\"."
    # }
    #
    #
    # function get_config() {
    #     local config
    #     config="$(grep -Po "(?<=^$1 = ).*" "$SCRIPT_CFG")"
    #     config="${config%\"}"
    #     config="${config#\"}"
    #     echo "$config"
    # }
    #
    #
    # function reset_config() {
    #     while read line; do
    #         set_config "$line" ""
    #     done < <(grep -Po ".*?(?=\ = )" "$SCRIPT_CFG")
    # }
    
    
    function usage() {
        echo
        echo "USAGE: $0 [OPTIONS]" # Add 'sudo' before '$0' if the script needs to be run under sudo (e.g. USAGE: sudo $0 [OPTIONS]). Don't change [OPTIONS]! Remember to remove this comment.
        echo
        echo "Use '$0 --help' to see all the options." # Add 'sudo' before '$0' if the script needs to be run under sudo (e.g. Use 'sudo $0 --help' ...). Remember to remove this comment.
    }
    
    # Add your own functions here.
    
    # You can add as many options as you want.
    # To add a new option -> Copy and paste from '#H -[O], --[OPTION] ...' until ';;' and make the desired changes.
    # If you want to align the descriptions of the options, just play with adding/removing spaces/tabs :P
    function get_options() {
        if [[ -z "$1" ]]; then
            usage
            exit 0
        else
            case "$1" in
    #H -h, --help                   Print the help message and exit.
                -h|--help)
                    echo
                    echo "$SCRIPT_TITLE"
                    for ((i=1; i<="${#SCRIPT_TITLE}"; i+=1)); do [[ -n "$dashes" ]] && dashes+="-" || dashes="-"; done && echo "$dashes"
                    echo "$SCRIPT_DESCRIPTION"
                    echo
                    echo "USAGE: $0 [OPTIONS]" # Add 'sudo' before '$0' if the script needs to be run under sudo (e.g. USAGE: sudo $0 [OPTIONS]). Don't change [OPTIONS]! Remember to remove this comment.
                    echo
                    echo "OPTIONS:"
                    echo
                    sed '/^#H /!d; s/^#H //' "$0"
                    echo
                    exit 0
                    ;;
    #H -v, --version                Show script version.
                -v|--version)
                    echo "$SCRIPT_VERSION"
                    ;;
    #H -[O], --[OPTION] (e.g '-v, --version')       [OPTION_DESCRIPTION] (e.g. Show script version.).
                -[O]|--[OPTION])
                    # If the option has arguments, uncomment the code below.
                    # check_argument "$1" "$2" || exit 1
                    # shift
    
                    # Add the functions for this options here.
                    ;;
                *)
                    echo "ERROR: invalid option '$1'" >&2
                    exit 2
                    ;;
            esac
        fi
    }
    
    function main() {
        # If you need to check if sudo is used, uncomment the code below.
        # Remember to add 'sudo' in 'usage' and 'help'.
        # if ! is_sudo; then
        #     echo "ERROR: Script must be run under sudo."
        #     usage
        #     exit 1
        # fi
    
        if ! is_retropie; then
            echo "ERROR: RetroPie is not installed. Aborting ..." >&2
            exit 1
        fi
    
        check_dependencies
    
        get_options "$@"
    }
    
    main "$@"
    

    and the config file:

    # Settings for [SCRIPT_TITLE] (e.g. RetroPie Shell Script Boilerplate)
    
    # Add your own [key = "value"] (e.g. path_to_whatever = "/path/to/whatever")
    # [KEY] WITHOUT quotes.
    # [VALUE] WITH quotes.
    # There MUST be 1 space before and after '='.
    # To indicate that a [KEY] has NO [VALUE] or is NOT SET, just leave the quotes, like this: "".
    
    # Description of the [key = "value"] (e.g. # Set path to whatever).
    [KEY] = "[VALUE]"
    
    # Add your own [key = "value"]
    

    I would like to especially thank @meleu for helping me with other projects I've worked on.

    I hope that somebody will find this useful and also I'd like to hear some feedback (criticism, suggestions, etc.) so I can improve it and in addition improve my skills developing scripts for RetroPie :D



  • @hiulit If I ever will use config files than this would be a good starting point ;) Usually I parse arguments via command parameters as all of my scripts are very tight and need no configuration.



  • @hiulit OMFG! I was thinking about making the same thing but was a bit unmotivated to start it from scratch. Now you started it... such a relief! :D

    After writing all those scripts here and there for the RetroPie community I think I have something to share. I hope to find some time to contribute on your repo.

    Thanks for bringing this up!



  • @cyperghost I'm glad you like! :D
    I like to use config files because it makes the script more 'user-friendly' in my opinion. Feel free to contribute to the repo if you find a better way of handling config files (setting and getting values)



  • @meleu Awesome! I'm already waiting for your contributions! And any other person who'd like to contribute too.
    Together we can come up with a nice and useful boilerplate :)



  • @hiulit I think the comments are kinda wordy. Would you mind if I change the style, trying to reduce the text a bit?



  • @meleu Yeah, sure, no problem! Go for it!



  • @hiulit PR submitted.



  • @meleu Thanks! PR commented ;)



  • I've updated the RetroPie Shell Script Boilerplate:

    UPDATE 1:

    • Merged #2 from @meleu .
    • Added check_dependencies().
    • Added reset_config() to use it in conjunction with set_config() and get_config().
    • Added --version (because I think everyone should use it :P)
    • Added 2 ways to find /home.
    • Added more useful global variables.

    These changes are also reflected in the first comment.

    Coming soon... dialog functions!! Again, thanks to @meleu ;)



  • @hiulit Some additional functions

    # This will determine of savestate directory = config
    # This is part of hiuilits Boilerplate script, with small modification
    # if '~' is detected then expand full homepath
    function get_config() {
        local config
        config="$(grep -Po "(?<=^$1 = ).*" "$CONFIG_FILE")"
        config="${config%\"}"
        config="${config#\"}"
        # [[ ${config:0:1} = "~" ]] && config="${config#??}" && config=~/"$config" # Expand homepath
        # [[ -z ${config##*/} ]] && config="${config%?}" # Sanitize pathes if last character is a /
        # [[ ${config:0:1} != "/" ]] && config="annother value because it is likely no path"
        echo "$config"
    }
    
    # This will determine which script is curently running
    # Is it 'runcommand-onend.sh' or 'runcommand-onstart.sh'
    # It will extract 'end' or 'start' ... Usefull if you need to know which runcommand called the script
    function get_runcommand() {
       local i
       local file_array=("runcommand-onend.sh" "runcommand-onstart.sh")
       for i in "${file_array[@]}"
       do
          [[ $(pgrep -f "$i") ]] && i="${i#*-on}" && echo "${i%.*}"
       done
    }
    
    # Determining file ages
    function file_age() {
        echo "$(date +%s -r "$1")"
    }
    


  • @cyperghost Hey, thanks! I'll take a look at them ;)



Contributions to the project are always appreciated, so if you would like to support us with a donation you can do so here.

Hosting provided by Mythic-Beasts. See the Hosting Information page for more information.