Zsh color inside command substitution - bash

Coloring output of commands in zsh is kind of simple. Consider the following example in zsh prompt:
print -P "%F{cyan}$(date +'%H:%M:%S')$reset"
You get cyan HH:MM:SS as expected. It works in prompt as expected as well. Now suppose I want to color minutes and seconds in a different color. I didn't manage to achieve it using %F{color}, is it possible?
I can make it work using ANSI codes, but even then it works with print and does not work when used as prompt in ~/.zshrc:
print -P "%F{cyan}$(date +'%H:\e[38;5;82m%M:%S')" - works in zsh
RPS1="%F{cyan}$(date +'%H:\e[38;5;82m%M:%S')" as a right prompt gives 17:\e[38;5;82m14:11
What am I missing? How do I escape the color code or even better use zsh %F{color} construct?

It would have some quoting problems.
It could not been used double quoets; the $(date...) part would be expanded, RPS1 would not be updated for each prompts.
It could be unescaped any escape(\e)s. (especially \e[38;5;82m part for date command)
So, for PS-like strings, it would be useful to quote using $'...' forms like this:
setopt promptsubst
RPS1=$'%F{cyan}$(date +"%H:%%{\e[38;5;82m%%}%M:%S")%{\e[0m%}'
If you can find the color index for \e[38;5;82m:
RPS1=$'%F{cyan}$(date +"%H:%%{%%F{82}%%}%M:%S")%{\e[0m%}'
It could be found by some tools like https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
Note: \e[38;5;82m and \e[0m are surrounded with %{...%}.
%{...%}
Include a string as a literal escape sequence. The string within the braces should not change the cursor position. Brace pairs can nest.
--- zshmisc(1), Visual Effects, Prompt Expansion
Note2: setopt promptsubst. without this option, print -P ... nor RPS1=... does not work.
PROMPT_SUBST
If set, parameter expansion, command substitution and arithmetic expansion are performed in prompts. Substitutions within prompts do not affect the command status.
--- zshoptions(1), PROMPT_SUBST, zsh options
setopt promptsubst
print -P $'%F{cyan}$(date +"%H:%%{\e[38;5;82m%%}%M:%S")%{\e[0m%}'
;# => 23:54:18
PS: %F{color} constructs would be easier for copy-pasting variables with dumping them on screen.
> print $RPS1 ;# this output could not been used for copy-pasting
%F{cyan}$(date +"%H:%%{%%}%M:%S")%{%}
> print $RPS1 | cat -v ;# this either (but close to)
%F{cyan}$(date +"%H:%%{^[[38;5;82m%%}%M:%S")%{^[[0m%}

Version 1 - Calling date only once:
d=$(date +'%H:%M:%S');h=${d:0:2};ms=${d:3:5};
RPS1="%F{cyan}$h:%F{green}$ms%F{default}"
Version 2 - Calling date twice:
RPS1="%F{cyan}$(date +'%H'):%F{green}$(date +'%M:%S')%F{default}"

There is no need to use the external command date: Zsh has built-in prompt escapes for displaying date and time:
[…]
%D{string}
string is formatted using the strftime function. See man page strftime(3) for more details.
[…]
So the simple coloring can be achieved with
RPS1='%F{cyan}%D{%H:%M:%S}%f'
In order to have two colors, you can just use two %D{…} blocks and color them differently
RPS1="%F{cyan}%D{%H}:%F{82}%D{%M:%S}"
This can be as complex as you need (want):
RPS1='%F{154}%D{%H}%F{155}:%F{156}%D{%M}%F{157}:%F{158}%D{%S}'

Related

Unbold piped text in Bash [duplicate]

Weird question. When I set a variable in Bash to display as a certain color, I don't know how to reset it. Here is an example:
First define the color code:
YELLOW=$(tput setaf 3)
RESET=$(tput sgr0)
Now set the error message variable and color part of it.
ERROR="File not found: "$YELLOW"Length.db$RESET"
This sets the variable ERROR as the error message to be returned from a function that will eventually be displayed on the terminal. The error will be all white with the exception of the file name. The file name is highlighted yellow for the user.
This works great except when logging with rsyslog. When the error message gets logged, it comes out something like this:
File not found: #033[33mLength.db#033(B#033[m
This obviously makes log files very difficult to read. At first I figured I could process using sed the error message immediately after outputting to the terminal but before logging, but there is nothing to search for and replace. ie, I thought I could use sed to do something similar to this:
ERROR=$(echo "$ERROR" | sed -r 's%\#033\[33m%%')
But those characters are not present when you echo the variable (which makes sense since you dont see it on the terminal). So im stuck. I dont know how to reset the color of the variable after setting it. I also tried to reverse the process somehow using $RESET but maybe my syntax is wrong or something.
You almost had it. Try this instead:
ERROR=$(echo "$ERROR" | sed 's%\o033\[33m%%g')
Note, however, that the use of the \oNNN escape sequence in sed is a GNU extension, and thus not POSIX compliant. If that is an issue, you should be able to do something more like:
ERROR=$(echo "$ERROR" | sed 's%'$(echo -en "\033")'\[33m%%g')
Obviously, this will only work for this one specific color (yellow), and a regex to remove any escape sequence (such as other colors, background colors, cursor positioning, etc) would be somewhat more complicated.
Also note that the -r is not required, since nothing here is using the extended regular expression syntax. I'm guessing you already know that, and that you just included the -r out of habit, but I mention it anyway just for the sake of clarity.
Here is a pure Bash solution:
ERROR="${ERROR//$'\e'\[*([0-9;])m/}"
Make it a function:
# Strips ANSI codes from text
# 1: The text
# >: The ANSI stripped text
function strip_ansi() {
shopt -s extglob # function uses extended globbing
printf %s "${1//$'\e'\[*([0-9;])m/}"
}
See:
Bash Shell Parameter Expansion
Bash Pattern-Matching

Using a bash script to insert into an SQL table with $s

I'm using a bash script to make changes to an SQL database. One of the values i'm updating uses dollar signs. The current value being something like "$$$$$" and i need to change it to "$$$$$$$$$$". However, a a $ in a bash script is used for variables.
How can i allow this small section of my bash script to used a $ as a normal character?
function prep() {
DATE_STAMP=$(date +%m%d%Y)
log "Changing mask to 10 characters"
log "$(/opt/CPU/bin/connx-query -q "update TYPE set TYPE.MASK = '$$$$$$$$$$'")"
}
As it stands right now, its just replacing each dollar sign with some random number found earlier in my script.
Bash provides different types of quoting, each with different rules about substitution (single quote ', double quote ", here document/string <<<"string" and and $'.
The double quote (used in the log ... update) will enable variable substitution, replacing each pair of $$ with the current shell PID (looks like random number).
Few options:
Consider quoting each '$' to prevent expansion
log "$(/opt/CPU/bin/connx-query -q "update TYPE set TYPE.MASK = '\$\$\$\$\$\$\$\$\$\$'")"
Over thought my own question. I can just escape the $. '\$\$\$\$\$\$\$\$\$\$'

Trouble understanding a bash command

I'm trying to set up a program and came across this line in a bash script. Could someone tell me what it does? I'm not very experienced with bash.
export PS1='\e[0;33mmyProject \e[0;32m\[\e]0;\u#\h: \w\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$\e[0m '
Thank you very much!
This command does two things. It sets the title of the terminal window, and
sets the bash prompt.
export PS1='\e[0;33mmyProject \e[0;32m\[\e]0;\u#\h: \w\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$\e[0m '
Piece by piece:
export PS1=
This sets the PS1 variable, which is contains the bash prompt.
\e[0;33m
\e is translated to the ESC character (ascii=0x1B), which is a Control Sequence Introducer, which signifies the beginning of an ANSI Escape Code. The m character at the end of the sequence indicates that the everything between [ and m is to be interpreted as a ;-separated list of SGR (Select Graphic Rendition) parameters (See here for more information). The 0 clears all previous text formatting, and the 33 sets the text color to yellow.
myProject
This just adds the string myProject to the bash prompt.
\e[0;32m
This clears all the previous text formatting (0) and sets the text color to green. (32)
\[ ... \]
\[ begins a sequence of non-printing characters which ends with \]. Everything between those two delimiters will not be visible in the prompt.
\e]0;\u#\h: \w\a
This sets the title of the terminal window to something like
username#hostname: /current/working/directory
The next bit:
${debian_chroot:+($debian_chroot)}
If the variable $debian_chroot has been defined, then this expression will evaluate to the value of $debian_chroot.
$debian_chroot is a variable that is set in /etc/bash.bashrc. This post explains it a lot better than I can.
\u#\h:\w\$\e[0m
\u evaluates to the username of the current user, \h evaluates to the name of the computer, and \w evaluates to the current working directory. \$ is just the character $. It needs to be escaped because in bash script, the character $ signifies that the following characters are the name of a variable. \e[0m reverts the text formatting back to default.
An image of what the prompt might look like in a terminal:
This is quite a complicated command you have here!
Let's break it down section by section.
export
This means that we are setting a variable to be used in other programs.
PS1=
The name of the variable is PS1.
\e
This is an escaped character. In bash (and most programming languages), Everything with a backslash before it is an escaped character. It is used for when you need to include a control character like a space, or the control key itself in a string. When it's escaped, bash treats it like it's part of the string, and not another control character.
[
This is the start of an array. It's very similar to an array in a C program.
;
This is an end character, it can mean several different things. Here, it's being used to define part of the array.
There is some other stuff here, but it's mostly just data in the array.
:
This is a NOT operand. It is used to determine the inverse of something.
${debian_chroot:+($debian_chroot)}
This is a variable. In bash, variables start with a $.
It is using the variable debian_chroot and adding it to itself if it's not null.
This command is just defining a variable, in this case an array containing information probably about a chroot with a debian install in it.

How to strip ANSI escape sequences from a variable?

Weird question. When I set a variable in Bash to display as a certain color, I don't know how to reset it. Here is an example:
First define the color code:
YELLOW=$(tput setaf 3)
RESET=$(tput sgr0)
Now set the error message variable and color part of it.
ERROR="File not found: "$YELLOW"Length.db$RESET"
This sets the variable ERROR as the error message to be returned from a function that will eventually be displayed on the terminal. The error will be all white with the exception of the file name. The file name is highlighted yellow for the user.
This works great except when logging with rsyslog. When the error message gets logged, it comes out something like this:
File not found: #033[33mLength.db#033(B#033[m
This obviously makes log files very difficult to read. At first I figured I could process using sed the error message immediately after outputting to the terminal but before logging, but there is nothing to search for and replace. ie, I thought I could use sed to do something similar to this:
ERROR=$(echo "$ERROR" | sed -r 's%\#033\[33m%%')
But those characters are not present when you echo the variable (which makes sense since you dont see it on the terminal). So im stuck. I dont know how to reset the color of the variable after setting it. I also tried to reverse the process somehow using $RESET but maybe my syntax is wrong or something.
You almost had it. Try this instead:
ERROR=$(echo "$ERROR" | sed 's%\o033\[33m%%g')
Note, however, that the use of the \oNNN escape sequence in sed is a GNU extension, and thus not POSIX compliant. If that is an issue, you should be able to do something more like:
ERROR=$(echo "$ERROR" | sed 's%'$(echo -en "\033")'\[33m%%g')
Obviously, this will only work for this one specific color (yellow), and a regex to remove any escape sequence (such as other colors, background colors, cursor positioning, etc) would be somewhat more complicated.
Also note that the -r is not required, since nothing here is using the extended regular expression syntax. I'm guessing you already know that, and that you just included the -r out of habit, but I mention it anyway just for the sake of clarity.
Here is a pure Bash solution:
ERROR="${ERROR//$'\e'\[*([0-9;])m/}"
Make it a function:
# Strips ANSI codes from text
# 1: The text
# >: The ANSI stripped text
function strip_ansi() {
shopt -s extglob # function uses extended globbing
printf %s "${1//$'\e'\[*([0-9;])m/}"
}
See:
Bash Shell Parameter Expansion
Bash Pattern-Matching

zsh random color in PS1

My .bashrc PS1 (abridged) is
\u\[\e[01;3$(($RANDOM % 8))m\]#\h \w $'
With the way bash works, it interpolates the random color after each command so the # is a different color each time (at least in the 31-37 range).
However, I've been unable to do something similar in zsh. The $'' syntax does not allow for command substitution, and concatenating does not work either:
$'\e[01;3'$(($RANDOM % 8))
# The \e[01;3 character is printed first, then the random number
Using quotes " does not work either; it just prints out a literal \e...
I know that zsh also has some built in text colors like %{$fg[red]%} and I could somehow select a random color from the array, but the problem is that it needs to be randomly selected by the PS1 and not simply on start up or the random choice will be made only once.
Is there any way I can interpolate a random number in the PS1 in zsh to achieve this?
You can use a precmd hook which will be evaluated before each prompt:
randomise_prompt_colour () {
PS1="%n%B%F{$((RANDOM % 8))}#%m %~ %(!.#.\$) "
}
add-zsh-hook precmd randomise_prompt_colour

Resources