why my function does not execute in PS1? - shell

get_git_branch(){
local branch__=
git branch &> /dev/null
if [ $? -eq 0 ]; then
branch__=`git branch --no-color | sed -ne 's/^\* \(.*\)$/\1/1p' | tr a-z A-Z`
else
branch__="NORMAL"
fi
echo -n $branch__
}
exit_status(){
local smile__=
if [ $? -eq 0 ]; then
smile__='(*´▽`*)'
else
smile__='(╥﹏╥)'
fi
echo -n $smile__
}
export PS1='[\w]\d\t\$\n\u->(`get_git_branch`)`exit_status`:'
This is PS1 setting in my bashrc,I want to check git branch and exit status in my terminal, get_git_branch works every time PS1 refreshed, but exit_status not, whey exit_status not executed?

It absolutely is executed. However, $? is changed by other code that's run before it -- like get_git_branch.
The best practice here is not to embed code where you want detailed flow control in PS1, but rather to use PROMPT_COMMAND.
get_git_branch(){
local branch
if branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); then
printf '%s\n' "${branch^^}" # if on bash 3.2, you may need to use tr instead
else
echo "NORMAL"
fi
}
exit_status(){
if (( ${1:-$?} == 0 )); then
printf '%s' '(*´▽`*)'
else
printf '%s' '(╥﹏╥)'
fi
}
build_prompt() {
last_exit_status_=$?
PS1='[\w]\d\t\$\n\u->($(get_git_branch))$(exit_status "$last_exit_status_"):'
}
PROMPT_COMMAND=build_prompt

Related

How can I pipe output, from a command in an if statement, to a function?

I can't tell if something I'm trying here is simply impossible or if I'm really lacking knowledge in bash's syntax. This is the first script I've written.
I've got a Nextcloud instance that I am backing up daily using a script. I want to log the output of the script as it runs to a log file. This is working fine, but I wanted to see if I could also pipe the Nextcloud occ command's output to the log file too.
I've got an if statement here checking if the file scan fails:
if ! sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all; then
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
This works fine and I am able to handle the error if the system cannot execute the command. The error string above is sent to this function:
Print()
{
if [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "No" ]; then
echo "$1" | tee -a "$log_file"
elif [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "Yes" ]; then
echo "$1" >> "$log_file"
elif [[ "$logging" -eq 0 ]] && [ "$quiet_mode" = "No" ]; then
echo "$1"
fi
}
How can I make it so the output of the occ command is also piped to the Print() function so it can be logged to the console and log file?
I've tried piping the command after ! using | Print without success.
Any help would be appreciated, cheers!
The Print function doesn't read standard input so there's no point piping data to it. One possible way to do what you want with the current implementation of Print is:
if ! occ_output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1); then
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
Print "'occ' output: $occ_output"
Since there is only one line in the body of the if statement you could use || instead:
occ_output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1) \
|| Print "Error: Failed to scan files. Are you in maintenance mode?"
Print "'occ' output: $occ_output"
The 2>&1 causes both standard output and error output of occ to be captured to occ_output.
Note that the body of the Print function could be simplified to:
[[ $quiet_mode == No ]] && printf '%s\n' "$1"
(( logging )) && printf '%s\n' "$1" >> "$log_file"
See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I replaced echo "$1" with printf '%s\n' "$1".
How's this? A bit unorthodox perhaps.
Print()
{
case $# in
0) cat;;
*) echo "$#";;
esac |
if [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "No" ]; then
tee -a "$log_file"
elif [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "Yes" ]; then
cat >> "$log_file"
elif [[ "$logging" -eq 0 ]] && [ "$quiet_mode" = "No" ]; then
cat
fi
}
With this, you can either
echo "hello mom" | Print
or
Print "hello mom"
and so your invocation could be refactored to
if ! sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all; then
echo "Error: Failed to scan files. Are you in maintenance mode?"
fi |
Print
The obvious drawback is that piping into a function loses the exit code of any failure earlier in the pipeline.
For a more traditional approach, keep your original Print definition and refactor the calling code to
if output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1); then
: nothing
else
Print "error $?: $output"
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
I would imagine that the error message will be printed to standard error, not standard output; hence the addition of 2>&1
I included the error code $? in the error message in case that would be useful.
Sending and receiving end of a pipe must be a process, typically represented by an executable command. An if statement is not a process. You can of course put such a statement into a process. For example,
echo a | (
if true
then
cat
fi )
causes cat to write a to stdout, because the parenthesis put it into a child process.
UPDATE: As was pointed out in a comment, the explicit subprocess is not needed. One can also do a
echo a | if true
then
cat
fi

How to echo a shell script to a file in groovy interface like Jenkinsfile

I have the below shell script that i need to echo to a file lets say script.sh from a groovy interface like Jenkinsfile but keep getting compilation errors.
#!/bin/bash
commit_hash=$(git rev-parse HEAD)
parent_hashes=`git rev-list --parents -n 1 $commit_hash`
parent_count=`wc -w <<< $parent_hashes`
if [[ $parent_count -gt 2 ]]
then
p=`git name-rev $parent_hashes | xargs -0 | grep -e '^\S\+ master$'`
if [[ ! -z $p ]]
then
echo "merged branch is master"
exit 0
else
echo "merged branch is anything but master"
exit 2
fi
else
echo "no branch merged"
exit 1
fi
I tried the below :-
sh '''echo '#!/bin/bash
commit_hash=$(git rev-parse HEAD)
parent_hashes=`git rev-list --parents -n 1 $commit_hash`
parent_count=`wc -w <<< $parent_hashes`
if [[ $parent_count -gt 2 ]]
then
p=`git name-rev $parent_hashes | xargs -0 | grep -e '^\S\+ master$'`
if [[ ! -z $p ]]
then
echo "merged branch is master"
exit 0
else
echo "merged branch is anything but master"
exit 2
fi
else
echo "no branch merged"
exit 1
fi' > script.sh'''
I see the shell script has single quotes in a line plus a few back slashes, so not sure why groovy is not allowing normal shell interpolation here.
How do i get to echo the contents of this shell script to a file using groovy. I am trying this out in scripted Jenkinsfile.
You can try using writeFile option to write the content into file, but in your case you have to escape backslash alone in your script. Below should work.
pipeline {
agent any
stages {
stage ("Test") {
steps{
writeFile file:'test.txt', text: '''#!/bin/bash
commit_hash=$(git rev-parse HEAD)
parent_hashes=`git rev-list --parents -n 1 $commit_hash`
parent_count=`wc -w <<< $parent_hashes`
if [[ $parent_count -gt 2 ]]
then
p=`git name-rev $parent_hashes | xargs -0 | grep -e '^\\S\\+ master$'`
if [[ ! -z $p ]]
then
echo "merged branch is master"
exit 0
else
echo "merged branch is anything but master"
exit 2
fi
else
echo "no branch merged"
exit 1
fi'''
}
}
}
}
To write your script into a file use the writeFile step (see here). This will create a file in your workspace from a string.
In a declarative pipeline it looks something like this:
writeFile(file: "fileName", text: "Your Script")

Bash compare a command output to string [duplicate]

This question already has answers here:
How to check if a string contains a substring in Bash
(29 answers)
Closed 3 years ago.
Output is same, and it always echos need to pull.
If I remove the quotes around $text in if condition it throws the too many arguments error.
var="$(git status -uno)" &&
text="On branch master Your branch is up-to-date with 'origin/master'. nothing to commit (use -u to show untracked files)";
echo $var;
echo $text;
if [ "$var" = "$text" ]; then
echo "Up-to-date"
else
echo "need to pull"
fi
Better do this, =~ for bash regex :
#!/bin/bash
var="$(git status -uno)"
if [[ $var =~ "nothing to commit" ]]; then
echo "Up-to-date"
else
echo "need to pull"
fi
or
#!/bin/bash
var="$(git status -uno)"
if [[ $var == *nothing\ to\ commit* ]]; then
echo "Up-to-date"
else
echo "need to pull"
fi
Warning: bash's regex require more ressources and won't work in other shell!
Simple old fashion
This syntax is POSIX compatible, not bash only!
if LANG=C git status -uno | grep -q up-to-date ; then
echo "Nothing to do"
else
echo "Need to upgrade"
fi
Or testing a variable (posix too)
From this answer to How to check if a string contains a substring in Bash, here is a compatible syntax, working under any standard POSIX shell:
#!/bin/sh
stringContain() { [ -z "${2##*$1*}" ] && { [ -z "$1" ] || [ -n "$2" ] ;} ; }
var=$(git status -uno)
if stringContain "up-to-date" "$var" ;then
echo "Up-to-date"
# Don't do anything
else
echo "need to pull"
# Ask for upgrade, see:
fi

Determine previous color used in PS1

Right now my PS1 looks like this
I want that "▸" background color to change depending on the color of the last section. So, if I wasn't in a git repo, it should be blue but when I'm in a git repo, yellow.
Here is what my PS1 looks like in my .bash_profile
# git info on prompt
function __git_info() {
local -r SYMBOL_GIT_BRANCH="⑂";
local -r SYMBOL_GIT_MODIFIED="*";
local -r SYMBOL_GIT_PUSH="↑";
local -r SYMBOL_GIT_PULL="↓";
hash git 2>/dev/null || return 0; # git not found
# current branch reference
local ref=$(git symbolic-ref --short HEAD 2>/dev/null);
# if it's not a normal branch name, get tag name or short unique hash
[[ -z "$ref" ]] && ref=$(git describe --tags --always 2>/dev/null);
[[ -n "$ref" ]] || return 0; #not a git repo
local following; # ahead/behind count
local modified; # whether something has been modified locally
local extras; # additional info
local status; # status of the repo
local untracked; # whether or not there are untracked files
local staged; # whether or not there are staged files
status=$(git status 2>&1 | tee);
untracked=$(printf "%s" "$status" 2> /dev/null | grep -m 1 "Untracked files" &> /dev/null; printf "%s" "$?");
staged=$(printf "%s" "$status" 2> /dev/null | grep -m 1 "Changes to be committed" &> /dev/null; printf "%s" "$?");
[[ "${untracked}" == "0" ]] && extras+="?";
[[ "${staged}" == "0" ]] && extras+="+";
# scan first two lines of output from `git status`
while IFS= read -r line; do
if [[ $line =~ ^## ]]; then #header line
[[ $line =~ ahead\ ([0-9]+) ]] && following+="$SYMBOL_GIT_PUSH${BASH_REMATCH[1]}"
[[ $line =~ behind\ ([0-9]+) ]] && following+="$SYMBOL_GIT_PULL${BASH_REMATCH[1]}"
else #branch is modified if output contains more lines after the header
modified=" $SYMBOL_GIT_MODIFIED";
break;
fi;
done < <(git status --porcelain --branch 2>/dev/null);
# print the git branch segment without a trailing newline
printf "%s" " [$SYMBOL_GIT_BRANCH$following $ref$modified$extras] ";
}
## Prompt customizations ##
function __host() {
printf '\[\e[30;102m\] \h \[\e[0m\]';
}
function __dir() {
printf '\[\e[1;97;44m\] \w \[\e[0m\]';
}
function __git_status() {
printf "\[\e[30;43m\]\$(__git_info)\[\e[0m\]";
}
function __arrow() {
printf '\[\e[1;97;44m\] ▸ \[\e[0m\]';
}
export PS1="$(__host)$(__dir)$(__git_status)$(__arrow) "
Anyone got any ideas how this can be accomplished? I tried setting global vars, but the PS1 is using subshells so that won't work.
Well, your __git_info function returns a status, so why not use it? (Make sure you have it return NON-zero when you ARE is a git repo.) Don't reset the colors in the function, but allow them to remain as they are and reset them after the arrow:
function __dir() {
printf '\[\e[1;97;44m\] \w ';
}
function __git_status() {
local info=$(__git_info)
[ $? -ne 0 ] && printf "\[\e[30;43m\]$info";
}
function __arrow() {
printf ' ▸ \[\e[0m\]';
}
export PS1="$(__host)$(__dir)$(__git_status)$(__arrow) "

Mac OSX | Bash .bash_profile not updating PS1

I have a small script named .bash_prompt which is called by source ~/.bash_prompt in ~/.bash_profile.
The script sets my PS1 to display some useful information about the current git repo.
Unfortunately the git-part is only being executed when spawning a new terminal, so the branch is only displayed when I call the script manually after changing to a git repo.
How can I make my bash prompt update everytime I execute a command?
function git_branch() {
local GITDIR=$(git rev-parse --show-toplevel 2>&1)
if [[ "$GITDIR" != '/Users/\u' ]]
then
local BRANCH=`git branch 2> /dev/null | sed -n '/^\*/s/^\* //p'`
if [ -n "$BRANCH" ]; then
echo -e "$BRANCH"
fi
else
echo ""
fi
}
function git_prompt() {
local prompt_unpushed_symbol="△"
local prompt_unpulled_symbol="▽"
local prompt_dirty_symbol="*"
local prompt_synced_symbol="✓"
local local_branch=$(git_branch)
local remote_branch="origin/$local_branch"
local first_log="$(git log $local_branch $remote_branch -1 2> /dev/null)"
local STATUS=`git status 2>&1`
if [[ "$STATUS" == *'Not a git repository'* ]]; then
echo ""
elif [[ "$STATUS" != *'working directory clean'* ]]; then
echo "[$local_branch $prompt_dirty_symbol]"
elif [[ "$STATUS" == *'Your branch is ahead'* ]]; then
echo "[$local_branch $prompt_unpushed_symbol]"
elif [[ -n "$first_log" ]]; then
echo "[$local_branch $prompt_unpulled_symbol]"
else
echo "[$local_branch $prompt_synced_symbol]"
fi
}
function colorPrompt {
local c_brace="\[\033[m\]"
local c_git="\[\033[31m\]"
local user_host="\[\033[36m\]\u\[\033[m\]#\[\033[32m\]\h"
local location="\[\033[33;1m\]\w"
local tail="\n\$ "
export PS1="[$user_host $location$c_brace]$c_git$(git_prompt)$c_brace$tail"
}
colorPrompt
The value of the PROMPT_COMMAND shell variable is executed prior to displaying the prompt; one of the main uses of this feature is to set the value of PS1. In your case, all you need to do is add
PROMPT_COMMAND=color_prompt
to your .bash_profile after sourcing .bash_prompt.

Resources