1

This is an example of my case function:

function SendToScreen(){
echo -e "$*"
}

So I call it by:

SendToScreen "Hello"

And, if I want to add color codes:

VioletForeGroundColor="\033[38;5;99m"
NormalColor="\033[0m"
SendToScreen "Hello"$VioletForeGroundColor" violet "$NormalColor" word."

That gives me a correct:

ANSI codes OK
But the problem comes if I want to send some DOS-type path (including \ slash):

VioletForeGroundColor="\033[38;5;99m"
NormalColor="\033[0m"
MyDOSPath="d:\vivisector"
SendToScreen "Hello"$VioletForeGroundColor" violet "$NormalColor" word. The path is $MyDOSPath"

Because \v is some sort of ANSI code, so this time I obtain:

ANSI codes problem
I need my function to output color text (bold, cursive, underline... etc), so I must use echo -e.

How could I solve the problem with such nagging control codes colliding characters like this \v (I suppose there will be another ones)?

I would like to repair the isssue by modifying the function, but I am not sure this is the proper method.

Thanks.

EDIT-1: We will choose \033 also known as \e as the only ANSI code that needs to remain.

Sopalajo de Arrierez
  • 3,543
  • 4
  • 34
  • 52
  • You'd have to arrange for `$MyDOSPath` to be escaped so that `echo -e` prints what you want. I probably use a C program to do that; I wouldn't want to write the shell for the job. I'd use `... The path is $(escape "$MyDOSPath")"`. Incidentally, you could use `SendToScreen "Hello ${VioletForeGroundColor}violet ${NormalColor}word."` to send a single argument to the function and yet have variables interpreted properly. – Jonathan Leffler Jan 22 '15 at 18:13
  • The program is already writen in bash, and it is a bit long to migrate everything now to C. But I think escaping the variables would be a good idea. The `escape` function could replace each `\v` by `\\v`. This function could be used too for escaping any other conflictive character. Thans for the suggestion, @JonathanLeffler . – Sopalajo de Arrierez Jan 22 '15 at 18:19
  • I meant 'a program to convert strings containing possible escape sequences so they're safe for `echo -e`', not rewrite the larger Bash script. – Jonathan Leffler Jan 22 '15 at 18:34
  • http://stackoverflow.com/a/20983251/775806 – n. m. could be an AI Jan 22 '15 at 19:57

2 Answers2

2

New answer:

function SendToScreen() {
    echo -e $(echo "${*//\\/\\\\}" | sed 's/\\\\033\[/\\033\[/g');
}

This one escapes everything, then un-escapes anything that looks like a color sequence (\033[). The possibility of sending filenames as color sequences is greatly reduced. You can reduce it even further by white-listing only those color sequences that you want to allow, and changing the sed command to a sequence of sed commands that un-escapes those exact sequences.

Old answer: Let's say you want to escape \v and \n, you can do this:

function SendToScreen(){
    a="${*//\\v/\\\\v}"
    a="${a//\\n/\\\\n}"
    echo -e "$a"
}

You can extend this with whatever other escapes you don't want to process.

Markku K.
  • 3,840
  • 19
  • 20
  • It seems a good idea. I am not very experienced on shell replacement: if I find some other nagging `\` code, say `\n`, could this method be easily upgraded to implement it too? – Sopalajo de Arrierez Jan 22 '15 at 18:24
  • What about `\t`. `\n`, `\007`, `\r`, `\f`, `\a`, `\b`, etc? – Jonathan Leffler Jan 22 '15 at 18:35
  • Answer updated to show how to extend to other escape sequences – Markku K. Jan 22 '15 at 18:48
  • Borderline...at best. You can't tell down in the `SendToScreen` function which backslashes need to be expanded by `echo -e` (those associated with colour escape sequences) and which must not be expanded (parts of DOS file names). So the mapping must be done in the invocation of the function, not in the function itself. – Jonathan Leffler Jan 22 '15 at 18:50
  • @JonathanLeffler, I don't understand what you mean by "You can't tell down in the SendToScreen function which backslashes need to be expanded by `echo -e`". The color escape sequences are either octal or hex, so they either start with "\x" or "\0". So, you can escape all the other escapes that you mentioned using this method, except `\007`. So, there will be some escapes that pass through, but the common ones (DOS-style paths) will be caught. – Markku K. Jan 22 '15 at 18:59
  • The DOS path might be: `C:\new\table\value\alert\form\033.txt`. How can `SendToScreen` know which of the two (or more) `\033` sequences in its argument list should be interpreted as 'escape' and which as a backslash followed by 3 digits? There's no way for `SendToScreen` to know! – Jonathan Leffler Jan 22 '15 at 19:02
  • Good point. After looking at your answer, I also realized that mine only replaced the first occurrence of the escape sequence. – Markku K. Jan 22 '15 at 19:08
  • Updated answer to improve matching of valid color escapes. – Markku K. Jan 22 '15 at 19:43
  • @JonathanLeffler and Markku : if I am not wrong, the `\e` string is equivalent to `\033`, right? If so, when invoking the function, should I remember to use only the second instead of the first? – Sopalajo de Arrierez Jan 22 '15 at 22:01
  • @SopalajodeArrierez: It is largely up to you. You have to decide how to handle it. You have various solutions outlined. The upshot is that regardless of what you use, if you pass arguments to SendToScreen to `echo -e`, you have to be sure that the argument doubles up all the backslashes that you do not want to be interpreted as the start of an escape sequence. I don't think there's a trivial, foolproof solution; the DOS path name I showed will be mangled by `echo -e`, but there's no easy, reliable, general purpose way for the function to guess what should be expanded and what should not. – Jonathan Leffler Jan 22 '15 at 22:06
  • You can extend the technique to also un-escape "\e[" ... just replace `sed 's/\\\\033\[/\\033\[/g'` with `sed 's/\\\\033\[/\\033\[/g' | sed 's/\\\\e\[/\\e\[/g'` – Markku K. Jan 22 '15 at 22:09
  • As long as the only ANSI characters I need to leave untoched are text format ones (color, bold, cursive, underline... etc, they all start by `\033`), I think this is the best solution for my case. Thanks you all. – Sopalajo de Arrierez Jan 22 '15 at 22:27
  • @SopalajodeArrierez ... which kind of solution you choose largely depends on which side of [worse is better](http://www.jwz.org/doc/worse-is-better.html) you agree with more (or disagree with less). I am clearly favoring simplicity over correctness in my answer :) – Markku K. Jan 22 '15 at 22:29
  • My final line inside the function that seems to be working is `echo -e "$(echo ${*//\\/\\\\} | sed 's/\\\\033\[/\\033\[/g')"`. Note the double quotes `"`. – Sopalajo de Arrierez Jan 23 '15 at 00:17
1

The echo -e simply interprets sequences starting with backslash, so you simply need to ensure that the $MyDOSPath argument has all backslashes doubled up. That could be:

SendToScreen "Hello ${VioletForeGroundColor}violet${NormalColor} word." \
             "The path is ${MyDOSPath//\\/\\\\}"

which uses a 'substitute' parameter expansion. The // means 'change every backslash to double backslash'.


As discussed in various comments, maybe the design of SendToScreen is sub-optimal. One possible alternative design uses:

SendToScreen [-e "string-to-expand"][-p "plain-string"] [-- "plain strings"]

Arguments that need to be expanded are, and those that should not be expanded are not. By default, they're not. So, example usage:

$ VioletForeGroundColor="\033[38;5;99m"
$ NormalColor="\033[0m"
$ MyDOSPath="C:\new\table\value\alert\form\033.txt"
$ echo "$MyDOSPath"
C:\new\table\value\alert\form\033.txt
$ bash SendToScreen.sh -e "${VioletForeGroundColor}violet${NormalColor}" -e "The path is ${MyDOSPath//\\/\\\\}" -p "Or $MyDOSPath" "Plain $MyDOSPath"
violet The path is C:\new\table\value\alert\form\033.txt Or C:\new\table\value\alert\form\033.txt Plain C:\new\table\value\alert\form\033.txt
$  bash SendToScreen.sh -e "${VioletForeGroundColor}violet${NormalColor}" -e "The path is ${MyDOSPath//\\/\\\\}" -p "Or $MyDOSPath" -e "Oops! $MyDOSPath" "Plain $MyDOSPath"
violet The path is C:\new\table\value\alert\form\033.txt Or C:\new\table\value\alert\form\033.txt Oops! C: ew able
              aluelert
                      orm.txt Plain C:\new\table\value\alert\form\033.txt
$

A hex dump of the last lot of output was:

0x0000: 1B 5B 33 38 3B 35 3B 39 39 6D 76 69 6F 6C 65 74   .[38;5;99mviolet
0x0010: 1B 5B 30 6D 20 54 68 65 20 70 61 74 68 20 69 73   .[0m The path is
0x0020: 20 43 3A 5C 6E 65 77 5C 74 61 62 6C 65 5C 76 61    C:\new\table\va
0x0030: 6C 75 65 5C 61 6C 65 72 74 5C 66 6F 72 6D 5C 30   lue\alert\form\0
0x0040: 33 33 2E 74 78 74 20 4F 72 20 43 3A 5C 6E 65 77   33.txt Or C:\new
0x0050: 5C 74 61 62 6C 65 5C 76 61 6C 75 65 5C 61 6C 65   \table\value\ale
0x0060: 72 74 5C 66 6F 72 6D 5C 30 33 33 2E 74 78 74 20   rt\form\033.txt 
0x0070: 4F 6F 70 73 21 20 43 3A 20 65 77 20 61 62 6C 65   Oops! C: ew able
0x0080: 0B 61 6C 75 65 07 6C 65 72 74 0C 6F 72 6D 1B 2E   .alue.lert.orm..
0x0090: 74 78 74 20 50 6C 61 69 6E 20 43 3A 5C 6E 65 77   txt Plain C:\new
0x00A0: 5C 74 61 62 6C 65 5C 76 61 6C 75 65 5C 61 6C 65   \table\value\ale
0x00B0: 72 74 5C 66 6F 72 6D 5C 30 33 33 2E 74 78 74 0A   rt\form\033.txt.
0x00C0:

You'll have to take my word for it that violet appeared in violet.

Clearly, the user (caller) of SendToScreen has to know which arguments should be expanded and which should not. However, it makes it very explicit.

Here's the code I used as a script. Repackaging as a function is left as an exercise for the reader. Extending it to add -c colour (or maybe -f foreground and -b background) is an exercise for the reader.

#!/bin/bash

output=()

while getopts "p:e:" opt
do
  case "$opt" in
    (e) output+=( $(echo -e "$OPTARG") );;
    (p) output+=( "$OPTARG" );;
  esac
done

shift $(($OPTIND - 1))

echo "${output[@]}" "$@"

Have fun!

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Unfortunately, this puts the burden on the user of SendToScreen to figure out which things to escape, and which not to escape. For example, what if they have variables that, themselves, contain color info? E.g. `ExamplePath="C:\new\table\${Red}your_project${Normal}"` – Markku K. Jan 22 '15 at 19:12
  • @MarkkuK. Yes, it does, but as discussed in comments to your answer, there's no way for the function to know which backslash sequences are to be interpreted by `echo -e` and which should be left unchanged. And `echo -e` simply changes everything. So, the caller has to take care because the function can't. Or the function has to be redesigned somehow so that sequences for colour changing are handled with `echo -e` and other sequences are handled without `echo -e`. With the current design, everything passed to the function is subject to `echo -e`; only the caller code can tell what shouldn't be. – Jonathan Leffler Jan 22 '15 at 19:17