4

I am using diff to format a string that includes tput color variables, and am unable to have those variables evaluated without using the "evil" eval command.

The command that creates the string:

output1="$(diff --changed-group-format="\${RED}%=\${CLS}" <(echo -e "${nstr1}") <(echo -e "${nstr2}")|tr -d '\n')"

and outputs this:

[String n${RED}â${CLS}m${RED}è™${CLS}]

I've looked and searched other answers, but nothing is working other than:

eval echo "${output1}"

From what I have read, my 3 options are eval(bad), indirect expansion(better), and arrays(best). Every attempt at indirection has failed. I'd love to use the array option but I am just not seeing how it would apply here. Am I missing something?

I don't think it's relevant, but the variables and the construction of strings sent to diff are in another question here.

Community
  • 1
  • 1
akovia
  • 117
  • 8

1 Answers1

1

You can use Bash parameter expansion, if you're willing to make do with a finite, known-in-advance set of color codes:

#!/usr/bin/env bash

# Define the variables containing ANSI color sequences.
RED="$(tput setaf 1)"
CYA="$(tput setaf 6)"
CLS="$(tput sgr0)"

# Sample input string
str='[String n${RED}â${CLS}m${CYA}è™${CLS}]'

# Replace the placeholders with their corresponding variable values.
str=${str//'${RED}'/${RED}}
str=${str//'${CYA}'/${CYA}}
str=${str//'${CLS}'/${CLS}}

# Output the result.
echo "$str"

This approach takes advantage of the fact that the argument used in Bash parameter expansion are themselves subject to expansion, unless single-quoted:

  • ${<varName>//<search>/<replace>} replaces all instances of <search> with <replace> in the value of variable <varName>.
  • '${RED}', for instance, - due to being single-quoted - is taken as the literal search term.
  • ${RED}, for instance - due to being unquoted - is expanded before being used as the replacement term, therefore effectively replacing literal ${RED} with the value of variable ${RED}.

Wrapped in a function:

printColored() {
  local str=$1
  local RED="$(tput setaf 1)" CYA="$(tput setaf 6)" CLS="$(tput sgr0)"
  str=${str//'${RED}'/${RED}}
  str=${str//'${CYA}'/${CYA}}
  str=${str//'${CLS}'/${CLS}}
  printf '%s\n' "$str"
}

printColored '[String n${RED}â${CLS}m${CYA}è™${CLS}]'

Incidentally, I'd rename ${CLS} to ${RST} (for "reset") or something similar, because the term "cls" suggests clearing the whole screen.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • I don't understand why this works but it does! Isn't the resulting string identical? If it's a matter of going through the motions, I could use shorter placeholder correct? Also, I already have those variables globally so I shouldn't have declare them local here right? (RED, CYA etc..) Lastly, great tip on cls, that got right by me. – akovia Dec 21 '15 at 00:39
  • @akovia: See if my update explains it. Yes, you could use shorter placeholders. If you're using global variables you don't have to declare them inside the function, but for better encapsulation you may choose to do so. – mklement0 Dec 21 '15 at 00:50
  • 1
    Thanks for expanding the explanation. This works nicely! – akovia Dec 21 '15 at 00:57