#!/bin/bash # http://en.wikipedia.org/wiki/INI_file: # * Provides a good explanation of the ini format - use this for docs * # * INI's have 'sections' and 'properties'. Properties have key = value format * # # Case insensitivity: Add a case sensitive option # Comments: Allow ; and # for comments. Must be on their own line # Blank lines: Allow blank lines always # Duplicate names: Duplicate property values overwrite previous values. # Provide an option to abort/error is duplicate is found? # Add option to merge duplicates separated by octal byte (\036 ??) # Duplicate sections are merged. Option to error if dup. # Escape chars: Handled by bash directly. Allow \ to continue a line. # Global properties: Support. Add to a GLOBAL section? # Hierarchy: No hierarchy support. Each section is own section. # Name/value delim: Use = by default. Allow : via option? # Quoted values: Allow values to be within " and ' to keep literal formatting. # Whitespace: Whitespace around section labels and []s is removed. # Whitespace within section labels is kept / translated. # Whitespace around property names is removed. # Whitespace within property names is kept as is (spaces squashed - option to override). # Property values have whitespace between = and data removed. # Property values are kept as is (no squashing) # Ordering: GLOBAL section must be at the top, sections continue until next section or EOF. # http://www.regular-expressions.info/posixbrackets.html # http://ajdiaz.wordpress.com/2008/02/09/bash-ini-parser/ # https://github.com/rudimeier/bash_ini_parser/blob/ff9d46a5503bf41b3344af85447e28cbaf95350e/read_ini.sh # http://tldp.org/LDP/abs/html/ # Specs: # [section] Can be upper/lower/mixed case (set by options) # Can only include: '-+_. [:alnum:]' # Any single or consecutive occurance of '-+_. ' are converted to a *single* _ # eg: [foo -+_. bar] becomes [foo_bar] ?? # Any leading/trailing spaces/tabs between the []s and name will be removed. # Notes: # * To make env vars available to subsequent programs, use -x|--export. parser_help() { #........1.........2.........3.........4.........5.........6.........7.........8.........9.........0.........1.........2.........3.........4.........5 cat < Parse an ini file into environment variables which can be used natively in Bash. Options: -e , --envvar= The prefix of the environment variables set by the parser. The default is 'INI'. -d , --envdelim= The character(s) to use as a deliminator between the environment variable and the section name. This is used when creating the environment variables which hold options belonging to a particular section of the ini file. Only letters, numbers and underscores (_) may be used. To use no deliminator at all, use -d '' or --envdelim=''. The default deliminator is a single underscore '_' ??? -i, --implied-boolean Options usually require a parameter (after the =) in order to be set. With this option, any option without a parameter contained in the ini file if assumed to be a boolean 'true' and set accordingly. Likewise, any option preceeded with 'no_' (eg: no_foo) will set the option 'foo' to boolean 'false'. Cannot be used with --no-boolean. -c, --case-sensitive Be case sensitive with section names and properties. Section names and property names will be used as is - no translation. -g, --global-name INI files can contain an optional implied "global" section - where there are property names/values before any [section] header. This option specified what section name the implied "global" section should be given in the environment variables which are set. The default is 'GLOBAL'. -l, --lowercase Usually, environment variables are converted to all uppercase before being set. This option forces all environment variables to be converted to lowercase instead. Note: This only effects the environment variable set with -e, and the section names read from the ini file. Options are ?????????????????????????????????????? -x, --export Export environment variables. --no-boolean Don't parse 'yes', 'true', 'on', 'no', 'false', 'off' into the corresponding boolean values, and set the options strictly as is. Incompatible with -i. --no-squash Do not squash multiple consecutive occurances of punctuation characters into a single _ when parsing section names and options. With this option 'foo.-_bar' would become 'foo___bar' rather than 'foo_bar'. --no-duplicates If a duplicate section name or option name is found, report error and stop. Usually sections with the same name will have their options merged, and duplicate option values will overwrite previous ones. --test Test/validate the ini file by running it through the parser. Testing the ini file will report any problems or syntax errors in the file, but will not set up the environment variables as would happen in normal parsing. Any parse errors are reported to stderr. When combined with the --debug option, every detail of the parsing process is reported to stderr. --debug Show full details of the ini file parsing process. Detail is written to stderr. Unless --test is used with this option, the parser will still set up the environment as would happen normally, -h, --help Show (this) help. -v, --version Show version and copyright information. EOF } parser_version() { #........1.........2.........3.........4.........5.........6.........7.........8.........9.........0.........1.........2.........3.........4.........5 cat <<-EOF Bash INI file parser v0.1.0. Copyright (C) 2019 Darren 'Tadgy' Austin . Licensed under the terms of the GNU General Public Licence version 3. This program comes with ABSOLUTELY NO WARRANTY. For details and a full copy of the license terms, see: . This is free software - you can modify and redistribute it under the terms of the GPL v3. EOF } parse_ini() { # Bash v4.1+ is required. if [[ -z "${BASH_VERSINFO[0]}" ]] || ((BASH_VERSINFO[0] < 4)) || ((BASH_VERSINFO[1] < 1)); then echo "${0##*/}: minimum of bash v4.1 required" >&2 return 1 fi # Set defaults. local PARSER_ENV_PREFIX="INI" local PARSER_ENV_DELIM="_" local PARSER_ALPHA_CLASS="[:alnum:]" # All alphanumeric characters. local PARSER_PUNCT_CLASS="-+_. !\"£\$%^&*()="$'\t' # Characters that are converted to _ in [section] names. !"£$%^&*()-_=+{}'@#~\|,<.>/? # TEMP: local PARSER_INIFILE="test.ini" parser_getopts() { while [ ! -z "$1" ]; do case "$1" in -h|-help|--help) parser_help return 0 ;; -v|-version|--version) parser_version return 0 ;; --) # Stop option processing. break ;; -*|--*) echo "${0##*/}: invalid option: $1" return 1 ;; esac done } # Parse arguments # parser_getopts "$@" || return 1 # File accessability checks if [ ! -e "$PARSER_INIFILE" ]; then echo "${0##*/}: $PARSER_INIFILE: no such file" return 1 elif [ ! -f "$PARSER_INIFILE" ]; then echo "${0##*/}: $PARSER_INIFILE: not a regular file" return 1 elif [ ! -r "$PARSER_INIFILE" ]; then echo "${0##*/}: $PARSER_INIFILE: permission denied" return 1 fi # Open the ini file for reading if ! exec {PARSER_INIFD}<"$PARSER_INIFILE"; then echo "${0##*/}: $PARSER_INIFILE: failed to open" return 1 fi # FIXME: Need to handle this properly: shopt -s extglob # Parse the ini file local IFS=$'\n' PARSER_READLINE PARSER_READLINENO=0 REPLY # local PARSER_CURSEC while :; do while :; do # Read a line of input from the file descriptor read -r -u $PARSER_INIFD || break ((PARSER_READLINENO++)) # Skip any blank/empty or comment lines [[ "$REPLY" =~ ^[[:blank:]]*([#;].*)*$ ]] && continue for ((I = 1; I < ${#REPLY}; I++)); do if [[ "${REPLY: -$I:1}" =~ [[:space:]] ]]; then continue fi done # If line ends in \, save the line and read next. # if [[ "${REPLY:-1:1}" =~ "\" ]] done printf "<%s>\n" "$PARSER_READLINE" exit # Strip any leading whitespace from the line # Not required here, # PARSER_READLINE="${PARSER_READLINE/#+([[:space:]])/}" # Is this a section header? if [ "${PARSER_READLINE:0:1}" = "[" ]; then if [[ "$PARSER_READLINE" =~ ^\[[$PARSER_PUNCT_CLASS$PARSER_ALPHA_CLASS]+\]$ ]]; then # Strip the []s and any leading/trailing whitespace. # FIXME: Allow leaving the leading/trailing whitespace in place with option? PARSER_READLINE="${PARSER_READLINE/#\[*([[:space:]])/}" PARSER_READLINE="${PARSER_READLINE/%*([[:space:]])\]/}" # FIXME: To convert single/consecutive punct_class into a single _ : PARSER_READLINE="${PARSER_READLINE//+([$PARSER_PUNCT_CLASS])/_}" # FIXME: To convert ALL occurances of punct_class into _ : # PARSER_READLINE="${PARSER_READLINE//@([$PARSER_PUNCT_CLASS])/_}" # FIXME: To convert single/consecutive punct_class into a single _ except for multiple _s already in line # PARSER_READLINE="${PARSER_READLINE//+([${PARSER_PUNCT_CLASS/_//}])/_}" # FIXME: To convert section name to uppercase: PARSER_READLINE="${PARSER_READLINE^^}" # FIXME: To convert section name to lowercase: # PARSER_READLINE="${PARSER_READLINE,,}" # Declare the associative array. # FIXME: If doing validation only, don't declare here. PARSER_CURSEC="$PARSER_READLINE" declare -g -A $PARSER_ENV_PREFIX$PARSER_READLINE set | grep $PARSER_ENV_PREFIX$PARSER_READLINE else echo "${0##*/}: $PARSER_INIFILE:$PARSER_READLINENO: invalid section name or format" # FIXME: If doing validation only, continue to process - with a flag indicating every option in this # section will be ignored if (-?) option is set. # If (-?) option, set flag to skip all further options until reaching next section marker. return 1 fi else # echo "Not header: $PARSER_READLINENO: $PARSER_READLINE" true fi # if first non-whitespace char after the first = is " or ', check the last non-whitespace char on the line. # if that character is a matching " or ', skip to normal processing. # if that character doesn't match the opening " or ', go to continued line processing # else (no opening " or ') check the last non-whitespace char on the line; if its a \ (line continuation marker) # go to continued line processing # fi # Continued line processing # Notes: If within a " or ' block, keep whitespace as entered - don't strip from begining of line. # If here from a continueation marker, remove leading whitespace. # Will need a flag to show if we're looking for an ending " or ' # Normal processing: # Escape chars? # Close file descriptor for ini file # clean up the environment # IFS=$INI_SAVED_IFS # Remove any variables begining INI_ done } parse_ini