I am trying to make a script that outputs a status bar for my window manager. It outputs the normal stuff like the time, date, weather, etc.
One of the strings it outputs is the number of updates for the system (Arch Linux). As the "API" where I am pulling the update number from has a max number of requests per day I had to add a delay to the updates() function (that outputs the number of updates) so that that maximum number of requests is not exceeded.
Adding this delay makes the problem start.
Somehow the updates_aur variable is not being stored in memory and cannot be accessed until the delay I implemented is removed. (MORE EXPLANATION IN CODE BELOW)
I would like for a delay to be implemented that makes the updates not being checked every iteration but one in 60.
I tried exporting the "updates_aur" and the "updates_arch" to environment variables so that they would be stored in memory but as the script creates a subshell they are not accessible for them to be updated/retrieved.
updates() {
if [ "$internet" = true ]; then
if ! updates_arch=$(checkupdates 2> /dev/null | wc -l ); then
updates_arch=0
fi
if (( $counter % 60 == 0 )); then #this is done to add a delay and not saturate aur requests
if ! updates_aur=$(yay -Qum --devel 2> /dev/null | wc -l); then
updates_aur=0
fi
else
:
fi
updates=$(("$updates_arch" + "$updates_aur"))
if [ "$updates" -gt 0 ]; then
echo " Updates: $updates"
else
echo " Updates: 0"
fi
echo $delim
else
:
fi
}
This is then called in a while loop (the while loop also increments the counter by 1)
Full code: https://github.com/Baitinq/dwm/blob/master/scripts/dwm-status
I expected the variable of aur_updates to be updated and stored whenever the counter % 60 == 0, but the actual result is that the variable can only be accessed when the counter % 60 == 0, as if it wasn't being stored in memory and updated, but created with every while loop iteration in which the counter % 60 == 0.
Since you're running your functions in a subshell, for example (from the status function):
echo $(updates)
Their variables' (updates_aur) values are lost when the subshell exits. There's no reason to echo a function that contains an echo within it. Just call your function directly:
updates
This occurs in several places with other functions. It's also not necessary there.
I'm so new to programming it hurts! I'm trying to make a very very basic exit out of the termial script. Basically you run the scipt. It will then ask if you want to exit terminal or stay. When I choose either 1 or 2, it just returns to the terminal.
Ideally, I wanted to type "logout" and I would get the same script with the same options, but I've been trying to 2 days and it doesn't make sense.
Thank you in adavance!
#! /bin/bash
# Created by Sarge on Feb. 9, 2015
#This script locks the screen when user logs out
# User must type "logout" and will then have to enter their password
# After the correct password for the user is enter
# the screen will go into lock mode
# clear the screen
clear
# exit options
echo "1. Exit"
echo "2. No"
# exit screen
echo "Exit terminal? (1 = Exit. 2 = No)."
# user input
read user_input
if [ $user_input = 1 ] ; then
exit
elif [ $user_input = 2 ] ; then
exit 0
fi
Thank you again.
The script is run in a different process than the interactive shell. exit therefore ends just the script. Note that the shell may not be what logged the user in and terminating it won't logout the user (eg. when the user runs shell within his shell). If it is enough for you to kill the shell, you can run kill $PPID.
It is quite hard to find what exactly logged the user in. The best approximation would be to climb the process tree up until the parent process is no longer running under the user you want to logout, then kill the child process (or all processes in this subtree to be sure nothing survives). You can use pstree to visualize what you need to do and ps to actually do it.
use == not = operator like so:
if [ $user_input == 1 ] ; then
exit
elif [ $user_input == 2 ] ; then
exit 0
fi
you may also choose to replace this with -eq like so:
if [ $user_input -eq 1 ] ; then
exit
elif [ $user_input -eq 2 ] ; then
exit 0
fi
Reason:
In most programming languages including BASH,
= is known as the assignment operator that assigns value on the right side to the variable on the left side
== is the equality check operator that checks for equality of LHS and RHS and returns either true or false based on comparison result
(This is what is done in the if condition in this program).
Figured out how to run off a key word. Created a bin folder in my main directory. I put my script in bin. Than I tried Alias = "end" = "logout". Now when I just type "end" (no quotes), my script automatically runs!!! Whoop!
I am using Konsole on kubuntu 14.04.
I want to take arguments to this shell-script, and pass it to a command. The code is basically an infinite loop, and I want one of the arguments to the inner command to be increased once every 3 iterations of the loop. Ignoring the actual details, here's a gist of my code:
#!/bin/bash
ct=0
begin=$1
while :
do
echo "give: $begin as argument to the command"
#actual command
ct=$((ct+1))
if [ $ct%3==0 ]; then
begin=$(($begin+1))
fi
done
I am expecting the begin variable to be increased every 3 iterations, but it is increasing in the every iteration of the loop. What am I doing wrong?
You want to test with
if [ $(expr $cr % 3) = 0 ]; then ...
because this
[ $ct%3==0 ]
tests whether the string $ct%3==0, after parameter substitution, is not empty. A good way for understanding this is reading the manual for test and look at the semantics when it is given 1, 2, 3 or more arguments. In your original script, it only sees one argument, in mine it sees three. White space is very important in the shell. :-)
In BASH you can completely utilize ((...)) and refactor your script like this:
#!/bin/bash
ct=0
begin="$1"
while :
do
echo "give: $begin as argument to the command"
#actual command
(( ct++ % 3 == 0)) && (( begin++ ))
done
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
Problem Statement:-
Below is the script that someone else wrote and he left the company so I don't know whom should I ask about this. So that is the reason I am posting here to find the solution.
What this script does is- It gzip the data from a particular folder (/data/ds/real/EXPORT_v1x0) for a particular date (20121017) and move it back to HDFS (hdfs://ares-nn/apps/tech/ds/new/) directory.
date=20121017
groups=(0 '1[0-3]' '1[^0-3]' '[^01]')
for shard in 0 1 2 3 4 5 6 7 8 9 10 11; do
for piece in 0 1 2 3; do
group=${groups[$piece]}
if ls -l /data/ds/real/EXPORT_v1x0_${date}_${shard}_T_${group}*.dat.gz; then
gzip -dc /data/ds/real/EXPORT_v1x0_${date}_${shard}_T_${group}*.dat.gz | \
hadoop jar /export/home/ds/lib/HadoopUtil.jar com.host.hadoop.platform.util.WriteToHDFS -z -u \
hdfs://ares-nn/apps/tech/ds/new/$date/EXPORT-part-$shard-$piece
sleep 15
fi
done
done
So during the migration to HDFS I found out this file has some problem in HDFS-
hdfs://ares-nn/apps/tech/ds/new/20121017/EXPORT-part-8-3
So Is there any way by doing some permutation from the above script we can find out what are the files under this directory (/data/ds/real/EXPORT_v1x0) which ultimately got converted to this hdfs://ares-nn/apps/tech/ds/new/20121017/EXPORT-part-8-3 which has the problem.
Any thoughts?
Update:-
Something like this below?
groups=(0 '1[0-3]' '1[^0-3]' '[^01]')
for shard in 0 1 2 3 4 5 6 7 8 9 10 11; do
for piece in 0 1 2 3; do
group=${groups[$piece]}
if ls -l /data/ds/real/EXPORT_v1x0_${date}_${shard}_T_${group}*.dat.gz; then
[ "$date/EXPORT-part-$shard-$piece" == "20121017/EXPORT-part-8-3" ] && {
echo /data/real/EXPORT_v1x0_${date}_${shard}_T_${group}*.dat.gz
}
fi
done
done
Few Sample Files Format I have in the /data/real/EXPORT folder-
/data/real/EXPORT_v1x0_20121017_4_T_115600_115800.dat.gz
/data/real/EXPORT_v1x0_20121017_4_T_235600_235800.dat.gz
/data/real/EXPORT_v1x0_20121017_4_T_115800_120000.dat.gz
/data/real/EXPORT_v1x0_20121017_4_T_235800_000000.dat.gz
And few sample output that I got after making changes-
/data/real/EXPORT_v1x0_20121017_0_T_0*.dat.gz: No such file or directory
/data/real/EXPORT_v1x0_20121017_0_T_1[0-3]*.dat.gz: No such file or directory
/data/real/EXPORT_v1x0_20121017_0_T_1[^0-3]*.dat.gz: No such file or directory
/data/real/EXPORT_v1x0_20121017_0_T_[^01]*.dat.gz: No such file or directory
In this case reaplce the whole gzip line to:
[ "$date/EXPORT-part-$shard-$piece" == "20121017/EXPORT-part-8-3" ] && {
echo /data/real/EXPORT_v1x0_${date}_${shard}_T_${group}*.dat.gz
}
That should do the trick.
Edit: remove sleep to speed up the loop!