Pause ‘for’ after every 5 loops - bash

I’ve got this bash script to download 52k files:
for i in {1..52000};
do wget -c "download.hebrewbooks.org/downloadhandler.ashx?req=$i" ;
done
However the server gives me an 429 error.
How can I pause the loop for X amount of time after every 5 files that are downloaded?

If i is a multiple of five, sleep.
for i in {1..52000}; do
wget -c "download.hebrewbooks.org/downloadhandler.ashx?req=$i"
((i % 5)) || sleep $X
done
Note that ((expr)) returns the Boolean value of expr, where false=0 and true=1, which is the opposite of normal Bash return codes. That's why you have to use OR || instead of AND &&. If that's too confusing, use this instead: ((i % 5 == 0)) && ...

Related

Retry a command only once : when a command fails (in bash)

for ( i=3; i<5; i++)
do
execute some command 1
if command 2 is successful then do not run the command 1 (the for loop should continue)
if command 2 is not successful then run command 1 only once (like retry command 1 only once, after this the for loop should continue)
done
This is to note that command 2 is dependent on command 1 and command 2 can only be executed after command 1
for example:
for ( i=3; i<5; i++)
do
echo "i" >> mytext.txt ---> command 1
if "check the content of mytext.txt file to see if the value of i is actually added" ---> command 2
if it is not added then execute echo "i" >> mytext.txt (command 1) again and only once.
if i value is added to the file .. then exit and continue the loop
done
Since the "command 1" is quite big and not just an example echo statement here.I do not want to add "command 1" twice .. once outside and once inside the if condition. I want this logic in an optimized way with no redundancy of code.
Per a comment it sounds like the OP may need to invoke command 1 up to 2 times for a given $i value, but only wants to type command 1 once in the script.
Siddhartha's suggestion to use a function is probably good enough but depending on the actual command 1 (OP mentions that it's 'quite big') I'm going to play devil's advocate and assume there could be additional issues with passing some args to the function (eg, a need to escape some characters ... ??).
The general idea is to have an internal loop that can be executed at most 2 times, with logic in the loop that will allow for an 'early' exit (eg, after just one pass through the loop).
Since we're using pseudo-code I'll use the same ...
for ( i=3; i<5; i++ )
do
pass=1 # reset internal loop counter
while ( pass -le 2 )
do
echo "i" >> mytext.txt # command 1
if ( pass -eq 1 ) # after first 'command 1' execution
&& ( value of 'i' is in mytext.txt ) # command 2
then
break # break out of inner loop; alternatively ...
# pass=10 # ensure pass >= 2 to force loop to exit on this pass
fi
pass=pass+1 # on 1st pass set pass=2 => allows another pass through loop
# on 2nd pass set pass=3 => will force loop to exit
done
done
you can declare functions like
function command
{
your_command -f params
}
for ( i=3; i<5; i++)
do
if command ; then
echo "success"
else
echo "retry"
command
fi
done

What's the difference between these two bash parallelization syntax?

Value "4" below is the number of CPU threads. Idea is to run the tasks in batch of 4 and wait until the current batch is finished before starting the next batch.
Syntax 1:
while read something; do
((++i%4==0)) && wait
(
task using something as input;
)
done < input_file.txt
Syntax 2:
while read something; do
((i=i%4)); ((i++==0)) && wait
(
task using something as input;
)
done < input_file.txt
To me they both work the same except the second one is longer. But when running in the cloud (AWS ubuntu 14.04), only syntax 1 worked. The syntax2 threw a generic syntax error at "((i=i%4));" step and it became a mystery.
"The second one is longer" doesn't help since you used pseudocode.
Maybe this will help:
while read x; do ((i=++i%4)) || wait; sleep $x & done < input_file.txt
My input_file.txt:
10
9
8
7
6
5
4
3
2
1

Variables comparision

I want to write a script with several commands and get the combination result of all them:
#!/bin/bash
command1; RET_CMD1=$(echo $?)
command2; RET_CMD2=$(echo $?)
command3; RET_CMD3=$(echo $?)
\#result is error if any of them fails
\#could I do something like:
RET=RET_CMD1 && RET_CMD2 && RET_CMD3 *<- this is the part that I can't remember how I did in the past..*
echo $RET
Thanks for your help!
I think you're just looking for this:
if ! { command1 && command2 && command3; }; then
echo "one of the commands failed"
fi
The result of the block { command1 && command2 && command3; } will be 0 (success) only if all of the commands exited successfully. The semicolon is needed if the block is all written on one line.
There is no need to save the return codes to variables, or even to refer to $?, since if works based on the return code of a command (or list of commands).
So to think about this...
we want to return 0 on success... or some other positive integer if an error occurred with one of the commands.
If no error occurred with any 3, they would all return 0, which means you would also return 0 in your script. Some simple addition can resolve this.
RET=$[RET_CMD1 + RET_CMD2 + RET_CMD3] # !
echo $RET
You can also replace the first line (!) with logical or operator, as you mentioned.
RET=$[RET_CMD1 | RET_CMD2 | RET_CMD3]
Note that addition and logical or are different in nature. But you seemed to want the logical or...
Disadvantages of this setup: Not being able to trace where the error occurred from the return value. Tracing errors from either 3 commands will need to rely on other error output generated. (This is just a forewarning.)

Do calculation in the Makefile

I got confused with Makefile. I am trying to run a simple command in the Makefile but it gives me the error "/bin/bash: line 3: :=: command not found". I am using shell to run this makefile
This is my part of my Makefile:
all:
vlog Benchmarks/$(NAME)/Syn/*.v
$(eval tux_number := 1)
$(eval range := 1)
$(eval ssh_log := 255)
echo "Start Range: ${range}"
echo "tux-number: ${tux_number}"
while [[ $$range -le 50 ]] ; do \
ssh -l yazdanbakhsh tux-$(tux_number).cae.wisc.edu exit ; \
echo "range: ${range}" ; \
eval $$range := $$((${range}+1)) ; \
done
Thanks
all:
#range=1; \
while [ $$range -le 10 ] ; \
do echo Range: $$range; \
let range=range+1 ; \
done;
Note that the whitespace in front of #range... is the only TAB.
Just to fix your obvious problems with Makefile syntax, here is an attempt at refactoring your attempt into valid code.
tux_number := 1
ssh_log := 255 # not used anywhere
all:
vlog Benchmarks/$(NAME)/Syn/*.v
echo "Start Range: 1" # This is probably no longer very useful output
echo "tux-number: ${tux_number}"
range=1; while [ $$range -le 50 ] ; do \
ssh -l yazdanbakhsh tux-$(tux_number).cae.wisc.edu exit ; \
echo "range: $$range" ; \
range=$$(expr "$$range + 1); \
done
Notice how tux_number and ssh_log are Makefile variables, while range only exists in the shell which executes the while loop. I have avoided the Bashisms in order to make this portable. (If portability is not important, you might want to refactor it back to Bash syntax and use for ((range=1; range<=50; range++)); do... instead.)
Your use of eval is misguided. As you can see, I simply lifted out the Makefile variables outside the recipe where they don't belong. What you were doing was (1) have Make evaluate the expression range := 1 (which evaluates to itself) and (2) use the output as a shell command in a recipe. Since it's not a valid shell command, you got the syntax error from Bash. Without further ado, I'll just take the easy way out here and say that eval is a complex subject, and until you get more experience with Make, it's probably just best to forget that it exists.
In order to properly make use of Make's facilities, I would make this parallelizable, i.e. split it up into 50 individual targets. This is a bit clumsy (there's probably a better way to define range here), but at least it should illustrate a number of differences to your approach. (If you don't insist on having range count up from 1, making it zero-based would make this a little less clumsy. This exploits the fact that the empty string is harmless in a shell snippet, so we can use it instead of a zero prefix. Again, this could be simplifed if you don't care about the human readability of the range index.)
digits := 0 1 2 3 4 5 6 7 8 9
deca := "" 1 2 3 4
range := $(filter-out ""0,$(foreach d,$(deca),$(foreach i,$(digits),$d$i))) 50
# Or, at the expense of an external process,
# range := $(shell perl -le 'print $$_ for 1..50')
.PHONY: all
all: $(patsubst %,ssh-%,$(range))
.PHONY: ssh-%
ssh-%:
ssh -l yazdanbakhsh tux-$(tux_number).cae.wisc.edu exit
echo "range: $*"
This can be run with something like make -j 5 to execute these in parallel batches of five, for example.
Incidentally, the commented-out $(shell ...) call might be the actual answer to your question, if what you really wanted to do was to use Make to drive an external program to calculate something for you.

How can the last command's wall time be put in the Bash prompt?

Is there a way to embed the last command's elapsed wall time in a Bash prompt? I'm hoping for something that would look like this:
[last: 0s][/my/dir]$ sleep 10
[last: 10s][/my/dir]$
Background
I often run long data-crunching jobs and it's useful to know how long they've taken so I can estimate how long it will take for future jobs. For very regular tasks, I go ahead and record this information rigorously using appropriate logging techniques. For less-formal tasks, I'll just prepend the command with time.
It would be nice to automatically time every single interactive command and have the timing information printed in a few characters rather than 3 lines.
This is minimal stand-alone code to achieve what you want:
function timer_start {
timer=${timer:-$SECONDS}
}
function timer_stop {
timer_show=$(($SECONDS - $timer))
unset timer
}
trap 'timer_start' DEBUG
PROMPT_COMMAND=timer_stop
PS1='[last: ${timer_show}s][\w]$ '
Using your replies and some other threads, I wrote this prompt which I want to share with you. I took a screenshot in wich you can see :
White : Last return code
Green and tick mark means success (return code was 0)
Red and cross mark means error (return code was >0)
(Green or Red) : Last command execution time in parenthesis
(Green or Red) : Current date time (\t)
(Green if not root, Red if root) : the logged username
(Green) : the server name
(Blue) : the pwd directory and the usual $
Here is the code to put in your ~/.bashrc file :
function timer_now {
date +%s%N
}
function timer_start {
timer_start=${timer_start:-$(timer_now)}
}
function timer_stop {
local delta_us=$((($(timer_now) - $timer_start) / 1000))
local us=$((delta_us % 1000))
local ms=$(((delta_us / 1000) % 1000))
local s=$(((delta_us / 1000000) % 60))
local m=$(((delta_us / 60000000) % 60))
local h=$((delta_us / 3600000000))
# Goal: always show around 3 digits of accuracy
if ((h > 0)); then timer_show=${h}h${m}m
elif ((m > 0)); then timer_show=${m}m${s}s
elif ((s >= 10)); then timer_show=${s}.$((ms / 100))s
elif ((s > 0)); then timer_show=${s}.$(printf %03d $ms)s
elif ((ms >= 100)); then timer_show=${ms}ms
elif ((ms > 0)); then timer_show=${ms}.$((us / 100))ms
else timer_show=${us}us
fi
unset timer_start
}
set_prompt () {
Last_Command=$? # Must come first!
Blue='\[\e[01;34m\]'
White='\[\e[01;37m\]'
Red='\[\e[01;31m\]'
Green='\[\e[01;32m\]'
Reset='\[\e[00m\]'
FancyX='\342\234\227'
Checkmark='\342\234\223'
# Add a bright white exit status for the last command
PS1="$White\$? "
# If it was successful, print a green check mark. Otherwise, print
# a red X.
if [[ $Last_Command == 0 ]]; then
PS1+="$Green$Checkmark "
else
PS1+="$Red$FancyX "
fi
# Add the ellapsed time and current date
timer_stop
PS1+="($timer_show) \t "
# If root, just print the host in red. Otherwise, print the current user
# and host in green.
if [[ $EUID == 0 ]]; then
PS1+="$Red\\u$Green#\\h "
else
PS1+="$Green\\u#\\h "
fi
# Print the working directory and prompt marker in blue, and reset
# the text color to the default.
PS1+="$Blue\\w \\\$$Reset "
}
trap 'timer_start' DEBUG
PROMPT_COMMAND='set_prompt'
Another very minimal approach is:
trap 'SECONDS=0' DEBUG
export PS1='your_normal_prompt_here ($SECONDS) # '
This shows the number of seconds since the last simple command was started. The counter is not reset if you simply hit Enter without entering a command -- which can be handy when you just want to see how long the terminal has been up since you last did anything in it. It works fine for me in Red Hat and Ubuntu. It did NOT work for me under Cygwin, but I'm not sure if that's a bug or just a limitation of trying to run Bash under Windows.
One possible drawback to this approach is that you keep resetting SECONDS, but if you truly need to preserve SECONDS as the number of seconds since initial shell invocation, you can create your own variable for the PS1 counter instead of using SECONDS directly. Another possible drawback is that a large seconds value such as "999999" might be be better displayed as days+hours+minutes+seconds, but it's easy to add a simple filter such as:
seconds2days() { # convert integer seconds to Ddays,HH:MM:SS
printf "%ddays,%02d:%02d:%02d" $(((($1/60)/60)/24)) \
$(((($1/60)/60)%24)) $((($1/60)%60)) $(($1%60)) |
sed 's/^1days/1day/;s/^0days,\(00:\)*//;s/^0//' ; }
trap 'SECONDS=0' DEBUG
PS1='other_prompt_stuff_here ($(seconds2days $SECONDS)) # '
This translates "999999" into "11days,13:46:39". The sed at the end changes "1days" to "1day", and trims off empty leading values such as "0days,00:". Adjust to taste.
You could utilize this zsh-borrowed hook for bash: http://www.twistedmatrix.com/users/glyph/preexec.bash.txt
Timing done with this hook (Mac OS X): Use Growl to monitor long-running shell commands
If you hadn't set up any of the other answers before you kicked off your long-running job and you just want to know how long the job took, you can do the simple
$ HISTTIMEFORMAT="%s " history 2
and it will reply with something like
654 1278611022 gvn up
655 1278611714 HISTTIMEFORMAT="%s " history 2
and you can then just visually subtract the two timestamps (anybody know how to capture the output of the shell builtin history command?)
I took the answer from Ville Laurikari and improved it using the time command to show sub-second accuracy:
function timer_now {
date +%s%N
}
function timer_start {
timer_start=${timer_start:-$(timer_now)}
}
function timer_stop {
local delta_us=$((($(timer_now) - $timer_start) / 1000))
local us=$((delta_us % 1000))
local ms=$(((delta_us / 1000) % 1000))
local s=$(((delta_us / 1000000) % 60))
local m=$(((delta_us / 60000000) % 60))
local h=$((delta_us / 3600000000))
# Goal: always show around 3 digits of accuracy
if ((h > 0)); then timer_show=${h}h${m}m
elif ((m > 0)); then timer_show=${m}m${s}s
elif ((s >= 10)); then timer_show=${s}.$((ms / 100))s
elif ((s > 0)); then timer_show=${s}.$(printf %03d $ms)s
elif ((ms >= 100)); then timer_show=${ms}ms
elif ((ms > 0)); then timer_show=${ms}.$((us / 100))ms
else timer_show=${us}us
fi
unset timer_start
}
trap 'timer_start' DEBUG
PROMPT_COMMAND=timer_stop
PS1='[last: ${timer_show}][\w]$ '
Of course this requires a process to be started, so it's less efficient, but still fast enough that you wouldn't notice.
I found that trap ... DEBUG was running every time $PROMPT_COMMAND was called, resetting the timer, and therefore always returning 0.
However, I found that history records times, and I tapped into these to get my answer:
HISTTIMEFORMAT='%s '
PROMPT_COMMAND="
START=\$(history 1 | cut -f5 -d' ');
NOW=\$(date +%s);
ELAPSED=\$[NOW-START];
$PROMPT_COMMAND"
PS1="\$ELAPSED $PS1"
It's not perfect though:
If history doesn't register the command (e.g. repeated or ignored commands), the start time will be wrong.
Multi-line commands don't get the date extracted properly from history.
Here's my take on Thomas'
uses date +%s%3N to get milliseconds as base unit,
simplified following code (less zeros)
function t_now {
date +%s%3N
}
function t_start {
t_start=${t_start:-$(t_now)}
}
function t_stop {
local d_ms=$(($(t_now) - $t_start))
local d_s=$((d_ms / 1000))
local ms=$((d_ms % 1000))
local s=$((d_s % 60))
local m=$(((d_s / 60) % 60))
local h=$((d_s / 3600))
if ((h > 0)); then t_show=${h}h${m}m
elif ((m > 0)); then t_show=${m}m${s}s
elif ((s >= 10)); then t_show=${s}.$((ms / 100))s
elif ((s > 0)); then t_show=${s}.$((ms / 10))s
else t_show=${ms}ms
fi
unset t_start
}
set_prompt () {
t_stop
}
trap 't_start' DEBUG
PROMPT_COMMAND='set_prompt'
Then add $t_show to your PS1
Another approach for bash 4.x and above would be to use coproc with PS0 and PS1 like below:
cmd_timer()
{
echo $(( SECONDS - $(head -n1 <&"${CMD_TIMER[0]}") ))
}
coproc CMD_TIMER ( while read; do echo $SECONDS; done )
echo '' >&"${CMD_TIMER[1]}" # For value to be ready on first PS1 expansion
export PS0="\$(echo '' >&${CMD_TIMER[1]})"
export PS1="[ \$(cmd_timer) ] \$"
This is a .bashrc ready snippet.
It is especially useful for everyone that uses undistract-me which overwrites trap DEBUG for its own purposes.
If somone just wants to see the time of execution,
add this line to bash_profile
trap 'printf "t=%s\n" $(date +%T.%3N)' DEBUG
Translated version for zsh.
Append to your ~/.zshrc file
function preexec() {
timer=$(date +%s%3N)
}
function precmd() {
if [ $timer ]; then
local now=$(date +%s%3N)
local d_ms=$(($now-$timer))
local d_s=$((d_ms / 1000))
local ms=$((d_ms % 1000))
local s=$((d_s % 60))
local m=$(((d_s / 60) % 60))
local h=$((d_s / 3600))
if ((h > 0)); then elapsed=${h}h${m}m
elif ((m > 0)); then elapsed=${m}m${s}s
elif ((s >= 10)); then elapsed=${s}.$((ms / 100))s
elif ((s > 0)); then elapsed=${s}.$((ms / 10))s
else elapsed=${ms}ms
fi
export RPROMPT="%F{cyan}${elapsed} %{$reset_color%}"
unset timer
fi
}
A version with split hours, minutes and seconds inspired by the zsh spaceship prompt, based on Ville's answer and this time conversion function by perreal.
I also added a threshold variable so that the timer only displays for long running commands.
time_threshold=5;
function convert_secs {
((h=${1}/3600))
((m=(${1}%3600)/60))
((s=${1}%60))
if [ $h -gt 0 ]; then printf "${h}h "; fi
if [ $h -gt 0 ] || [ $m -gt 0 ]; then printf "${m}m "; fi
if [ $s -gt 0 ]; then printf "${s}s "; fi
}
function timer_start {
timer=${timer:-$SECONDS}
}
function timer_stop {
timer_time=$(($SECONDS - $timer))
if [ ! -z $timer_time ] && [ $timer_time -ge ${time_threshold} ]; then
timer_show="took $(convert_secs $timer_time)"
else
timer_show=""
fi
unset timer
}
trap 'timer_start' DEBUG
PROMPT_COMMAND=timer_stop
PS1='\n\w ${timer_show}\n\\$ '
For the coloured output in my screenshot:
bold=$(tput bold)
reset=$(tput sgr0)
yellow=$(tput setaf 3)
cyan=$(tput setaf 6)
PS1='\n${bold}${cyan}\w ${yellow}${timer_show}${reset}\n\\$ '
Will putting a \t in PS1 work for you?
It does not give the elapsed time but it should be easy enough to subtract the times when necessary.
$ export PS1='[\t] [\w]\$ '
[14:22:30] [/bin]$ sleep 10
[14:22:42] [/bin]$
Following the OP's comment that he is already using \t.
If you can use tcsh instead of bash, you can set the time variable.
/bin 1 > set time = 0
/bin 2 > sleep 10
0.015u 0.046s 0:10.09 0.4% 0+0k 0+0io 2570pf+0w
/bin 3 >
You can change the format of the printing to be less ugly (se the tcsh man page).
/bin 4 > set time = ( 0 "last: %E" )
/bin 5 > sleep 10
last: 0:10.09
/bin 6 >
I do not know of a similar facility in bash
this is my version
use date to format time, only calc days
set terminal title
use \$ in PS1 for user $ + root #
show return code / exit code
use date -u to disable DST
use hidden names like _foo
_x_dt_min=1 # minimum running time to show delta T
function _x_before {
_x_t1=${_x_t1:-$(date -u '+%s.%N')} # float seconds
}
function _x_after {
_x_rc=$? # return code
_x_dt=$(echo $(date -u '+%s.%N') $_x_t1 | awk '{printf "%f", $1 - $2}')
unset _x_t1
#_x_dt=$(echo $_x_dt | awk '{printf "%f", $1 + 86400 * 1001}') # test
# only show dT for long-running commands
# ${f%.*} = int(floor(f))
(( ${_x_dt%.*} >= $_x_dt_min )) && {
_x_dt_d=$((${_x_dt%.*} / 86400))
_x_dt_s='' # init delta T string
(( $_x_dt_d > 0 )) && \
_x_dt_s="${_x_dt_s}${_x_dt_d} days + "
# format time
# %4N = four digits of nsec
_x_dt_s="${_x_dt_s}$(date -u -d0+${_x_dt}sec '+%T.%4N')"
PS1='rc = ${_x_rc}\ndT = ${_x_dt_s}\n\$ '
} || {
PS1='rc = ${_x_rc}\n\$ '
}
# set terminal title to terminal number
printf "\033]0;%s\007" $(tty | sed 's|^/dev/\(pts/\)\?||')
}
trap '_x_before' DEBUG
PROMPT_COMMAND='_x_after'
PS1='\$ '
sample output:
$ sleep 0.5
rc = 0
$ sleep 1
rc = 0
dT = 00:00:01.0040
$ sleep 1001d
rc = 0
dT = 1001 days + 00:00:00.0713
$ false
rc = 1
$

Resources