Git Bash is pretty sluggish overall (compare 1.082s of average runtime under WSL/Ubuntu vs 4.460s in MinTTY). I've narrowed down a whopping 1.479s to the following chunk of code:
# Determine if this terminal supports colors
if test -t 1; then
if [[ -n "$(tput colors)" ]] && [[ "$(tput colors)" -ge 8 ]]; then
MY_APP_FMT_SUPPORTED=true
MY_APP_FMT_BOLD="$(tput bold)"
MY_APP_FMT_UNDERLINE="$(tput smul)"
MY_APP_FMT_INVERSE="$(tput smso)"
MY_APP_FMT_BLACK="$(tput setaf 0)"
MY_APP_FMT_RED="$(tput setaf 1)"
MY_APP_FMT_GREEN="$(tput setaf 2)"
MY_APP_FMT_YELLOW="$(tput setaf 3)"
MY_APP_FMT_BLUE="$(tput setaf 4)"
MY_APP_FMT_MAGENTA="$(tput setaf 5)"
MY_APP_FMT_CYAN="$(tput setaf 6)"
MY_APP_FMT_WHITE="$(tput setaf 7)"
MY_APP_FMT_CODE=$MY_APP_FMT_CYAN
# placing it down below so that option -x doesn't cause bad highlighting
# to persist
MY_APP_FMT_CLEAR="$(tput sgr0)"
fi
fi
Given my understanding of the performance of *nix tools on Windows, I suspect the slowdown is from all the subshells.
Should these subshells explain the entire slowdown? If not, I'll need to continue researching why Git Bash is still sluggish.
Is there a more performant way to do this while maintaining terminal compatibility?
You can group tput calls by using -S option:
#!/usr/bin/env bash
tkeys=(bold smul "setaf 0" "setaf 1") # You can add the rest
tvalues_s=$(tput -S < <(printf "%s\n" "${tkeys[#]}"))
declare -a tvalues=( ${tvalues_s//$'\e'/ $'\e'} )
declare -p tvalues
Now that you have values in tvalues, which you can assign to MY_APP_FMT_...
Related
I'm trying to write a stupid program that randomizes text color without repeating in bash and I don't know how.
#!/bin/bash
name=$1
number=$2
array1=()
for i in {1..$number}; do
random=$(($RANDOM % 6 + 31))
array1+=($random)
color="\033[0;${random}m"
while["${array1[*]}" =~ "${color}"]; do
random=$(($RANDOM % 6 + 31))
array1[$i]="$random"
done
echo -e $color$name
done
If you want to print name in number different colors, you can use an associative array to ensure each random color is unique
#! /bin/bash
MAX=256
name="$1"
number="$(($2 > MAX ? MAX : $2))"
declare -A colors
while (( ${#colors[#]} < number)); do
color=$((RANDOM % MAX))
if [[ ! "${colors[$color]+?}" ]]; then
colors[$color]=1
printf '%s%s%s\n' "$(tput setaf ${color})" "${name}" "$(tput sgr0)"
fi
done
I set the max to 256 but you can use any number. Also, it's using tput instead of hardcoding escape sequences.
I want to have the same prompt in both Bash and Zsh. And I want it to:
bell a ring, if the last command failed, and
display its error code.
In Bash, I do have:
BLK="\[$(tput setaf 0; tput bold)\]"
RED="\[$(tput setaf 1; tput bold)\]"
grn="\[$(tput setaf 2)\]"
GRN="\[$(tput setaf 2; tput bold)\]"
yel="\[$(tput setaf 3)\]"
reset_color="\[$(tput sgr0)\]"
PS1='\n\
`if [[ $? -gt 0 ]]; then printf "\[\033[01;31m\]$?"; tput bel; else printf "\[\033[01;32m\]0"; fi`\
\[\033]0;$PWD\007\] \
\[\033[0;32m\]\u#\h\
\[\033[01;30m\]:\
\[\033[;;33m\]\w\
\[\033[36m\]`__git_ps1`\
\[\033[0m\]\n$ '
In Zsh, that's my config:
BLK=$(tput setaf 0; tput bold)
RED=$(tput setaf 1; tput bold)
grn=$(tput setaf 2)
GRN=$(tput setaf 2; tput bold)
yel=$(tput setaf 3)
reset_color=$(tput sgr0)
PROMPT="
%(?.$GRN.$RED)%?$reset_color $grn%n#%m$BLK:$reset_color$yel%~ $reset_color
%(!.#.$) "
And this is how it looks like in the terminal:
Both prompts do ring the bell when there is an error with the last command.
But, in Bash, it prints 0 instead of the right return code of the command that
failed.
How to fix that?
PS- Any better way to improve the above code is welcomed!
The command to test $? itself resets $? to the result of the test. You need to save the value you want to display first.
PS1='\n\
$(st=$?; if [[ $st -gt 0 ]]; then printf "\[\033[01;31m\]$st"; tput bel; else printf "\[\033[01;32m\]0"; fi)\
\[\033]0;$PWD\007\] \
\[\033[0;32m\]\u#\h\
\[\033[01;30m\]:\
\[\033[;;33m\]\w\
\[\033[36m\]`__git_ps1`\
\[\033[0m\]\n$ '
I would recommend building up the value of PS1 using PROMPT_COMMAND, instead of embedding executable code. This gives you more flexibility
for commenting and separating any computations you need from the actual
formatting. make_prompt doesn't need quite so many lines, but it's
just a demonstration.
set_title () {
printf '\033]0;%s%s' "$1" "$(tput bel)"
}
make_prompt () {
local st=$?
local c bell
bell=$(tput bel)
# Green for success, red and a bell for failure
if [[ $st -gt 0 ]]; then
c=31
else
c=32 bell=
fi
win_title=$(set_title "$PWD")
git_status=$(__git_ps1)
PS1="\n"
PS1+="\[\e[01;${c}m$bell\]" # exit status color and bell
PS1+=$st
PS1+="\[$win_title\]" # Set the title of the window
PS1+="\[\e[0;32m\]" # Color for user and host
PS1+="\u#\h"
PS1+="\[\e[01;30m\]" # Color for : separator
PS1+=":"
PS1+="\[\e[;;33m\]" # Color for directory
PS1+="\w"
PS1+="\[\e[36m\]" # Color for git branch
PS1+=$git_status
PS1+="\[\e[0m\]" # Reset to terminal defaults
PS1+="\n$ "
}
PROMPT_COMMAND=make_prompt
zsh already has terminal-agnostic escape sequences for adding color.
PROMPT="%B%(?.%F{green}.%F{red}$(tput bel))%?%f%b %F{green}%n#%m%F{black}%B:%b%F{yellow}%~ %f%(!.#.$) "
Any external command (such as printf or tput) resets the value of $?. You need to capture it in a variable before that happens.
rc=$?
if [ $rc -gt 0 ]; then
printf "\[\033[01;31m\]$rc"
tput bel
else
printf "\[\033[01;32m\]0"
fi
(Unwrapped for legibility; this will replace the code inside the backticks.)
Notice that this will still overwrite the value of $?; perhaps add exit $? at the end to properly preserve the value.
biwhite=$(tput bold)$(tput setaf 7)
color_off=$(tput sgr0)
printf "%s$USER%s at %s$HOME%s has path %s$PATH%s" "$biwhite" "$color_off" "$biwhite" "$color_off" "$biwhite" "$color_off"
Is there a printf shortcut to avoid having to define every %s when I want to add color to only certain parts of a statement?
Entering "$biwhite" "$color_off" 3 times seems redundant.
It's good practice to avoid putting parameter expansions in the format string of printf, in case they contain percent signs as well. That said, parameter substitution offers a way around some of the repetitiveness.
w="${biwhite}X${color_off}"
printf "%s at %s has path %s" "${w/X/$USER}" "${w/X/$HOME}" "${w/X/$PATH}"
It's not foolproof, but it's fairly unlikely that X will appear in the output of tput. You can pick a longer string instead, at the cost of more typing.
I'm afraid, though, that adding color codes to a string is inherently painful.
I think you could write a bash function:
biwhite=$(tput bold)$(tput setaf 7)
color_off=$(tput sgr0)
whiten() {
echo "$biwhite$1$color_off"
}
echo "$(whiten "$USER") at $(whiten "$HOME") has path $(whiten "$PATH")"
Also, why use printf if you're not using any of it's formatting capabilities?
Provided below is a function which colorizes every other argument, starting with the second. (Thus, a non-colorized prefix can be provided by passing a non-empty string in this first position, or a colorized one by leaving it empty, as here).
Notably, no subshells are involved in this code's execution except in the tput invocations used for demonstrative purposes. It is thus, while verbose, very low-overhead to execute.
biwhite=$(tput bold)$(tput setaf 7)
color_off=$(tput sgr0)
# colorize every other argument, starting from the 2nd.
colorize_words() {
local start_color end_color
: "${start_color:=$biwhite}" "${end_color:=$color_off}"
while (( $# )); do
printf '%s' "$1"
shift || break
printf '%s%s%s' "$start_color" "$1" "$end_color"
shift || break
done
printf '\n'
}
colorize_words "" "$USER" " at " "$HOME" " has path " "$PATH"
This can be customized by passing start_color and end_color values to an individual invocation; for example:
# this prints every other argument in red
start_color=$(tput setaf 1) colorize_words "hello " "cruel " world
One day, I typed the command
echo "\033[32mHELLOBASE\033[m"
in the gnome bash shell. The terminal showed me a green HELLOBASH string.
I found this interesting. From my experience and serveral tests, I can change
the number 32 from 0 up to 47. Next I wrote the following code,
for i in {0..48};do
echo \033[$imHELLOBASH\[033m
done
Of course, it doesn't work, or I cannot be here! So how to improve the above code to function?
Let's do this the right way -- looking up color codes in our termcap (or, for modern systems, terminfo) database using the tput command:
for ((i=0; i<=48; i++)); do
tput setaf "$i"
echo HELLOBASH
done
If you want to see all available colors on a 256-color terminal, use this code token from BashFAQ #37:
colors256() {
local c i j
printf "Standard 16 colors\n"
for ((c = 0; c < 17; c++)); do
printf "|%s%3d%s" "$(tput setaf "$c")" "$c" "$(tput sgr0)"
done
printf "|\n\n"
printf "Colors 16 to 231 for 256 colors\n"
for ((c = 16, i = j = 0; c < 232; c++, i++)); do
printf "|"
((i > 5 && (i = 0, ++j))) && printf " |"
((j > 5 && (j = 0, 1))) && printf "\b \n|"
printf "%s%3d%s" "$(tput setaf "$c")" "$c" "$(tput sgr0)"
done
printf "|\n\n"
printf "Greyscale 232 to 255 for 256 colors\n"
for ((; c < 256; c++)); do
printf "|%s%3d%s" "$(tput setaf "$c")" "$c" "$(tput sgr0)"
done
printf "|\n"
}
colors256
For additional background on how and why any of this works, see the bash-hackers page on terminal codes.
As for why your original code didn't work even on terminals using ANSI color codes, by the way -- #rici pegged it correctly: Your parameter expansion was ambiguous without adding curly braces.
That is to say:
$imHELLOBASH
...needed to be...
${i}mHELLOBASH
...to avoid the shell trying to find and expand a variable called imHELLOBASH rather than a variable named i.
It is not much to do with bash, more to do with the terminal driver. Rather than trying to use control characters, I suggest you use tput instead, for example:
i=1
while (( $i < 10 ))
do
tput setaf $i
echo "This is $i"
(( i++ ))
done
I'm creating a bash script and would like to display a message with a right aligned status (OK, Warning, Error, etc) on the same line.
Without the colors, the alignment is perfect, but adding in the colors makes the right aligned column wrap to the next line, incorrectly.
#!/bin/bash
log_msg() {
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
NORMAL=$(tput sgr0)
MSG="$1"
let COL=$(tput cols)-${#MSG}
echo -n $MSG
printf "%${COL}s" "$GREEN[OK]$NORMAL"
}
log_msg "Hello World"
exit;
I'm not sure why it'd wrap to the next line -- having nonprinting sequences (the color changes) should make the line shorter, not longer. Widening the line to compensate works for me (and BTW I recommend using printf instead of echo -n for the actual message):
log_msg() {
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
NORMAL=$(tput sgr0)
MSG="$1"
let COL=$(tput cols)-${#MSG}+${#GREEN}+${#NORMAL}
printf "%s%${COL}s" "$MSG" "$GREEN[OK]$NORMAL"
}
You have to account for the extra space provided by the colors.
log_msg() {
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
NORMAL=$(tput sgr0)
MSG="$1"
STATUS="[OK]"
STATUSCOLOR="$GREEN${STATUS}$NORMAL"
let COL=$(tput cols)-${#MSG}+${#STATUSCOLOR}-${#STATUS}
echo -n $MSG
printf "%${COL}s\n" "$STATUSCOLOR"
}