Having trouble updating my global variable in this shell script.
I have read that the variables inside the loops run on a sub shell. Can someone clarify how this works and what steps I should take.
USER_NonRecursiveSum=0.0
while [ $lineCount -le $(($USER_Num)) ]
do
thisTime="$STimeDuration.$NTimeDuration"
USER_NonRecursiveSum=`echo "$USER_NonRecursiveSum + $thisTime" | bc`
done
That particular style of loop does not run in a sub-shell, it will update the variable just fine. You can see that in the following code, equivalent to yours other than adding things that you haven't included in the question:
USER_NonRecursiveSum=0
((USER_Num = 4)) # Add this to set loop limit.
((lineCount = 1)) # Add this to set loop control variable initial value.
while [ $lineCount -le $(($USER_Num)) ]
do
thisTime="1.2" # Modify this to provide specific thing to add.
USER_NonRecursiveSum=`echo "$USER_NonRecursiveSum + $thisTime" | bc`
(( lineCount += 1)) # Add this to limit loop.
done
echo ${USER_NonRecursiveSum} # Add this so we can see the final value.
That loop runs four times and adds 1.2 each time to the value starting at zero, and you can see it ends up as 4.8 after the loop is done.
While the echo command does run in a sub-shell, that's not an issue as the backticks explicitly capture the output from it and "deliver" it to the current shell.
I've been trying to customize my Bash prompt so that it will look like
[feralin#localhost ~]$ _
with colors. I managed to get constant colors (the same colors every time I see the prompt), but I want the username ('feralin') to appear red, instead of green, if the last command had a nonzero exit status. I came up with:
\e[1;33m[$(if [[ $? == 0 ]]; then echo "\e[0;31m"; else echo "\e[0;32m"; fi)\u\e[m#\e[1;34m\h \e[0;35m\W\e[1;33m]$ \e[m
However, from my observations, the $(if ...; fi) seems to be evaluated once, when the .bashrc is run, and the result is substituted forever after. This makes the name always green, even if the last exit code is nonzero (as in, echo $?). Is this what is happening? Or is it simply something else wrong with my prompt? Long question short, how do I get my prompt to use the last exit code?
As you are starting to border on a complex PS1, you might consider using PROMPT_COMMAND. With this, you set it to a function, and it will be run after each command to generate the prompt.
You could try the following in your ~/.bashrc file:
PROMPT_COMMAND=__prompt_command # Function to generate PS1 after CMDs
__prompt_command() {
local EXIT="$?" # This needs to be first
PS1=""
local RCol='\[\e[0m\]'
local Red='\[\e[0;31m\]'
local Gre='\[\e[0;32m\]'
local BYel='\[\e[1;33m\]'
local BBlu='\[\e[1;34m\]'
local Pur='\[\e[0;35m\]'
if [ $EXIT != 0 ]; then
PS1+="${Red}\u${RCol}" # Add red if exit code non 0
else
PS1+="${Gre}\u${RCol}"
fi
PS1+="${RCol}#${BBlu}\h ${Pur}\W${BYel}$ ${RCol}"
}
This should do what it sounds like you want. Take a look a my bashrc's sub file if you want to see all the things I do with my __prompt_command function.
If you don't want to use the prompt command there are two things you need to take into account:
getting the value of $? before anything else. Otherwise it'll be overridden.
escaping all the $'s in the PS1 (so it's not evaluated when you assign it)
Working example using a variable
PS1="\$(VALU="\$?" ; echo \$VALU ; date ; if [ \$VALU == 0 ]; then echo zero; else echo nonzero; fi) "
Working example without a variable
Here the if needs to be the first thing, before any command that would override the $?.
PS1="\$(if [ \$? == 0 ]; then echo zero; else echo nonzero; fi) "
Notice how the \$() is escaped so it's not executed right away, but each time PS1 is used. Also all the uses of \$?.
Compact solution:
PS1='... $(code=${?##0};echo ${code:+[error: ${code}]})'
This approach does not require PROMPT_COMMAND (apparently this can be slower sometimes) and prints [error: <code>] if the exit code is non-zero, and nothing if it's zero:
... > false
... [error: 1]> true
... >
Change the [error: ${code}] part depending on your liking, with ${code} being the non-zero code to print.
Note the use of ' to ensure the inline $() shell gets executed when PS1 is evaluated later, not when the shell is started.
As bonus, you can make it colorful in red by adding \e[01;31m in front and \e[00m after to reset:
PS1='... \e[01;31m$(code=${?##0};echo ${code:+[error: ${code}]})\e[00m'
--
How it works:
it uses bash parameter substitution
first, the ${?##0} will read the exit code $? of the previous command
the ## will remove any 0 pattern from the beginning, effectively making a 0 result an empty var (thanks #blaskovicz for the trick)
we assign this to a temporary code variable as we need to do another substitution, and they can't be nested
the ${code:+REPLACEMENT} will print the REPLACEMENT part only if the variable code is set (non-empty)
this way we can add some text and brackets around it, and reference the variable again inline: [error: ${code}]
I wanted to keep default Debian colors, print the exact code, and only print it on failure:
# Show exit status on failure.
PROMPT_COMMAND=__prompt_command
__prompt_command() {
local curr_exit="$?"
local BRed='\[\e[0;91m\]'
local RCol='\[\e[0m\]'
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
if [ "$curr_exit" != 0 ]; then
PS1="[${BRed}$curr_exit${RCol}]$PS1"
fi
}
The following provides a leading green check mark when the exit code is zero and a red cross in all other cases. The remainder is a standard colorized prompt. The printf statements can be modified to present the two states that were originally requested.
PS1='$(if [ $? -eq 0 ]; then printf "\033[01;32m""\xE2\x9C\x93"; else printf "\033[01;31m""\xE2\x9C\x95"; fi) \[\e[00;32m\]\u#\h\[\e[00;30m\]:\[\e[01;33m\]\w\[\e[01;37m\]\$ '
Why didn't I think about that myself? I found this very interesting and added this feature to my 'info-bar' project. Eyes will turn red if the last command failed.
#!/bin/bash
eyes=(O o ∘ ◦ ⍤ ⍥) en=${#eyes[#]} mouth='_'
face () { # gen random face
[[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
if [[ $1 ]]; then printf "${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}"
else printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
fi
}
info () { error=$?
[[ -d .git ]] && { # If in git project folder add git status to info bar output
git_clr=('GIT' $(git -c color.ui=always status -sb)) # Colored output 4 info
git_tst=('GIT' $(git status -sb)) # Simple output 4 test
}
printf -v line "%${COLUMNS}s" # Set border length
date=$(printf "%(%a %d %b %T)T") # Date & time 4 test
test=" O_o $PWD ${git_tst[*]} $date o_O " # Test string
step=$[$COLUMNS-${#test}]; [[ $step -lt 0 ]] && step=0 # Count spaces
line="$GRN${line// /-}$DEF\n" # Create lines
home="$BLD$BLU$PWD$DEF" # Home dir info
date="$DIM$date$DEF" # Colored date & time
#------+-----+-------+--------+-------------+-----+-------+--------+
# Line | O_o |homedir| Spaces | Git status | Date| o_O | Line |
#------+-----+-------+--------+-------------+-----+-------+--------+
printf "$line $(face) $home %${step}s ${git_clr[*]} $date $(face) \n$line" # Final info string
}
PS1='${debian_chroot:+($debian_chroot)}\n$(info)\n$ '
case "$TERM" in xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)} $(face 1) \w\a\]$PS1";;
esac
Improved demure answer:
I think this is important because the exit status is not always 0 or 1.
if [ $EXIT != 0 ]; then
PS1+="${Red}${EXIT}:\u${RCol}" # Add red if exit code != 0
else
PS1+="${Gre}${EXIT}:\u${RCol}" # Also displays exit status
fi
To preserve the original prompt format (not just colors),
you could append following to the end of file ~/.bashrc:
PS1_ORIG=$PS1 # original primary prompt value
PROMPT_COMMAND=__update_prompt # Function to be re-evaluated after each command is executed
__update_prompt() {
local PREVIOUS_EXIT_CODE="$?"
if [ $PREVIOUS_EXIT_CODE != 0 ]; then
local RedCol='\[\e[0;31m\]'
local ResetCol='\[\e[0m\]'
local replacement="${RedCol}\u${ResetCol}"
# Replace username color
PS1=${PS1_ORIG//]\\u/]$replacement}
## Alternative: keep same colors, append exit code
#PS1="$PS1_ORIG[${RedCol}error=$PREVIOUS_EXIT_CODE${ResetCol}]$ "
else
PS1=$PS1_ORIG
fi
}
See also the comment about the alternative approach that preserves username color and just appends an error code in red to the end of the original prompt format.
You can achieve a similar result to include a colored (non-zero) exit code in a prompt, without using subshells in the prompt nor prompt_command.
You color the exit code portion of the prompt, while having it only appear when non-zero.
Core 2$ section of the prompt: \\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]
Key elements:
return code, if not 0: \${?#0} (specificly "removes prefix of 0")
change color without adding to calculated prompt-width: \\[\\033[0;31m\\]
\\[ - begin block
\\033 - treat as 0-width, in readline calculations for cmdline editing
[0;31;4m - escape code, change color, red fg, underline
\\] - end block
Components:
\\[\\033[0;31;4m\\] - set color 0;31m fg red, underline
\${?#0} - display non-zero status (by removing 0 prefix)
\\[\\033[0;33m\\] - set color 0;33m fg yellow
\$ - $ or # on EUID
\\[\\033[0m\\] - reset color
The full PS1 I use (on one host):
declare -x PS1="\\[\\033[0;35m\\]\\h\\[\\033[1;37m\\] \\[\\033[0;37m\\]\\w \\[\\033[0;33m\\]\\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]"
Note: this addresses a natural extension to this question, in a more enduring way then a comment.
Bash
function my_prompt {
local retval=$?
local field1='\u#\h'
local field2='\w'
local field3='$([ $SHLVL -gt 1 ] && echo \ shlvl:$SHLVL)$([ \j -gt 0 ] && echo \ jobs:\j)'"$([ ${retval} -ne 0 ] && echo \ exit:$retval)"
local field4='\$'
PS1=$'\n'"\e[0;35m${field1}\e[m \e[0;34m${field2}\e[m\e[0;31m${field3}\e[m"$'\n'"\[\e[0;36m\]${field4}\[\e[m\] "
}
PROMPT_COMMAND="my_prompt; ${PROMPT_COMMAND}"
Zsh
PROMPT=$'\n''%F{magenta}%n#%m%f %F{blue}%~%f%F{red}%(2L. shlvl:%L.)%(1j. jobs:%j.)%(?.. exit:%?)%f'$'\n''%F{cyan}%(!.#.$)%f '
Images of prompt
Program shall ask the secret key to run the program; a user should program this secret key in advance. If the user enters correct secret key it should move to next step (3), else it should prompt to enter correct key for five times and then exit the program
You may be trying to implement something like this
#!/bin/bash
secretKey="qwerty1234"
failcount=5
success=0
while [ $failcount -gt 0 ]
do
echo "please enter secretKey"
read inp
if [ $inp = $secretKey ]
then
success=1
break
else
((failcount--))
echo $failcount" tries remaining"
fi
done
if [ $success = 0 ]
then
exit 1
fi
echo "code runs here"
The above code has a preset secret key written into the script (in this case qwerty1234)
The program loops 5 times as determined by the failcount variable.
If the password is entered correctly, the success variable is set to 1 and the code executes.
If the password is incorrect 5 times, the loop ends with the success variable as 0.
This causes the program to exit with error code 1
Hope this helped, however please try to provide a clearer example, possibly with some basic implementation in the future.
I have an array of docker containers, arr=(testfoler1 testfoler2 testfoler3 testfoler1)
i know testfoler1 has file notify.txt at location /tmp/, i.e. /tmp/notify.txt.
testfoler2 and testfoler3, are empty.
Now my requirement is once this file is found i will stop the container and will remove that container from arr.
So the flow should be like this.
step 1: /tmp/notify.txt is found in testfoler1 and the new array will be,
arr=(testfoler2 testfoler3 testfoler1)
step 2: it will search for testfolder2 and testfolder3, but as no file is there no action is performed.
step 3: as it reaches to testfoler1 which is at the 2nd index, it will find the notify.txt file and it should remove it from array.
And my last expected array would be (testfoler2 testfoler3) and the loop should keep on running until the file is found or i stop the script.
My script runs successfully til it iterate to (testfoler1 testfoler2
testfoler3). Issue start coming when my array becomes (testfoler2
testfoler3 testfoler1). Here it work fine for testfolder2 and
testfolder3 as file is not found but when it reaches to testfolder1,
instead of removing the testfolder1, it removes testfolder2 and array becomes
(testfoler3 testfoler1) instead of (testfoler2 testfoler3) And then
keep iterating and then it removes testfolder3 and then
testfoler1.However it should have deleted testfolder1 because it had
the file and should have kept running for testfolder2 and testfolder3.
Please refer the code i have tried with:
FILE=/tmp/notify.txt
arr=(testfoler1 testfoler2 testfoler3 testfoler1)
sizeOfArray="${#arr[#]}"
index=0
while [ ! $sizeOfArray -eq 0 ]
do
sizeOfArray="${#arr[#]}"
test=`sudo docker container diff ${arr[index]}|grep $FILE|wc -l`
if [ $test = 1 ]; then
echo "notify.txt is found in container ${arr[index]}"
##Get array length
sizeOfArray="${#arr[#]}"
sudo docker stop ${arr[index]}
sudo docker container ls -a|grep ${arr[index]}
###################Issue seems to be here
unset arr[${arr[index]}]
arr=( "${arr[#]}" )
##################Need some help on code above
echo "When file is FOUND, name of all array elements ${arr[*]}"
echo "Size of array after deletion *********** "${#arr[#]}""
sizeOfArray="${#arr[#]}"
index=$((index + 1))
if [ $index -gt $sizeOfArray ] ; then
index=0
fi
continue
else
echo "notify file is not created in ${arr[index]}"
echo "When file is NOT found, name of all array elements ${arr[*]}"
index=$((index + 1))
if [ $index -ge $sizeOfArray ]; then
echo "Index value is greater/equal size of suites"
index=0
fi
fi
done
This does not delete element index of arr:
unset arr[${arr[index]}]
If that's what you intended to do, you should use:
unset arr[index]
What I try to achieve, is to define global variables in my script. These variables can be reused in a loop (preferably a while loop..) and with every iteration, the loop should get a new set a variables.
My script (so far):
PACKAGE_ASSET_ID=AUTO`date +%s`000001
TITLE_ASSET_ID=AUTO`date +%s`000002
MOVIE_ASSET_ID=AUTO`date +%s`000003
PREVIEW_ASSET_ID=AUTO`date +%s`000004
POSTER_ASSET_ID=AUTO`date +%s`000005
while read name; do
#DATE=`date +%s`
#PACKAGE_ASSET_ID="AUTO${DATE}000001"
#TITLE_ASSET_ID="AUTO${DATE}000002"
#MOVIE_ASSET_ID="AUTO${DATE}000003"
#PREVIEW_ASSET_ID="AUTO${DATE}000004"
#POSTER_ASSET_ID="AUTO${DATE}000005"
echo $PACKAGE_ASSET_ID
echo $TITLE_ASSET_ID
echo $MOVIE_ASSET_ID
echo $PREVIEW_ASSET_ID
echo $POSTER_ASSET_ID
done <names.txt
Within the file names.txt, there are 15 entries. For every entry, the while loop needs to process these sets of variables. Giving me something like
AUTO1521884581000001
AUTO1521884581000002
AUTO1521884581000003
AUTO1521884581000004
AUTO1521884581000005
AUTO1521884592000001
AUTO1521884592000002
AUTO1521884592000003
AUTO1521884592000004
AUTO1521884592000005
As you can see in the script, I tried putting it into the while loop and with different syntax but regretfully, without success. The results I get are always the same set of variables, for all the 15 entries.
Did you really expect that bash (even bash!) would need more than a second to read one line?? Try adding the nanoseconds.
while read name; do
DATE=$(date +%s%N)
PACKAGE_ASSET_ID="AUTO${DATE}000001"
TITLE_ASSET_ID="AUTO${DATE}000002"
MOVIE_ASSET_ID="AUTO${DATE}000003"
PREVIEW_ASSET_ID="AUTO${DATE}000004"
POSTER_ASSET_ID="AUTO${DATE}000005"
echo $PACKAGE_ASSET_ID
echo $TITLE_ASSET_ID
echo $MOVIE_ASSET_ID
echo $PREVIEW_ASSET_ID
echo $POSTER_ASSET_ID
done <names.txt