Bash script - for loop and if-else - linux

I am new to Bash scripting and despite multiple attempts to refactor the logic structure shown in pseudocode below I cant get this to work. How do I piece the if/else logic together with a for loop?
Pseudocode:
I have a command that checks if any subfolders exist in a directory of the HDFS filesystem. Lets call this command_A.
If subfolders do NOT exist {
proceed with remaining execution of script
}
Else {
sleep for 30 minutes and run command_A again to see if the subfolders have been removed. Note: This sleep and re-check cycle should repeat up to 4 times and if subfolders are never removed the script is killed with exit 1
}
Sample of What I Have Tried is Below. I can't figure out how I am supposed to use the || in conjunction with the else statement.
Using these structures:
1. for i in {1..4}; do command_A && break || sleep 1800; done
2. if command_A ; then echo "Command succeeded" else echo "Command failed" fi
Test Example:
for i in {1..4};
do
if hdfs dfs -test -e $mypath/*
echo "Folder is empty" && break
else
???
Update showing working solution:
for i in {1..4};
do
if hdfs dfs -test -e $mypath/*;
then
if [ $i -eq 4 ]
then
echo "script exiting now with code 1"
else
echo "Folder is full"
sleep 10
fi
else
echo "Folder is empty"
break
fi
done

UPDATE
This is the working complete code for this particular question, I'll keep the original slightly more generic code below for future reference since it is less complex (less nested if/else) for others searching for similar problems.
for i in {1..4};
do
if hdfs dfs -test -e $mypath/*;
then
if [ $i -eq 4 ]
then
echo "script exiting now with code 1"
else
echo "Folder is full"
sleep 10
fi
else
echo "Folder is empty"
break
fi
done
I think something like this will work, which is pretty much what you have. You just have to put what you're checking for in the if statement and this code should work for you.
for i in {1..4}
do
if [ <check for subdirs> ]
then
echo "Folder is empty!"
break
else
sleep 1800
fi
done

Will this work?
sleep_count=0
max_attempts=4
for ((i=0; i < $max_attempts; i++)); do
if hdfs dfs -test -e $mypath/*; then
echo "No subfolders!"
break
else
# subfolders exist; wait for a while and see if they go away
((sleep_count++))
sleep 1800
fi
done
if [[ $sleep_count == $max_attempts ]]; then
# waited too long for subfolders to go away
exit 1
fi
# probably repeat the above steps again?

Related

Bash script that allows one word as user input

Made a script that the user gives a "parameter" and it prints out if it is a file, directory or non of them. This is it :
#!/bin/bash
read parametros
for filename in *
do
if [ -f "$parametros" ];
then
echo "$parametros is a file"
elif [ -d "$parametros" ];
then
echo "$parametros is a directory"
else
echo " There is not such file or directory"
fi
exit
done
Altough i want the user to be allowed to give only one word as a parameter. How do i make this happen ? (For example if user press space after first word there would be an error message showing "wrong input")
#!/bin/bash
read parametros
if [[ "$parametros" = *[[:space:]]* ]]
then
echo "wrong input"
elif [[ -f "$parametros" ]]
then
echo "$parametros is a file"
elif [[ -d "$parametros" ]]
then
echo "$parametros is a directory"
else
echo " There is not such file or directory"
fi
See http://mywiki.wooledge.org/BashFAQ/031 for the difference between [...] and [[...]].
You have to use the $#. It gives the number of the parameters.
The code will be something like:
if [ "$#" -ne 1 ]; then
printf 'ERROR!\n'
exit 1
fi
First, I'm curious why you want to restrict to one word - a file or directory could have spaces in it, but maybe you are preventing that somehow in your context.
Here are a few ways you could approach it:
Validate the input after they enter it - check if it has any spaces, eg: if [[ "parametros" == *" " ]]; then...
Get one character at a time in a while loop, eg with: read -n1 char
Show an error if it's a space
Break the loop if it's 'enter'
Build up the overall string from the entered characters
1 is obviously much simpler, but maybe 2 is worth the effort for the instant feedback that you are hoping for?

Yes or No in Bash Script

I have a set of 100 questions. My requirement is when a user enter "yes", then question 1 should appear. If not, directly it go to question 2. Like that it should go on till 100 questions. Any lead would be appreciated.
This is what I tried, but it is failing.
#!/bin/bash
echo "Execute question1 "
select result in Yes No
do
echo "How to see apache config file"
exit
done
echo "execute question2"
select result in Yes No Cancel
do
echo "Command for listing processes"
exit
done
Thanks in advance
Here is a way to do this with an array.
#!/bin/bash
questions=(
"How to see apache config file"
"Command for listing processes"
"My hovercraft is full of eels"
)
for((q=0; q<${#questions[#]}; q++)); do
echo "Execute question $q?"
select result in Yes No; do
case $result in
Yes)
echo "${questions[q]}";;
esac
break
done
done
Using select for this seems rather clumsy, though. Perhaps just replace it with
read -p "Execute question $q? " -r result
case $result in
[Yy]*) echo "${questions[q]}";;
esac
Having just a list of questions still seems weird. With Bash 5+ you could have an associative array, or you could have a parallel array with the same indices with answers to the questions. But keeping each question and answer pair together in the source would make the most sense. Maybe loop over questions and answers and assign every other one to an answers array, and only increment the index when you have read a pair?
pairs=(
"How to see Apache config file"
"cat /etc/httpd.conf"
"Command for listing processes"
"ps"
"My hovercraft is full of what?"
"eels"
)
questions=()
answers=()
for((i=0; i<=${#pairs[#]}/2; ++i)); do
questions+=("${pairs[i*2]}")
answers+=("${pairs[1+i*2]}")
done
This ends up with two copies of everything, so if you are really strapped for memory, maybe refactor to just a for loop over the strings and get rid of the pairs array which is only useful during initialization.
Use an array of questions and loop over it, like this:
#!/bin/bash
n=1
questions=(
'How to see apache config file'
'Command for listing processes'
)
check_user_input(){
read -p "y/n " input
case $input in
[Yy]*) return 0;;
[Nn]*) return 1;;
*) check_user_input;;
esac
}
for question in "${questions[#]}"; {
echo "Execute question $n"
check_user_input && echo "$question"
((n++))
}
Here is a straight forward example. Play with it.
#!/bin/bash
echo "Type 'y' for yes, 'n' to skip or 'q' to quit and press Enter!"
for((i=1; i < 101; ++i)); do
echo 'Execute question '$i
while read user_input; do
if [[ "$user_input" = 'q' ]]; then
break 2
elif [[ "$user_input" = 'n' ]]; then
break
elif [[ $i -eq 1 ]]; then
echo 'How to see apache config file?'
break 2 # Change from "break 2" to "break" for the next question.
elif [[ $i -eq 2 ]]; then
echo 'Command for listing processes.'
break 2 # Change from "break 2" to "break" for the next question.
else
echo "Wrong input: $user_input"
echo "Type 'y' for yes, 'n' to skip or 'q' to quit and press Enter!"
fi
done
done
echo 'Finished'

While loop within sleep and retry the proccess

I have a control file file.txt that always changes in value anytime depends on the other proccess.
The value of the file is like 1, 2, or 3.
I want to execute my new job when the value of control file is 3. So i need to check first the control file before do the execution job.
I was try while loop below using sleep for a moment and retry the proccess automatically every 5 seconds for 3 times, but the retry proccess is not running. How i resolved this?
#!/bin/bash
myname="kyy"
while read file; do
if [ $file == 2 ]; then
echo "`date +%Y%m%d:%H:%M:%S:%N` [ERROR]:-Value of Control File : $file "
echo "`date +%Y%m%d:%H:%M:%S:%N` [NOTICE]:-Nothing to do.. Script will exiting.."
else
echo "`date +%Y%m%d:%H:%M:%S:%N` [SUCCESS]:-Go to next step "
echo "`date +%Y%m%d:%H:%M:%S:%N` [SUCCESS]:-My name is $myname "
fi
sleep 3
((c++)) && ((c==3)) && break
done < /home/hcuseros/file.txt
This would be a simple way, where you only need to fill in your commands depending on the outcome of the test:
for i in {1..3}; do
file=$(cat test.txt)
if [ $file -eq "3" ]; then
...
else
...
fi
sleep 5
done

simple shell script question. please give me advise

i have a question about simple shell script.
this is the source code of rand.sh below
#!/bin/bash
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
>&2 echo "The error was using magic numbers"
exit 1
fi
echo "Everything went accrding to plan"
and i'm going to make a new shell script, let me call it quiz.sh.
quiz.sh should loop until n==42. if n==42, save the stdout("Something went wrong") and stderr("The error was using magic numbers")
and it finally terminated with printing out those stdout,stderr and Total execution count.
here is my quiz.sh
#!/bin/bash
cnt=0
while [[ "${n}" -ne 42 ]]
do
(( cnt = "${cnt}"+1 ))
source ./rand.sh &> error.txt
done
cat error.txt
echo "${cnt}"
but this is not working. because of exit 1 in rand.sh, the program is terminated before executing cat and echo which is at the end two line.
how can i fix it?? please let me know!
I want to make happen cat error.txt and echo "${cnt}" as well
Run the loop in a subshell
(
while something; do
something
exit 1 # exits only from the subshell
done
)
Note: parent shell doesn't access/inherit child process environment. So cnt is going to be empty in parent shell. Transfer it some other way.
(
cnt=0
while ((n != 42)); do
((cnt++))
echo "$cnt" > cntfile.txt
# >& is deprecated
source myrand > error.txt 2>&1
done
)
cnt=$(<cntfile.txt)
cat error.txt
echo "$cnt"
Reference Bash manual command grouping.
As KamilCuk pointed out correctly, you should use $n instead of n.
Furthermore, I personally would add that using source ./rand.sh &> error.txt is kind of weird in this case. If you want to run it as a background process, use:
./rand.sh &> error.txt &
wait $! # $! is the pid
Otherwise, just make a function out of it:
#!/bin/bash
function myrand {
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
>&2 echo "The error was using magic numbers"
return 1
fi
echo "Everything went accrding to plan"
return 0
}
cnt=0
while [[ "${n}" -ne 42 ]]
do
(( cnt = "${cnt}"+1 ))
myrand() &> error.txt
done
cat error.txt
echo "${cnt}"
p.s. code not tested, but I guess it works.

Is there a way to automatically restart a bash script? [duplicate]

For example, in the below script startover starts back from the top:
##########################################################################
## CHECK TIME
##########################################################################
time=$(date +%k%M)
if [[ "$time" -ge 1800 ]] && [[ "$time" -le 2200 ]];then
echo "Not a good time to transcode video!" && exit 0
else
echo "Excellent time to transcode video!" && echo "Lets get started!"
fi
##########################################################################
## CHECK TIME
##########################################################################
startover
Also keeping in mind exit 0 should be able to stop the script.
You could "recurse" using the following line:
exec bash "$0" "$#"
Since $0 is the path to the current script, this line starts the script without creating a new process, meaning you don't need to worry about too many restarts overflowing the process table on your machine.
Put it in a while loop. I'd also suggest you add a "sleep" so that you're not racing your machine's CPU as fast as it will go:
while true; do
##########################################################################
## CHECK TIME
##########################################################################
time=$(date +%k%M)
if [[ "$time" -ge 1800 ]] && [[ "$time" -le 2200 ]]; then
echo "Not a good time to transcode video!" && exit 0
else
echo "Excellent time to transcode video!" && echo "Lets get started!"
fi
##########################################################################
## CHECK TIME
##########################################################################
for i in {1..5}; do
echo $i
sleep 1
done
done
DO NOT USE WHILE LOOP at the start of the script since the condition below will exit the script and break the loop.
echo "Not a good time to transcode video!" && exit 0
You can try trapping the exit signal so that when the script exits it restarts
##########################################################################
## CHECK TIME
############bash##############################################################
trap '<path to script> ' EXIT
time=$(date +%k%M)
if [[ "$time" -ge 1800 ]] && [[ "$time" -le 2200 ]];then
echo "Not a good time to transcode video!" && exit 0
sleep 1;
else
echo "Excellent time to transcode video!" && echo "Lets get started!"
sleep 1;
fi
##########################################################################
## CHECK TIME
##########################################################################
echo 1
echo 2
echo 3
echo 4
echo 5
startover
Note: I add a sleep of 1 second because this will give you the time to see message. trap the exit signal and re-running the script is acting like a while loop. I am also assuming that these codes are in a script.
How about enclosing the entire script in a while loop? For example,
while :
do
script
done
You may want to add a condition to break out of the loop.
This is not good practice, but what you asked for.
Put this at the end of your script. "$( cd "$( dirname "$0" )" && pwd )/$(basename $0)"

Resources