I want to have a standard BASH PS1 prompt that will automatically have a colour set based upon the hostname of the server.
This would meant that whenever logging into a server, it would instantly be clear and familiar that you are on that server as the prompt colour would be different to other servers
A given hostname must always give the same colour
I have found similar ideas with hex codes, but this is specifically for use with BASH colours
The idea would then be that there can be a standard bash prompt code snippet that can be included everywhere and will always give different colours for different servers without any further code changes
In a nutshell, the question is what bash function could you write that will take 2 arguments - a string and a hash. It should echo out the string in a colour that is determined by the hash, and the colour should always be the same for any given hash
EDIT - TO CLARIFY
the answers so far assume that host names are known in advance
I am looking for something that will assign the same random colour deterministically based on whatever host name is for that server
I am definitely not looking for something that requires any kind of code change when installing the PS1 on a new server
This post is along the lines but doesn't seem to have a simple PS1 snippet that I can use https://aweirdimagination.net/2015/02/28/better-hash-based-colors/
I am looking for something that will assign the same random colour deterministically based on whatever host name is for that server
Take a hashing algorithm and compute the hash of the hostname - as to convert a string to numbers. Then use this pseudorandom-number to generate a color.
gen_prompt_function() {
local number
number=$(
# get "random" string that depends on hostname
md5sum <<<"$HOSTNAME" |
# meh - take first byte and convert it to decimal
cut -c-2 | xargs -i printf "%d\n" "0x{}" |
# convert 0-255 range into 30-37 range
awk '{print int($0/255.0*(37-30)+30)}'
)
printf '\[\e[%d;1m\]%s\[\e[m\]' "$number" "$HOSTNAME"
}
PS1="$(gen_prompt_function)"'$ '
Read ANSI escpe sequences, tput and about color handling in terminfo and bash manual controlling prompt. Remember to add \[ \] around color codes.
PS. In my bash adventures solely for hostname prompt coloring I have written a color handling script that generates a rainbow from 3 RGB colors taken from first 18 characters extracted from hash of a string, like in screeshot below. That script is used in my PS1 configuration.
declare -A color=([hosty]=33 [hostr]=31 [hostb]=34 [hostm]=35)
function color () {
host=$1
printf $'\e[%d;1m%s\e[m' "${color[$host]}" "$host"
}
PS1='$(color $HOSTNAME)$ '
Determine what the host name is, then define PS1.
case $HOSTNAME in
foo.com) color='...' ;;
bar.org) color='...' ;;
esac
PS1="..." # using $color as necessary
Related
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
To help myself remember that I am logged into a different system, quickly and visually, I use different colors for the \h field in the Bash PS1 environment variable.
I am looking for a way to reliably generate the PS1, tied to a static host-identifier; say, via the hostid command.
Originally, my PS1, which was adapted from something generated by bashrcgenerator.com, looked like:
export PS1="\u#\[$(tput bold)\]\[$(tput sgr0)\]\[\033[38;5;35m\]\h\[$(tput sgr0)\]\[$(tput sgr0)\]\[\033[38;5;15m\]\[$(tput sgr0)\]:\[\033[38;5;245m\]\w\[$(tput sgr0)\]\[\033[38;5;15m\]\[$(tput sgr0)\]\\$ "
What a mess.
So to start any progress, the first step was to do some refactoring. This landed me at the following script:
bold="$(tput bold)"
reset="$(tput sgr0)"
green="\e[38;5;35m"
gray="\e[38;5;245m"
directory='\w'
host='\h'
user='\u'
function colorize() {
echo -n "${2}${1}${reset}"
}
export PS1="${user}#$(colorize $host $green):$(colorize $directory $gray)\\$ "
At this point, you can at least see what the heck is going on.
Now, I need to write a function like:
get_repeatable_color_for_hostid() {
# hash the $(hostid) into a valid color escape string
# e.g. 16ab1d60 --> \e[38;5;35m
}
In order to do so, I need to understand:
What are the meanings of the field portions xx;y;zzm inside of e.g. \e[38;5;35m?
How do I do the hashing from the hostid to that color escape sequence, s.t. colors are randomized as much as possible.
Regarding colors this page helped me a lot to understand all details: http://misc.flogisoft.com/bash/tip_colors_and_formatting
Since there aren't so many colors to chose from one idea would be to simply assign a color to each of the host, and in case you have more hosts then group the hosts based on importance (dev, prod etc.) each group with it's own color.
There is also this idea to generate the color (from 1 to 256) using the checksum of the hostname: COLOR=$(($(hostname | cksum | cut -f1 -d" ")%256+1))
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.
My current snippet of code looks like this ...
#Location of network config files
nfds="/etc/sysconfig/network-scripts/"
#Standard prefer of network config files
fil="ifcfg-"
#Array variable that feeds "$nic"
cards= array loop built from "nic=$(ls /sys/class/net | grep en)"
#Set color for Divice labile
div="\033[38;5;39m"
#Set Fix format and colour info
fix="\033[38;5;118m"
#Set color for OK
ok="\033[38;5;28m"
#Clear All font and color info
ctf="\033[0m"
function currentCardDefRoute(){
defr=$(grep DEFROUTE $nfds$fil$cards | cut -d = -f 2)
if [[ $defr = "yes" ]] || [[ $defr = "no" ]]; then
echo -e " "$div$cards$ctf"'s current default route is\t"$div$defr$ctf"\t\t\t\t ["$ok"OK"$ctf"]"
$st
else
echo -e " "$div$cards$ctf"'s current default route is \t"$fix"Missing"$ctf"\t\t\t ["$fix"PLEASE FIX"$ctf"]"
$st
fi
}
I indent 1 space on all echo lines for readability and consistent formatting. Keeping output readable and easy to understand.
Im looking to us the "columns" option and make the output more dynamic and have the format consistent no matter the screen size or var result. I would love to also get rid of all the "\t"s in my code. I have tried printf to no success.
I googled a lot of different ways and not seen the specific answer Im looking for or a variation I can draw an answer from.
Thank you for your help.
btw. This is the first code I have ever written so go easy guys :)
You may want to try using the column utility. It's sole purpose is for formatting output into columns. That may be easier than trying to do the same thing with echo or printf.
If you have to use printf, you'll want to use a format specifier like "%25.25s". The first number is the "minimum field width", which (in this case) causes the output to be at least 25 characters wide. If the output is shorter, it's padded with whitespace. The second number indicates the maximum number of characters to print. When these two numbers are the same, it effectively says to print the string in a field that's exactly 25 characters wide. You can use this to force varying-length strings to take up the same amount of space on the screen.
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