BASH: different colour for text entered by user - colors

I've been tidying up my BASH prompt.
Currently it looks like this:
Here is the code:
# NOTE: OSX uses .bashprofile http://superuser.com/questions/244964/mac-os-x-bashrc-not-working
# http://mywiki.wooledge.org/BashFAQ/037
bold=$( tput bold || tput md )
black=$( tput setaf 0 )
red=$( tput setaf 1 )
green=$( tput setaf 2 )
blue=$( tput setaf 4 )
white=$( tput setaf 7 || tput AF 7 )
RESET=$( tput sgr0 )
# https://github.com/sickill/stderred
export DYLD_INSERT_LIBRARIES="/usr/lib/libstderred.dylib${DYLD_INSERT_LIBRARIES:+:$DYLD_INSERT_LIBRARIES}"
export STDERRED_ESC_CODE="$bold$red"
pre_prompt ()
{
if [ $? = 0 ]; then
echo "$green ✔";
else
echo "$red ✘";
fi
printf "$RESET\n"
printf "$bold"
printf "%s#%s ~ %s:\n" "$USER" "$HOSTNAME" "$PWD"
printf "$RESET"
}
# execs before prompt
export PROMPT_COMMAND=pre_prompt
# \[ ... \] --> http://mywiki.wooledge.org/BashFAQ/053
export PS1="\[$bold$blue\] ⤐ \[$RESET$bold\]"
export PS2="-2-> "
export PS3="-3-> "
export PS4="-4-> "
Note I'm using a super little piece of code by sickill to have STDERR get printed in red.
The only thing that I would still like to improve is to hilight text entered by the user.
Is there any way to do this?

After your $RESET, add $bold plus whatever color code you want to use to highlight the commandline entry.

Related

Color code in Bash using for loop in a Linux terminal

When I use color code separately in the echo statement, it works fine.
But I am trying to write 5 colored statements using for loop but it is not working.
What can I do?
#!/bin/bash
for i in {31..35}
do
echo -e "Normal \e[$imColoredText \e[0m"
done
Output of individual code:
Output of Bash script:
echo and printf are both too fragile:
for i in {1..5}; do
echo Normal; tput setaf $i; echo ColoredText; tput setaf 9;
done
You can also do things like:
for i in {1..5}; do printf "Normal%sColoredText%s\n" "$(tput setaf $i)" "$(tput setaf 9)"; done
or:
red=$(tput setaf 1)
normal=$(tput setaf 9)
echo "${normal}Normal${red}Red${normal}"
It is preferable not to use echo -e which is non-standard, but prefer printf instead.
Here it is:
#!/usr/bin/env sh
i=1
while [ $i -le 5 ]; do
printf 'Normal '
tput setaf "$i"
printf 'ColoredText'
tput sgr0
printf '\n'
i=$((i + 1))
done
You need to put your loop variable in curly brackets (variable expansion):
echo -e "Normal \e[${i}mColoredText \e[0m"

Parse error in Bash script

I am trying to make a bash script for each time I set up a new linux system. My problem is I am getting an error saying parse error:
condition expected: =
I think the problem is that it is inside a while loop because it works when I remove it, but the while loop is kinda important for my program so cant just remove it. The script is meant to make a list which shows the options I can do in my script and it is supposed to have colors and a way to check if the option has ben ran before. The script seems to stop working after the line:
while [ $loop = 1 ]; do
When I try to run without the while loop it works so I think this may be a cause of the problem.
loop=1
scriptstatus=(0 0 0 0 0 0 0 0 );
whatitdoes=('Install terminal programs' 'Install security programs' 'Install normal programs' 'Install spotify' 'Customize gnome' 'Install and customize zsh' 'Install etcher' 'Not yet run' 'Not yet run');
#'$(tput bold)$(tput setaf 1)Not yet run$(tput sgr0)'
while [ $loop = 1 ]; do
answer=""
clear
echo "$(tput bold) Script: What script does: Status:$(tput sgr0)"
COUNTER=0
while [ $COUNTER -lt 7 ]; do
if [[ $scriptstatus[$COUNTER] = 0 ]]
then
echo " ["$COUNTER"] "$'\x1d'" ${whatitdoes[$COUNTER]} "$'\x1d'" $(tput bold)$(tput setaf 1)Not yet run$(tput sgr0)"
elif [[ $scriptstatus[$COUNTER] = 1 ]]
then
echo " ["$COUNTER"] "$'\x1d'" ${whatitdoes[$COUNTER]} "$'\x1d'" $(tput bold)$(tput setaf 2)Completed$(tput sgr0)"
else
echo "error"
fi
let COUNTER=COUNTER+1
done | column -t -s$'\x1d'
echo "quit: Exits the program"
echo "Choose one option:"
read answer
done
Turns out I just wrote the array expansion wrongly; instead of:
if [[ $scriptstatus[$COUNTER] = 0 ]]
I had to use:
if [[ ${scriptstatus[$COUNTER]} = 0 ]]
The { and } are required for array subscripting.

BASH printing 2 strings on the same 2 lines

I'm rather new to BASH and I was wondering how could I print 2 strings on the same 2 lines.
What I'm trying to do, is create a 2 line progress-bar in BASH.
Creating 1 line progress bar is rather easy, I do it like this:
echo -en 'Progress: ### - 33%\r'
echo -en 'Progress: ####### - 66%\r'
echo -en 'Progress: ############ - 100%\r'
echo -en '\n'
But now I'm trying to do the same thing but with 2 lines, and everything I tried failed so far.
In the second line, I want to put a "Progress Detail" that tells me at what point in the script it is, like for example: what variable is gathering, what function is it running. But I just can't seem to create a 2 line progress bar.
It's possible to overwrite double lines using tput and printf, for example:
function status() {
[[ $i -lt 10 ]] && printf "\rStatus Syncing %0.0f" "$(( i * 5 ))" ;
[[ $i -gt 10 ]] && printf "\rStatus Completing %0.0f" "$(( i * 5 ))" ;
printf "%% \n" ;
}
for i in {1..20}
do status
printf "%0.s=" $(seq $i) ;
sleep .25 ; tput cuu1 ;
tput el ;
done ; printf "0%%\n" ; printf " %.0s" {1..20} ; printf "\rdone.\n"
one-liner:
for i in {1..20}; do status ; printf "%0.s=" $(seq $i) ; sleep .25 ; tput cuu1 ; tput el ; done ; printf "0%%\n" ; printf " %.0s" {1..20} ; printf "\rdone.\n"
The loop calls the status function to display the appropriate text during a particular time.
The resulting output would be similar to:
Status Completing 70%
==============
You can use \033[F to go to previous line, and \033[2K to erase the current line (just in case your output length changes).
That's the script I did:
echo -en 'Progress: ### - 33%\r'
echo -en "\ntest" # writes progress detail
echo -en "\033[F\r" # go to previous line and set cursor to beginning
echo -en 'Progress: ####### - 66%\r'
echo -en "\n\033[2K" # new line (go to second line) and erase current line (aka the second one)
echo -en "test2" # writes progress detail
echo -en "\033[F\r" # go to previous line and set cursor to beginning
echo -en 'Progress: ############ - 100%\r'
echo -en "\n\033[2K" # new line and erase the line (because previous content was "test2", and echoing "test" doesn't erase the "2")
echo -en "test" # write progress detail
echo -en '\n'

Bash test in pipe results in weird behaviour

I am trying to write a coloured output library for bash with various styling options allowing colouring and styling using redirection.
e.g.
echo "Red" | red outputs red text
and
echo "Bold" | bold outputs bold text
and
echo "Yellow bold" | yellow | bold outputs bold yellow text
The code I wrote so far is as follows:
#shellcheck shell=bash
# set debug
# set -o xtrace
# number of colors supported
__colors=$(tput colors 2> /dev/null)
# colors
__black="$(tput setaf 0)"
__red="$(tput setaf 1)"
__green="$(tput setaf 2)"
__yellow="$(tput setaf 3)"
__blue="$(tput setaf 4)"
__magenta="$(tput setaf 5)"
__cyan="$(tput setaf 6)"
__white="$(tput setaf 7)"
# style
__default="$(tput sgr0)"
__bold="$(tput bold)"
__underline="$(tput smul)"
function has_colors() {
COLOR=${COLOR:-auto}
if [[ $COLOR = 'never' ]]; then
return 1
elif [[ $COLOR = 'always' ]]; then
return 0
else
# check if stoud is terminal and terminal supports colors
[[ -t 1 ]] && \
[[ -n $__colors ]] && \
[[ $__colors -ge 8 ]]
fi
}
function __style() {
read -r input
if has_colors; then
echo -e "$1" "$input" "$__default"
else
echo -e "$input"
fi
}
function black() {
__style "$__black"
}
function red() {
__style "$__red"
}
function green() {
__style "$__green"
}
function yellow() {
__style "$__yellow"
}
function blue() {
__style "$__blue"
}
function magenta() {
__style "$__magenta"
}
function cyan() {
__style "$__cyan"
}
function white() {
__style "$__white"
}
function bold() {
__style "$__bold"
}
function underline() {
__style "$__underline"
}
Setting COLOR=always outputs with escape codes all the time. On the other hand COLOR=auto preforms some checks to make sure current stdout is the terminal and terminal supports colors.
The problem is using multiple styling options does not seem to be working.It always applies the last styling option. For example:
echo "Yellow bold" | yellow | bold outputs bold text, but not yellow.
On the other hand:
echo "Bold yellow" | bold | yellow outputs yellow text, but not bold.
Funny thing is; setting COLOR=always seems to be working just fine. So it looks like the test I perform to see if stdout is terminal [[ -t 1 ]] is causing this. I am not sure if it is because there is some kind of delay with that test. But when I remove [[ -t 1 ]] bit, it works.
Any idea how I can achieve this ? Not expert with Bash or how shell works for that matter. Quite confused here.
Looking at this with a clear head, I found the problem in my approach.
[[ -t 1 ]] tests if stdout is terminal, when I pipe two function like echo "Hello" | yellow | bold , [[ -t 1 ]] is false when going through function yellow denoting output is not a terminal.
Which is because output of the function( yellow) is piped into the second function (bold). That's why it does not output escape code for yellow and just outputs the input.
So If I keep piping to another function like echo "Hello" | yellow | bold | underline it will only underline the output.
This seemed to be a nice and easy way of outputting with colour, but now I have to change my approach unless there is a way to know if currently running function is being piped but not redirected?
EDIT
This post shows there is a way to detect if command is being redirected or being piped.
Still in the end this approach doesn't seem quite feasible because if I do not disable colour when piped it will output colour codes when piped to another command that is not another output styling function; That maybe an edge case, but still solution is not %100 fault proof
EDIT Solution:
Changed the approach. Instead of piping formats one after another using next format options as parameters to first one like below does the trick
echo "Hello" | yellow bold underline
The final code is follows:
#shellcheck shell=bash
# set debug
# set -xv
# number of colors supported
__colors=$(tput colors 2> /dev/null)
# foreground colors
__black="$(tput setaf 0)"
__red="$(tput setaf 1)"
__green="$(tput setaf 2)"
__yellow="$(tput setaf 3)"
__blue="$(tput setaf 4)"
__magenta="$(tput setaf 5)"
__cyan="$(tput setaf 6)"
__white="$(tput setaf 7)"
# background colors
__bg_black="$(tput setab 0)"
__bg_red="$(tput setab 1)"
__bg_green="$(tput setab 2)"
__bg_yellow="$(tput setab 3)"
__bg_blue="$(tput setab 4)"
__bg_magenta="$(tput setab 5)"
__bg_cyan="$(tput setab 6)"
__bg_white="$(tput setab 7)"
# style
__reset="$(tput sgr0)"
__bold="$(tput bold)"
__underline="$(tput smul)"
function has_colors() {
COLOR=${COLOR:-auto}
if [[ $COLOR = 'never' ]]; then
return 1
elif [[ $COLOR = 'always' ]]; then
return 0
else
[[ -t 1 ]] && [[ -n $__colors ]] && [[ $__colors -ge 8 ]]
fi
}
function __format() {
local format="$1"
local next="${2:-}" # next formatting function e.g. underline
if has_colors; then
echo -en "$format"
if [[ -n $next ]]; then
shift 2
tee | "$next" "$#"
else
tee
echo -en "$__reset"
fi
else
tee #print output
fi
}
function black() { __format "$__black" "$#"; }
function red() { __format "$__red" "$#"; }
function green() { __format "$__green" "$#";}
function yellow() { __format "$__yellow" "$#"; }
function blue() { __format "$__blue" "$#"; }
function magenta() { __format "$__magenta" "$#";}
function cyan() { __format "$__cyan" "$#";}
function white() { __format "$__white" "$#";}
function bg_black() { __format "$__bg_black" "$#"; }
function bg_red() { __format "$__bg_red" "$#"; }
function bg_green() { __format "$__bg_green" "$#";}
function bg_yellow() { __format "$__bg_yellow" "$#"; }
function bg_blue() { __format "$__bg_blue" "$#"; }
function bg_magenta() { __format "$__bg_magenta" "$#";}
function bg_cyan() { __format "$__bg_cyan" "$#";}
function bg_white() { __format "$__bg_white" "$#";}
function bold() { __format "$__bold" "$#";}
function underline() { __format "$__underline" "$#"; }

Draw a horizontal line from dash character occupying the full width of a terminal in bash

I need a command that will draw a horizontal "line" in the terminal. The line must be exactly the width of the terminal long (regardless of a current terminal width) and consist of a dash character (although a unicode symbol for a horizontal line can be also used).
It is better if it can be colored.
I need to use it like this:
echo some text
drawline
echo more text
And the output would look something like this:
echo some text
---------------------------------------------------------------------------------
echo more text
Try with:
echo some text
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' -
echo some text
In bash and zsh there is the $COLUMNS variable which you can use.
I use this line for that purpose:
printf %"$COLUMNS"s |tr " " "-"
You can also use seq, but this is not as intuitive as the other solution:
seq -s- $COLUMNS|tr -d '[:digit:]'
Edit:
It seems that $COLUMNS is a local bash variable and you will need to export it. So now there are (at least) 2 options.
Export the variable before you call the script:
export COLUMNS; ./your_script.sh
Use tput as Zumo de Vidrio suggests.
printf %"$(tput cols)"s |tr " " "-"
I'll add some details to the current top answer. You can use the following "hack" (also as suggested here):
printf '%.s─' $(seq 1 $(tput cols))
Notice how this doesn't use the simple dash - but instead the box drawing dash ─. This way consecutive dashes actually connects which prints a nice continuous line:
This nice line ────────────────────────────
And not this one ----------------------------
You can for example use this to show a line before (and/or after) every command registering hooks using zsh
# ~/.zshrc
draw_hline=false
preexec() {
if [[ $1 == ls* ]]; then
hline
draw_hline=true
fi
}
precmd() {
if [ $draw_hline = true ]; then
hline
echo
fi
draw_hline=false
}
Registering hooks using bash can probably be done in a very similar way using this.
A simple Perl one-liner
stty size | perl -ale 'print "-"x$F[1]'
NOTE
you can see the height and width of your terminal with stty size
you can use tput to set colors and printf to print line according to number of columns.
export RED=$(tput setaf 1 :-"" 2>/dev/null)
export GREEN=$(tput setaf 2 :-"" 2>/dev/null)
export YELLOW=$(tput setaf 3 :-"" 2>/dev/null)
export BLUE=$(tput setaf 4 :-"" 2>/dev/null)
export RESET=$(tput sgr0 :-"" 2>/dev/null)
echo $GREEN some text $RESET
echo $RED; printf -- "-%.0s" $(seq $(tput cols)); echo $RESET
echo $YELLOW more text $RESET
Super simple horizontal ruler:
repeat $(tput cols) echo -ne "-";

Resources