bash until not meeting condition [duplicate] - linux

Wondering if it's possible to finagle this logic (checking a variable for changes over time and running a loop while true) into a bash if statement or while loop condition. I was hoping for something like:
var=$(du -h *flat*.vmdk)
var2=$(sleep 1 ; du -h *flat*.vmdk)
if [[ $var != $var2 ]]; then
while true
do
echo -ne $(du -h *flat*.vmdk)\\r
sleep 1
done
else
echo "Transfer complete"
fi
I've also played with a while loop, rather than an if then with no luck.
while [ $var != $var2 ] ; do echo -ne $(du -h *flat*.vmdk)\\r ; sleep 1 ; done
But I'm seeing that's not possible? Or I'm having issues where things are incorrectly getting expanded. I'm open to any solution, although I am limited by a very basic shell (ESXi Shell) where many common unix/shell tools may not be present.

You are doing while [ $var != $var2 ] but never updating any of these variables ..
I would do something like:
function get_size() {
echo $(du -h *flat*.vmdk)
}
var="$(get_size)"
sleep 1
var2="$(get_size)"
while [ $var != $var2 ]; do
var=$var2
var2="$(get_size)"
echo -ne "$(get_size)\\r"
sleep 1
done
echo "Transfer complete"
What it does:
Use a function, because when you have to write two times or more a same line, it should trigger a "I should make it a function" in your brain.
Updating $var and $var2 within the while loop, so you don't check the same exact values each time, but check diff between last value and current one.
Add newlines to your code, because code is done to be read by humans, not machines, humans does not likes one-liners :)
I've not tested it

Not a generic solution, but if what you need is to wait while file keeps on changing, you can simply monitor it's modification timestamp with find (taking that this command is available), like that:
while find . -name *flat*.vmdk -newermt $(date --date "-1 second" +#%s)|read
do
sleep 1
done
echo "Transfer Completed !"
w/o using any variables at all.

I like #zeppelin's approach and I think I would have used it, but the date command in my environment was limited and I wasn't looking to invest any more time trying to figure that out. I did go with Arount's solution with a few modifications as seen below:
get_size() {
echo $(du -h *flat*.vmdk)
}
update() {
var="$(get_size)"
sleep 2
var2="$(get_size)"
}
update
while [ "$var" != "$var2" ]; do
update
echo -ne "$(get_size)\\r"
sleep 1
done
echo "Transfer complete"
The changes I needed:
ESXi Shell uses sh/Dash so I wasn't able to use the proposed function get_size() {
For whatever reason, the variables always matched until I created the update function to run in and outside the while loop.
Works well/as expected now. Thank you everyone for your help...hope it helps someone else.

Related

script that will monitor changes in at least 2 files/directories and execute a third script only when both are modified

I have two sensors that each create an entry in a text file when triggered. Now I need something to monitor these two files (i can also put them in 2 directories each if that helps in any way) and trigger a third script only when changes occur to both of the aforementioned files/directories. I need real-time (or near to it) between events and notification. I have found tools like inotify-wait, fswatch, entr and some others but all of these are triggered at any change.
At the moment I'm trying this but it does not work properly:
#!/bin/bash
while inotifywait -e modify /home/user/triggerdir/ ;
do
if [ "inotifywait -e modify /home/user/triggerdir2/" ];
then
echo Alert | mail -n -s "test-notify SCRIPT HUZAAAA" user#gmail.com
else
# Don't do anything unless we've found one of those
:
fi
done
I have looked for similar issues/solutions on the web, the closest would be this but it has no working answer.
Since you're having trouble with that, you might consider a simplistic approach.
Rather than a loop, I'd put your script in the crontab. Run it every day, every hour, every minute, whatever you need. If you need more often, you could loop, but make sure you at least sleep a second to be nice to the CPU.
If a minute or more between event and notification is ok this should be all you need:
#!/bin/bash
key=/some/safe/path/.hidden_filename
[[ -e "$key" ]] || touch "$key" # make sure it exists
if [[ file1 -nt "$key" && file2 -nt "$key" ]]; then
mail -n -s "test-notify SCRIPT HUZAAAA" user#gmail.com <<< "Alert!"
touch "$key"
fi
I have hacked something together which does work as I need it to though is terrible coding (probably shouldn't be called that)
3 scripts involved:
script 1:
#!/bin/bash
count=0
while :
do
{ inotifywait -e modify /home/user/triggerdir/ && let count="$count + 1"; } || exit 1
if [ "$count" -eq "2" ]; then
echo Alert | mail -n -s "Huzzah" user#gmail.com
/home/user/trigger2.sh &&
killall trigger.sh inotifywait
fi
done
script 2:
#!/bin/bash
count=0
while :
do
{ inotifywait -e modify /home/user/triggerdir/ && let count="$count + 1"; } || exit 1
if [ "$count" -eq "2" ]; then
echo Alert | mail -n -s "Huzzah" user#gmail.com
/home/user/trigger.sh &&
killall trigger2.sh #Do something.
# count=-250
fi
done
as the two scripts spawn bash/inotify processes I run once in 24 hours a cronjob two kill those using this script 3:
#!/bin/bash
killall trigger2.sh trigger.sh inotifywait bash
any help to improve is welcome, thanks :)

Arrays in Shell Script, not Bash

I am probably just having a brain fart, but I can not for the life of me figure out how to loop through an array in shell script, not bash. Im sure the answer is on stackoverflow somewhere already, but I can not find a method of doing so without using bash. For my embedded target system bash is not currently an option. Here is an example of what I am attempting to do and the error that is returned.
#!/bin/sh
enable0=1
enable1=1
port=0
while [ ${port} -lt 2 ]; do
if [ ${enable${port}} -eq 1 ]
then
# do some stuff
fi
port=$((port + 1))
done
Whenever I run this script the error "Bad substitution" is returned for line with the if statement. If you guys have any ideas I would greatly appreciate it. Thanks!
a="abc 123 def"
set -- $a
while [ -n "$1" ]; do
echo $1
shift
done
Output via busybox 1.27.2 ash:
abc
123
def
BusyBox provides ash which does not directly provide array support. You could use eval and something like,
#!/bin/busybox sh
enable0=0
enable1=1
for index in 0 1 ; do
eval assign="\$enable$index"
if [ $assign == 1 ]; then
echo "enable$index is enabled"
else
echo "enable$index is disabled"
fi
done
One could use positional parameters for that...
http://pubs.opengroup.org/onlinepubs/009696799/utilities/set.html
#!/bin/sh
enable0=0
enable1=1
set -- $enable0 $enable1
for index in 0 1; do
[ "$1" -eq 1 ] && echo "$1 is enabled." || echo "$1 is disabled."
shift
done
Running on busybox:
~ $ ./test.sh
0 is disabled.
1 is enabled.
It's best not to use eval unless there is no other alternative. (The recent spate of bash exploits is due to the shell internally evaling the contents of environment variables without verifying their contents first). In this case, you seem to be in complete control for the variables involved, but you can iterate over the variable values without using eval.
#!/bin/sh
enable0=1
enable1=1
for port_enabled in "$enable0" "$enable1"; do
if [ "$port_enabled" -eq 1 ]; then
# do some stuff
fi
done

Smarter and more condensed method to match input variable in shell script?

I have a file with a list of numerous unique items, for this example I am using user ID's. The starting section of my script should display the list to the user running the script and allow them to chose one of the ID's. The script should then cross check the choice made by the user against the original file and if it matches it should provide a message advising of the match and continue with the script. If it does not match, the script should advise the user and exit.
My current script does this OK, but I was wondering if there is any way to make it a bit smarter/more condensed, perhaps using arrays? Current script:
This is my first post on this site so I apologies in advanced for any mistakes which have been made in the process of posting.
FILE=testfile
IDLIST="$(awk '{print $1}' $FILE)"
echo "$IDLIST"
echo "\nSelect one of the options"
read input
OUTPUT="$(for i in $IDLIST
do
if [[ $i = $input ]]
then
echo "Matched."
fi
done)"
if [[ -z $OUTPUT ]]
then
echo "Invalid choice."
exit 0
else
ID=$input
fi
echo "It is a match, continuing with script"
As you can imagine, there are many ways of doing this. One is using select instead:
PS3="Select an ID: "
select id in $(cut -d ' ' -f 1 testfile)
do
[[ -z $id ]] && echo "Pick a number" || break
done
echo "You selected $id"

Bash infinite loop sleep having strange behavior (NGINX/PHP-FPM/PGSQL)

I'm not sure it should be in stackoverflow or serverfault. I post here because it may be a programming problem.
I have this infinite loop:
#!/bin/bash
MESSAGE="XXX0"
RESULT=`curl "http://somepage.php?thread=0"`
while :
do
if [[ "$RESULT" == "DONE" ]]
then
RESULT=`curl "http://somepage.php?thread=0"`
elif [[ "$RESULT" == "NONE" ]]
then
sleep 5
RESULT=`curl "http://somepage.php?thread=0"`
else
printf "%s %s\n" "$(date --rfc-3339='seconds'): ELSE1-" "$RESULT" >> /var/log/XXX/loopXXX-`date --rfc-3339='date'`
sleep 5
RESULT=`curl "http://somepage.php?thread=0"`
if [[ "$RESULT" == "DONE" ]]
then
RESULT=`curl "http://jsomepage.php?thread=0"`
elif [[ "$RESULT" == "NONE" ]]
then
sleep 5
RESULT=`curl "http://somepage.php?thread=0"`
else
printf "STOP"
break
fi
fi
done
I have 3 loops doing the same job and requesting thread 0 to 2. In the DBtable the PHP page request, there is a column thread. So the three loops query the same table (read/write) but never the same lines.
The problem I experience is that in some nights (almost no activity), one loop doesn't request a page for several hours (I checked in NGINX access log). This only happen sometimes and the server is way more powerfull than needed yet.
Is there problems using infinite loop with curl? In total I have around 10 loops (different pages/tables) but they have a 10s sleep instead of 5s.
Is there a problem in my script with memory/curl? Have you ever experienced something similar?
THanks!
One of the curl lines is probably taking much longer than you expect to execute.
You should use curl's --max-time parameter in order to limit the duration of any single execution to something sane. It expects seconds.
e.g.
RESULT=`curl --max-time 10 "http://somepage.php?thread=0"`
Note that you may now encounter failures where instead you had been seeing long delays. Checking the output might be satisfactory for your application, but return codes are the path to enlightenment. You may even want to use the "-e" option in your shebang and/or create a handler to be used with a trap for ERR.
Try to set max timeouts for your every curl command to prevent them from hanging. Example:
curl -m 50 ...

Issue controlling script flow

I'm new to shell scripting, my script appears to be okay, but its the flow that I'm having an issue controlling. Could someone point out what silly mistake I've made please.
#! /bin/sh
echo "Are you sure youx want to delete $1? Answer y or n"
read ans
echo $ans
if $ans = "y"|"Y"
then
mv $1 /home/parallels/dustbin
echo "File $1 has been deleted"
else echo "File $1 has not been deleted"
fi
Make your if condition like this:
if [ "$ans" = "y" -o "$ans" = "Y" ]
There are a few things wrong with your script. Some are serious, some are less so.
First, the serious problems.
As guru suggested, you need to use square brackets to surround your if condition. This is because if only tests for the output of a condition, it doesn't perform actual string comparisons. Traditionally, a program called /bin/test, which was also called /bin/[ took care of that. These days, that functionality is built in to the shell, but /bin/sh still behaves as if it's a separate program.
In fact, you can do interesting things with if when you don't use square brackets for your condition. For example, if grep -q 'RE' /path/to/file; then is quite common. The grep -q command issues no output, but simply returns a "success" or "fail" that is detected by if.
Second serious problem is that you are echoing a status that may or may not be true. I call this a serious problem because ... well, log messages simply shouldn't make false claims. If the permissions are wrong for the file in $1, or the filename contains a space, then your mv command will fail, but the message will claim that it did not. More on this later.
Next, the less serious problems.
These are mostly style and optimization things.
First off, read on most platforms includes a -p option that lets you specify a prompt. Use this, and you don't need to include an echo command.
Second, your indenting makes it hard to see what the if construct is wrapping. This isn't a huge problem in a program this small, but as you grow, you REALLY want to follow consistent standards.
Third, you can probably get more flexibility in multiple-choice questions like this if you use case statements instead of if.
After all that, here's how I'd write this script:
#!/bin/sh
if [ "$1" = "-y" ]; then
ans=y
shift
elif [ -t 0 ]; then
read -p "Are you sure you want to delete '$1' (y/N) ? " ans
fi
case "$ans" in
Y*|y*)
retval=0
if [ -z "$1" ]; then
retval=64
echo "ERROR: you didn't specify a filename." >&2
if [ ! -f "$1" ]; then
retval=66
echo "ERROR: file '$1' not found!" >&2
elif mv "$1" /home/parallels/dustbin/; then
echo "File '$1' has been deleted" >&2
else
retval=$?
echo "ERROR: file '$1' could not be deleted!" >&2
fi
;;
*)
echo "ABORT: file '$1' has not been deleted" >&2
retval=4
;;
esac
exit $retval
Aside from what's mentioned above, here are some things in this code snippet:
[ "$1" = "-y" ] - if the user specifies a -y option, then we behave as if the question was answered with a "yes".
[ -t 0 ] - this tests whether we are on an interactive terminal. If we are, then it makes sense to ask questions with read.
Y*|y*) - in a case statement, this matches any string that begins with an upper or lower case "y". Valid affirmative responses would therefore be "Y", "yes", "yellow", etc.
[ ! -f "$1" ] - this tests whether the file exists. You can man test or man sh to see the various tests available in shell. (-f may not be the most appropriate for you.)
>&2 - at the end of a line, sends its output to "standard error" instead of "standard out". This changes how output will be handled by pipes, cron, etc. Errors and log data are often sent to stderr, so that stdout can be dedicated to a program's actual output.
mv "$1" ... - The filename is in quotes. This protects you in case the filename has special characters like spaces in it.
$retval - the values for this came from a best guess of the closest item in man sysexits.
retval=$? - this is the exit status of the most recently executed command. In this case, that means we're assigning mv's exit status to the variable $retval, so that if mv failed, the whole script reports the reason for the fail, as far as mv is concerned.
You can also convert the user response to either case and just check it for respective case like
read ans
ans=${ans,,} # make 'ans' lowercase, or use ${ans^^} for making it uppercase
if [ "$ans" = "y" ]
then
....
fi
Below is the perfect code with error handling included
#!/bin/sh
echo "Are you sure you want to delete $1? Answer y or n"
read ans
echo $ans
if [ $ans == "y" ] || [ $ans == "Y" ]
then
if [ -f $1 ]
then
mv $1 /home/parallels/dustbin
echo "File $1 has been deleted"
else
echo " File $1 is not found"
fi
else
echo "File $1 has not been deleted"
fi

Resources