LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Shell script braces and variables (https://www.linuxquestions.org/questions/programming-9/shell-script-braces-and-variables-826076/)

Woodsman 08-13-2010 03:39 PM

Shell script braces and variables
 
Been scratching my head with braces and variables.

I have a text file named shell-colors that defines escape sequences and colors:

ESC="\033["

BOLDRED="${ESC}01;31m"
BOLDGREEN="${ESC}01;32m"
BOLDYELLOW="${ESC}01;33m"
BOLDBLUE="${ESC}01;34m"
BOLDMAGENTA="${ESC}01;35m"
BOLDCYAN="${ESC}01;36m"
BOLDWHITE="${ESC}01;37m"
COLOR_RESET="${ESC}00m"

I source this file in interactive shell scripts.

When I want to add colors in a script I use braces to define the color variable:

${BOLDYELLOW}This is a warning.${COLOR_RESET}

Works greats.

I want to create a subroutine in a script where I can send messages but vary the color. For example:

MSG="Some text."
Color_Message BOLDYELLOW

Color_Message ()
{
SOMECOLOR=$1
${SOMECOLOR}$MSG${COLOR_RESET}
}

When I use, say, ${BOLDYELLOW} directly I get the color but when I pass the text string BOLDYELLOW I can't reformat that string into the variable ${BOLDYELLOW}. That is,

${BOLDYELLOW}$MSG${COLOR_RESET}

works but

${SOMECOLORSTRING}$MSG${COLOR_RESET}

does not work.

How do I reformat the string as a variable?

Thanks much.

cjcox 08-13-2010 05:44 PM

you'd use:

Color_Message ${BOLDYELLOW}

If you really want to use "names" rather than the values, you can look at doing an extra evaluation (using eval, for example).

e.g.

Code:

Color_Message () {
  colorname="$1"
  eval colorescape='\$${colorname}'
  ${colorescape}${MSG}${COLOR_RESET}
}

MSG=whatever
Color_Message BOLDYELLOW

Now... none of this is portable AT ALL... and you really need to look into making terminfo calls to get the escapes rather than hard coding them. man tput and man terminfo and man infocmp and possibly man tic

Woodsman 08-13-2010 08:03 PM

Quote:

you'd use:

Color_Message ${BOLDYELLOW}
So simple. I guessed I might have been missing the obvious. :) Thank you!

konsolebox 08-13-2010 08:48 PM

I think that can also be done like this:
Code:

# usage: color_message <message> <colorname>

color_message () {
    MSG=1 COLORNAME=$2
    eval "COLORSPACE=\$$COLORNAME"
    echo "${COLORSPACE}${MSG}${COLOR_RESET}"
}

# or simply

color_message () {
    eval "echo \"\$$2\$1\$COLOR_RESET\""
}

I was wondering, why not just create a function for each particular task:

Code:

function log_message {
    echo "$BOLDGREEN$1$COLOR_RESET"
}

function log_warning {
    echo "$BOLDYELLOW$1$COLOR_RESET"
}

function log_error {
    echo "$BOLDRED$1$COLOR_RESET"
}

function log_debug {
    echo "$BOLDCYAN$1$COLOR_RESET"
}


Woodsman 08-13-2010 09:20 PM

Quote:

I was wondering, why not just create a function for each particular task:
I had considered that idea. :)

I use the shell-colors container in almost all of my shell scripts and likewise in the script I was revising. In my revisions I noticed a lot of repetitive code with respect to user informational messaging. I consolidated that repetition into a simple function I could call. Much nicer and more efficient, of course.

In that script I noticed I had about 8 or 9 snippets where I could not use my new function, but if I could pass the desired color as an argument/parameter I could augment my new function with an elif statement.

I could have created a separate color functions for this one script but that would have been less efficient than just using the color variables directly inline.

konsolebox 08-13-2010 09:39 PM

Since you're after efficiency I also thought about sharing my codes. This is part of playshell:

log.sh
Code:

include available.sh
include assume.sh
include console.sh
include debug.sh
include env.sh
include misc.sh
include useextglob.sh


#
# log.sh
#
# provides the logging framework of playshell
#
# author: konsolebox
# copyright free
# created and updated 2006-2010
#


LOG_LEVELS_SILENT=0    ## no output even warnings and errors
LOG_LEVELS_QUIET=1    ## output only warnings and errors
LOG_LEVELS_NORMAL=2    ## normal
LOG_LEVELS_VERBOSE=3  ## include verbose messages
LOG_LEVELS_DEBUG=4    ## include debug messages and all other messages

LOG_ID_MSG=0
LOG_ID_INF=1
LOG_ID_WRN=2
LOG_ID_ERR=3
LOG_ID_VER=4
LOG_ID_DBG=5
LOG_ID_IMS=6

LOG_FUNCTIONS=(
        [LOG_ID_MSG]=log_message
        [LOG_ID_IMS]=log_imessage
        [LOG_ID_INF]=log_info
        [LOG_ID_WRN]=log_warning
        [LOG_ID_ERR]=log_error
        [LOG_ID_VER]=log_verbose
        [LOG_ID_DBG]=log_debug
)

LOG_FUNCTIONS_LEVELS=(
        [LOG_ID_MSG]=$LOG_LEVELS_NORMAL
        [LOG_ID_IMS]=$LOG_LEVELS_SILENT
        [LOG_ID_INF]=$LOG_LEVELS_NORMAL
        [LOG_ID_WRN]=$LOG_LEVELS_QUIET
        [LOG_ID_ERR]=$LOG_LEVELS_QUIET
        [LOG_ID_VER]=$LOG_LEVELS_VERBOSE
        [LOG_ID_DBG]=$LOG_LEVELS_DEBUG
)

LOG_FUNCTIONS_COLORS_DEFAULT=(
        [LOG_ID_MSG]=$CONSOLE_HI_GREEN
        [LOG_ID_IMS]=$CONSOLE_HI_GREEN
        [LOG_ID_INF]=$CONSOLE_HI_WHITE
        [LOG_ID_WRN]=$CONSOLE_HI_YELLOW
        [LOG_ID_ERR]=$CONSOLE_HI_RED
        [LOG_ID_VER]=$CONSOLE_GREEN
        [LOG_ID_DBG]=$CONSOLE_HI_CYAN
)

LOG_FUNCTIONS_COLORS_NONE=(
        [LOG_ID_MSG]=
        [LOG_ID_IMS]=
        [LOG_ID_INF]=
        [LOG_ID_WRN]=
        [LOG_ID_ERR]=
        [LOG_ID_VER]=
        [LOG_ID_DBG]=
)

LOG_FUNCTIONS_COLORS=(
        "${LOG_FUNCTIONS_COLORS_DEFAULT[@]}"
)

LOG_LEVEL=$LOG_LEVELS_NORMAL

LOG_USEESCAPECODES=true

LOG_WRAPCOLUMNS=72


# void log_configure (["mode=<MODE>"][, "colors=<COLORSVAR>|true|false|none"][, "escapecodes=true|false"][, "@no-setup")
#
function log_configure {
        local SETUP=true

        assume "\$2 == @(mode=@(debug|verbose|normal|quiet|silent)|colors=@(+([[:alpha:]])*([[:digit:]_])|true|false|none)|escapecodes=@(true|false))" "$1"

        while [[ $# -gt 0 ]]; do
                case "$1" in
                mode=*)
                        case "${1#mode=}" in
                        debug)
                                LOG_LEVEL=$LOG_LEVELS_DEBUG
                                ;;
                        verbose)
                                LOG_LEVEL=$LOG_LEVELS_VERBOSE
                                ;;
                        normal)
                                LOG_LEVEL=$LOG_LEVELS_NORMAL
                                ;;
                        quiet)
                                LOG_LEVEL=$LOG_LEVELS_QUIET
                                ;;
                        silent)
                                LOG_LEVEL=$LOG_LEVELS_SILENT
                                ;;
                        esac
                        ;;
                colors=*)
                        case "${1#colors=}" in
                        false|none)
                                LOG_FUNCTIONS_COLORS=(${LOG_FUNCTIONS_COLORS_NONE})
                                ;;
                        true)
                                LOG_FUNCTIONS_COLORS=("${LOG_FUNCTIONS_COLORS_DEFAULT[@]}")
                                ;;
                        *)
                                eval "LOG_FUNCTIONS_COLORS=(\"\${${1#colors=}[@]}\")"
                                ;;
                        esac
                        ;;
                escapecodes=*)
                        LOG_USEESCAPECODES=${1#escapecodes=}
                        ;;
                @no-setup)
                        SETUP=false
                        ;;
                esac

                shift
        done

        [[ $SETUP = true ]] && log_setup
}


# void log_setup (void)
#
function log_setup {
        local NAME COLOR PREFIX TEMPLATE FUNCTION WRAPTEMPLATE0 WRAPTEMPLATE1
        local -i LEVEL INDEX
        local -a TEMPLATES=()

        # normal functions

        if [[ LOG_LEVEL -ge LOG_LEVELS_DEBUG ]]; then
                PREFIX='${FUNCNAME[1]}: '
        else
                PREFIX=''
        fi

        for INDEX in ${!LOG_FUNCTIONS[@]}; do
                NAME=${LOG_FUNCTIONS[INDEX]}
                LEVEL=${LOG_FUNCTIONS_LEVELS[INDEX]}
                COLOR=${LOG_FUNCTIONS_COLORS[INDEX]}

                if [[ LEVEL -le LOG_LEVEL ]]; then
                        if [[ $LOG_USEESCAPECODES = false ]]; then
                                TEMPLATE="<MESSAGE>"
                        elif [[ -n $COLOR ]]; then
                                TEMPLATE="${CONSOLE_NORMAL}${COLOR}<MESSAGE>${CONSOLE_NORMAL}"
                        else
                                TEMPLATE="${CONSOLE_NORMAL}<MESSAGE>"
                        fi

                        eval "function $NAME { echo -e \"${TEMPLATE/'<MESSAGE>'/${PREFIX}\$1}\"; LOG_LASTLEVEL=$LEVEL; }"
                else
                        TEMPLATE=''

                        eval "function $NAME { LOG_LASTLEVEL=$LEVEL; }"
                fi

                TEMPLATES[INDEX]=$TEMPLATE
        done

        # special function log_fcall()

        if [[ LOG_LEVELS_DEBUG -le LOG_LEVEL ]]; then
                TEMPLATE=${TEMPLATES[LOG_ID_DBG]/'<MESSAGE>'/'$MESSAGE'}

                eval "
                        function log_fcall {
                                local MESSAGE

                                if [[ \$# -eq 1 ]]; then
                                        MESSAGE=\"\${FUNCNAME[1]}( \\\"\$1\\\" )\"
                                elif [[ \$# -gt 1 ]]; then
                                        local LASTARG=\${!#}
                                        local -a ARGSTEMP=(\"\${@:1:\$(( \$# - 1 ))}\")
                                        ARGSTEMP=(\"\${ARGSTEMP[@]/#/\\\"}\")
                                        MESSAGE=\"\${FUNCNAME[1]}( \${ARGSTEMP[@]/%/\\\",} \\\"\${LASTARG}\\\" )\"
                                else
                                        MESSAGE=\"\${FUNCNAME[1]}()\"
                                fi

                                echo -e \"${TEMPLATE}\"

                                LOG_LASTLEVEL=$LOG_LEVELS_DEBUG
                        }
                "
        else
                eval "function log_fcall { LOG_LASTLEVEL=${LOG_LEVELS_DEBUG}; }"
        fi

        # special function log_finalerror()

        if [[ LOG_LEVELS_ERROR -le LOG_LEVEL ]]; then
                TEMPLATE=${TEMPLATES[LOG_ID_ERR]/'<MESSAGE>'/'$1'}

                eval "
                        function log_finalerror {
                                echo \"$TEMPLATE\"
                                exit \"\${2:-1}\"
                        }
                "
        else
                function log_finalerror { exit "${2:-1}"; }
        fi

        # special function log_fatalerror()

        if [[ LOG_LEVELS_ERROR -le LOG_LEVEL ]]; then
                TEMPLATE=${TEMPLATES[LOG_ID_ERR]/'<MESSAGE>'/'${MESSAGE[*]}'}

                eval "
                        function log_fatalerror {
                                local -a MESSAGE=()
                                local -i L=0 F

                                MESSAGE[L++]=\"${PREFIX}fatal error: \${FUNCNAME[1]}(): \$1\"
                                MESSAGE[L++]=''
                                MESSAGE[L++]='function call stack:'
                                MESSAGE[L++]=''

                                for (( F = \${#FUNCNAME[@]}; F--; )); do
                                        MESSAGE[L++]=\$'\\t'\"\${FUNCNAME[F]}()\"
                                done

                                MESSAGE[L++]=''

                                local IFS=\$'\\n'

                                echo \"$TEMPLATE\"

                                exit \"\${2:-1}\"
                        }
                "
        else
                function log_fatalerror { exit "${2:-1}"; }
        fi

        # wrapped versions

        WRAPTEMPLATE0="
                function <NAME0> {
                        local WRAPPED
                        utils_parawrap \"\$1\" WRAPPED \"\${2:-${LOG_WRAPCOLUMNS}}\"
                        local -i I
                        for I in \${!WRAPPED[@]}; do
                                <NAME1> \"\${WRAPPED[I]}\"
                        done
                }
        "
        WRAPTEMPLATE1="
                function <NAME0> {
                        LOG_LASTLEVEL=<LEVEL>
                }
        "

        for INDEX in ${!LOG_FUNCTIONS[@]}; do
                NAME=${LOG_FUNCTIONS[INDEX]}
                LEVEL=${LOG_FUNCTIONS_LEVELS[INDEX]}

                FUNCTION=${WRAPTEMPLATE0/<NAME0>/"${NAME}_wrapped"}

                if [[ LEVEL -le LOG_LEVEL ]]; then
                        FUNCTION=${FUNCTION/<NAME1>/"$NAME"}
                else
                        FUNCTION=${FUNCTION/<LEVEL>/"$LEVEL"}
                fi

                eval "$FUNCTION"
        done
}


# void log_checkterminal (void)
#
function log_checkterminal {
        if available tput; then
                if [[ -z $(tput reset) || ! $(tput colors) = 8 ]]; then
                        LOG_USEESCAPECODES=false
                        LOG_USECOLORS=false
                fi
        fi
}


# void log (string TEXT, ["color=<COLOR>"], ["level=<LEVEL>"])
#
# the generic logging function
#
function log {
        local COLOR='' LEVEL=$LOG_LEVELS_NORMAL A

        for A; do
                case "$A" in
                color=*)
                        COLOR=${A#color=}
                        case "$COLOR" in
                        message)
                                COLOR=${LOG_FUNCTIONS_COLORS[LOG_ID_MSG]}
                                ;;
                        info)
                                COLOR=${LOG_FUNCTIONS_COLORS[LOG_ID_INF]}
                                ;;
                        warning)
                                COLOR=${LOG_FUNCTIONS_COLORS[LOG_ID_WRN]}
                                ;;
                        error)
                                COLOR=${LOG_FUNCTIONS_COLORS[LOG_ID_ERR]}
                                ;;
                        verbose)
                                COLOR=${LOG_FUNCTIONS_COLORS[LOG_ID_VER]}
                                ;;
                        debug)
                                COLOR=${LOG_FUNCTIONS_COLORS[LOG_ID_DBG]}
                                ;;
                        #beginoptionalblock
                        $'\e'\[*)
                                ;;
                        *)
                                echo "log: invalid parameter to color: $COLOR"
                                exit 1
                                ;;
                        esac
                        #endoptionalblock
                        ;;
                level=*)
                        LEVEL=${A#level=}
                        case "$LEVEL" in
                        silent)
                                LEVEL=$LOG_LEVELS_SILENT
                                ;;
                        quiet)
                                LEVEL=$LOG_LEVELS_QUIET
                                ;;
                        normal)
                                LEVEL=$LOG_LEVELS_NORMAL
                                ;;
                        verbose)
                                LEVEL=$LOG_LEVELS_VERBOSE
                                ;;
                        debug)
                                LEVEL=$LOG_LEVELS_DEBUG
                                ;;
                        #beginoptionalblock
                        +([[:digit:]]))
                                ;;
                        *)
                                echo "log: invalid parameter to level: $LEVEL"
                                exit 1
                                ;;
                        #endoptionalblock
                        esac
                        ;;
                esac
        done

        if [[ LEVEL -le LOG_LEVEL ]]; then
                if [[ $LOG_USEESCAPECODES = false ]]; then
                        echo -e "$1"
                elif [[ -n $COLOR ]]; then
                        echo -e "${CONSOLE_NORMAL}${COLOR}${1}${CONSOLE_NORMAL}"
                else
                        echo -e "${CONSOLE_NORMAL}${1}"
                fi
        fi

        LOG_LASTLEVEL=$LEVEL
}


# void log_message (string TEXT)
#
function log_message {
        :
}


# void log_imessage (string TEXT)
#
function log_imessage {
        :
}


# void log_info (string TEXT)
#
function log_info {
        :
}


# void log_warning (string TEXT)
#
function log_warning {
        :
}


# void log_error (string TEXT)
#
function log_error {
        :
}


# void log_verbose (string TEXT)
#
function log_verbose {
        :
}


# void log_debug (string TEXT)
#
function log_debug {
        :
}


# void log_fcall ([string ARGS])
#
function log_fcall {
        :
}


# void log_finalerror (string FUNCTION, [int EXITCODE])
#
function log_finalerror {
        :
}


# void log_fatalerror (string FUNCTION, [int EXITCODE])
#
function log_fatalerror {
        :
}


# void log_newline (void)
#
# creates a newline with level based on the last level
#
function log_newline {
        [[ LOG_LEVEL -ge LOG_LASTLEVEL ]] && echo
}


# bool log_checklevel (string IDENT, ...)
#
# where IDENT can be any of the significant part of the name of the
# supported functions like message, imessage, info, error, warning,
# verbose, debug, fcall, finalerror and criticalerror, any of the valid
# levels like silent, quiet, normal, verbose, debug or just any number.
#
# More than one IDENT can be specified.
#
# If one of the IDENTs represents a level that is lower than the current
# level, this function returns true or false if none otherwise.
#
function log_checklevel {
        local -i LEVEL

        for A; do
                case "$1" in
                message)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_MSG]}
                        ;;
                info)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_INF]}
                        ;;
                warning)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_WRN]}
                        ;;
                error)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_ERR]}
                        ;;
                verbose)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_VER]}
                        ;;
                debug)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_DBG]}
                        ;;
                imessage)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_IMS]}
                        ;;
                finalerror)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_ERR]}
                        ;;
                criticalerror)
                        LEVEL=${LOG_FUNCTIONS_LEVELS[LOG_ID_ERR]}
                        ;;
                silent)
                        LEVEL=${LOG_LEVELS_SILENT}
                        ;;
                quiet)
                        LEVEL=${LOG_LEVELS_QUIET}
                        ;;
                normal)
                        LEVEL=${LOG_LEVELS_NORMAL}
                        ;;
                verbose)
                        LEVEL=${LOG_LEVELS_VERBOSE}
                        ;;
                debug)
                        LEVEL=${LOG_LEVELS_DEBUG}
                        ;;
                +([[:digit:]]))
                        LEVEL=$1
                        ;;
                #beginoptionalblock
                *)
                        echo "log_checklevel: invalid parameter: $LEVEL"
                        exit 1
                        ;;
                #endoptionalblock
                esac

                [[ LEVEL -le LOG_LEVEL ]] && return 0
        done

        return 1
}


{
        [[ $DEBUG = true ]] && LOG_LEVEL=$LOG_LEVELS_DEBUG

        log_checkterminal

        log_setup
}

console.sh
Code:

# console.sh
#
# contains common escape codes
#
# author: konsolebox
# copyright free
# created and updated 2008-2010
#


# normal colors

CONSOLE_NORMAL=$'\e[0m'
CONSOLE_BLACK=$'\e[30m'
CONSOLE_RED=$'\e[31m'
CONSOLE_GREEN=$'\e[32m'
CONSOLE_YELLOW=$'\e[33m'
CONSOLE_BLUE=$'\e[34m'
CONSOLE_MAGENTA=$'\e[35m'
CONSOLE_CYAN=$'\e[36m'
CONSOLE_WHITE=$'\e[37m'


# high-intensity colors

CONSOLE_HI=$'\e[0m'
CONSOLE_HI_BLACK=$'\e[1;30m'
CONSOLE_HI_RED=$'\e[1;31m'
CONSOLE_HI_GREEN=$'\e[1;32m'
CONSOLE_HI_YELLOW=$'\e[1;33m'
CONSOLE_HI_BLUE=$'\e[1;34m'
CONSOLE_HI_MAGENTA=$'\e[1;35m'
CONSOLE_HI_CYAN=$'\e[1;36m'
CONSOLE_HI_WHITE=$'\e[1;37m'


# background colors

CONSOLE_BG_BLACK=$'\e[40m'
CONSOLE_BG_RED=$'\e[41m'
CONSOLE_BG_GREEN=$'\e[42m'
CONSOLE_BG_YELLOW=$'\e[43m'
CONSOLE_BG_BLUE=$'\e[44m'
CONSOLE_BG_MAGENTA=$'\e[$5m'
CONSOLE_BG_CYAN=$'\e[46m'
CONSOLE_BG_WHITE=$'\e[47m'


# special codes

CONSOLE_CLEARSCREEN=$'\e[H\e[2J'
CONSOLE_CLEARALLAFTER=$'\e[J'
CONSOLE_CLEARTOEOL=$'\e[K'
CONSOLE_CLEARLINE=$'\e[G\e[K'
CONSOLE_HOME=$'\e[H'
CONSOLE_HOMELINE=$'\e[G'
CONSOLE_RESET=$'\e[]R]'
CONSOLE_RESTORE=$'\e[u'
CONSOLE_SAVE=$'\e[s'


# functions (added when needed only)

function console_clearline {
        echo -n "$CONSOLE_CLEARLINE"
}

colors.sh
Code:

include console.sh
include log.sh


#
# colors.sh
#
# author: konsolebox
# copyright free
# created and updated 2008-2010
#


COLORS_LIGHTBG=(
        [LOG_ID_MSG]=$CONSOLE_GREEN
        [LOG_ID_WRN]=$CONSOLE_YELLOW
        [LOG_ID_ERR]=$CONSOLE_RED
        [LOG_ID_VER]=$CONSOLE_GREEN
        [LOG_ID_DBG]=$CONSOLE_CYAN
)


COLORS_DARKBG=(
        [LOG_ID_MSG]=$CONSOLE_HI_GREEN
        [LOG_ID_WRN]=$CONSOLE_HI_YELLOW
        [LOG_ID_ERR]=$CONSOLE_HI_RED
        [LOG_ID_VER]=$CONSOLE_HI_GREEN
        [LOG_ID_DBG]=$CONSOLE_CYAN
)

The codes might be pretty uneasy but I hope it can give you an idea.


All times are GMT -5. The time now is 08:24 AM.