zsh random color in PS1 - zshrc

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

Related

How to print the output of a remote terminal with/without color code in TCL?

I have this tcl script which prints the output on a remote terminal . But due to color coding implementation in the scripts , it prints the escape sequence on the remote terminal.
I have approximately 200+ commands and want to make the o/p of all the commands in a readable format with/without any color coding and not the escape sequences .
This is the output which i am getting:
This is the expected ouput:
The sequences you want to remove all have the form:
escape(hex 1b) "[" (sequence of digits and semi-colon) "m"
So you could match these sequences with a regular expression and remove them by substituting the empty string, i.e. assuming the original version is in variable input and you want to put this in variable output minus the escape sequences:
regsub -all -- {\x1b\[[\d;]*m} $input {} output
For more info see the manual page at https://www.tcl-lang.org/man/tcl8.6/TclCmd/regsub.html

Zsh export ignoring quotes and backslashes

I have a shell script (let's call it produce.sh) which outputs data in the following form, but does not save it to a file:
FOO=value
BAR=value2
ZAP=value3
I'd like to use these values as environment variables in a shell script. I'm currently doing this using the following shell code:
export $(./produce.sh)
This works great, except when the values to the right of the = contain spaces. For instance:
FOO=split value
I've tried two different approaches in produce.sh:
Wrapping the values in quotes (FOO="split value")
Escaping whitespace with backslashes (FOO=split\ value)
Both of these do not work: if I inspect the environment variables, FOO contains "split in the first example and split\ in the second.
How can I get export to handle this correctly?
The f parameter expansion flag in zsh will split an input on newlines, so this should handle input values with whitespace:
export ${(f)"$(./produce.sh)"}
What's happening
The output from produce.sh:
key-value pairs.
each kv pair is on its own line.
key is separated from the value by =.
syntax is shell-like, but not exactly shell syntax, so,
white space and some other characters special to the shell are allowed in the value.
The parts of the substitution:
produce.sh : generates the key-value output, for example: N=V1\nP=V 2\n.
$(...) : command substitution. It is replaced with the output, minus trailing newlines: N=V1\nP=V 2.
"..." : the quotes ensure that the prior result is treated as a single term for the next step.
${(f)...} : expands that single term into multiple scalar values, splitting on newlines due to the (f) flag. The result is effectively 'N=V1' 'P=V 2'.
export : assigns and exports each parameter, acting like export 'N=V1' 'P=V 2'.
Another option
The substitution below adds some other cryptic zsh-isms to create an associative array. This avoids adding arbitrary variables to the consuming shell's
environment:
% ./s2.sh
A=A
B=
C=C C
% typeset -A vals=("${(#s:=:)${(f)"$(./s2.sh)"}}")
% print ${vals[A]}
A
% print ${vals[C]}
C C
A minor tradeoff - this will not work if the value contains an equals, e.g.
D=D=D.
FOO=split value
Does not set a variable to a value containing a space. It temporarily sets FOO to split and runs the command value in an environment where FOO has been set. The overall effect is similar to
(export FOO=split; value)
You can experiment with this by doing a
FOO=split printenv
which will run printenv and show in its output the value of FOO.
To set a variable to a value containing space, you need to escape the space, for instance with
FOO=split\ value
or by using quotes.
For processing this file, you could use eval:
eval export $(./produce.sh)
Of course you should this only if you have full control over what produce.sh is writing to stdout (i.e. don't let any tainted data sneak in), because eval is a potential security hole.
Alternatively, you could modify produce.sh to already produce the export prefix in front of each line, and then do just a
eval $(./produce.sh)

Zsh color inside command substitution

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}'

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.

gnuplot print multiple files with array

I need to plot multiple files in one plot with a bash script. I can manage this by stating the location of the files manually in plot function ie
plot "$outputdir/10/values.csv" using 1:2 with lines title "10", \
"$outputdir/20/values.csv" using 1:2 with lines title "20", \
"$outputdir/30/values.csv" using 1:2 with lines title "30"
This will print all three values files in one plot. But I have a dynamic array which changes depending on how many values it is suppose to get. So lets say the array arr looks like this instead
arr=(10 20 30 40)
#Sometimes arr has more values ex; arr=(10 20 30 40 50 60 70 80)
#arr corresponds to folders that will be created
#And will contain a values.csv file that I want to plot
Then the 40/values.csv will not be printed if I don't manually change the plot code
My current attempt to fix this is a for loop in gnuplot
#The array arr is dynamically generated in another function
#Varies in size as example above
ArrLength=${#arr[#]} #Get the length of array
plot for [p=0:$ArrLength] "$outputdir/${arr[$p]}/values.csv" using 1:2 with lines title "${arr[$p]}"
I don't get a error when plotting but in plot only one value is plotted and thats the first value in array ie it will only plot $outputdir/10/values.csv.
I tried setting p=0:4 to plot the first five files and still it only plotted the first file only. What is wrong with the for loop above?
Best regards
You seem to be mixing bash and gnuplot in a strange way. Using a bash script to try to generate a gnuplot script on the fly with inserted variables is a quick way to confuse yourself. It also makes it difficult to read and debug. It is easy to forget what bash is evaluating and what gnuplot is evaluating.
When I look at your first line
ArrLength=${#arr[#]} #Get the length of array
I can see that this is bash code because gnuplot would interpret a comment beginning with the first #. (This is also bash's syntax for arrays, not gnuplot's.) The dollar sign $ has a different meaning in gnuplot. Rather than mark a variable identifier, $ is a column number operator ($2 is column 2, $i is column i, etc.). So look at the line
plot for [p=0:$ArrLength] "$outputdir/${arr[$p]}/values.csv" using 1:2 with lines title "${arr[$p]}"
This is clearly a line of bash syntax, apparently inside a string trying to write a line of gnuplot. Bash will evaluate the variables $ArrLength, $outputdir, and ${arr[$p]}, and replace them with some string of their values. Also keep in mind that p is a variable in gnuplot, not a variable in bash. Bash will evaluate $p to something (an empty string if it has not been defined). You can't expect the gnuplot variable p to be used as the index in the bash evaluation of ${arr[$p]}, and then somehow result in a different string for each iteration of gnuplot's loop.
In short, what you have written is not gnuplot syntax, and it is really not a minimal and complete bash script either. It is not clear exactly how you intended bash and gnuplot to fit together like this, but it seems you have joined them too tightly.
My suggestion is to write a bash script and write a gnuplot script (as separate files). Gnuplot has its own flow control, iteration loops, and variable evaluation. You can write a self-contained gnuplot script for the general case of everything you need it to do, and then give it specifics on the command line from your bash script.
For example, it seems that your subdirectories are all multiples of 10, and always starting with 10. The only variable aspect is how many there are (what the last one is). Let's say this last value was somehow stored in a gnuplot variable last. Also, suppose we also somehow have the base output directory in outputdir:
(Inside the gnuplot script, named plot.gp):
plot for [p=10:last:10] sprintf("%s/%d/values.csv", outputdir, p) with lines title sprintf("%d", p)
The for [p=10:last:10] means to iterate from 10 through last (inclusive), adding 10 at each iteration. The first sprintf() function (like C) builds a string with the outputdir and p (both are variables in gnuplot). The using 1:2 is not necessary as the first two columns are the default columns to use with lines, but you can include them if you want to be explicit. The second sprintf() builds a title string from the iteration variable p.
Now, this assumes that outputdir and last have meaningful values. You can assign these values from your bash script when you invoke gnuplot on the command line:
(Inside the bash script, invoke the gnuplot script)
gnuplot -e "last=40" -e "outputdir=\"$outputdir\"" plot.gp
The -e option tells gnuplot to evaluate the given string before running the script in the file plot.gp. In this example, the gnuplot variable last will have the value 40 and the gnuplot variable outputdir will have whatever value bash evaluates $outputdir to be. Notice the escaped double quotes inside double quotes. The outer double quotes are to allow bash to evaluate variables inside the string ($outputdir needs to be evaluated by bash). The inner (escaped) quotes are to delimit the string within the gnuplot code. For example, if bash evaluates $outputdir to data, then gnuplot would see outputdir="data" which is a valid gnuplot assignment of a string to the variable outputdir. You could, if you want, combine these two -e options into one:
gnuplot -e "last=40;outputdir=\"$outputdir\"" plot.gp
You will likely want to use the value for last from your array in bash, rather than hard coding it like this. So in practice it may look more like
gnuplot -e "last=${arr[${#arr[#]}-1]};outputdir=\"$outputdir\"" plot.gp
Or, if you have bash 4.3 or later, you should be able to use a negative index:
gnuplot -e "last=${arr[-1]};outputdir=\"$outputdir\"" plot.gp
Notice that there are no escaped quotes around the use of the array variable. It is expected that it will evaluate to an integer (40, 90, etc.) and we want to assign last to an integer, not a string like outputdir.
If this one string seems complex, try thinking about the entire script like this. It would be easy to get confused as to what bash is doing and what gnuplot is doing.
In summary, write a bash script, and a separate gnuplot script. Gnuplot is capable of handling a general case. From bash, just give it some specifics on the fly, don't try to generate the entire script on the fly. It really does make things simpler.

Resources