Extract value from file for input into IF Statement - bash

I have a config.txt file where the first line has a number 1, I need to read this and do certain tasks based on whether the number is 1 or 2 or 3 etc
The problem is I cannot test that config value, none of the if statements below work, and I tried many more variances.
config=$(head -n 1 /mnt/writable/config.txt) #grabs vale from file
echo $config #prints 1
But the following if statements do not echo anything.
if [[ "$config" = "1" ]];
then
echo "is a 1";
fi
if [[ "$config" == "1" ]];
then
echo "is a 1";
fi
if [[ "$config" = 1 ]];
then
echo "is a 1";
fi
if [ $config = 1 ];
then
echo "is a 1";
fi
I also tried "declare -i config" at the top but that didnt work either. Spent a day and no luck so far.

Because you have space or tab in your first line( I think )
So if you print using
echo $config
it will print 1 correctly but when you use it in if condition, it may not work as your wish ...
so I suggest that you should try following in your script,
config=$(head -n 1 /mnt/writable/config.txt | tr -d '[:space:]')
and all your if condition may work.

Integer equality can be tested by using -eq option.
Do man test for further details about if condition.
if [ $config -eq 1 ]; then echo "config is 1"; else echo "config is not 1"; fi

Related

What is the best way to accept a 2nd user input from options defined by the 1st user input?

My background is in SQL but I've been learning Bash to create tools to help non-Linux users find what they need from my Linux system - I am pretty green with Bash, I apologize if this looks a bit dumb.
The goal of the script is to essentially display all directories within the current directory to the user, and allow them to input 1-9 to navigate to lower directories.
My sticking point is that I'm trying to use arrays to define potential filepaths, since in practice new directories will be added over time and it is not practical to edit the script each time a filepath is added.
Here's my prototype so far, currently it navigates into Test1, Test2, or Test3 then echos pwd to prove it is there.
#Global Variables
DIR_MAIN='/home/admin/Testhome'
#Potential Filepaths
#/home/admin/Testhome/Test1/Test1-1/
#/home/admin/Testhome/Test1/Test1-2/
#/home/admin/Testhome/Test2/Test2-1/
#/home/admin/Testhome/Test2/Test2-2/
#/home/admin/Testhome/Test3/Test3-1/
#/home/admin/Testhome/Test3/Test3-2/
#Defining Array for first user input
arr=($(ls $DIR_MAIN))
#System to count total number of directories in filepath, then present to user for numbered selection
cnt=0
for i in ${arr[#]}
do
cnt=$(($cnt+1))
echo "$cnt) $i"
done
read -p "Select a folder from the list: " answer
case $answer in
1)
cd $DIR_MAIN/${arr[0]}
echo "Welcome to $(pwd)"
;;
2)
cd $DIR_MAIN/${arr[1]}
echo "Welcome to $(pwd)"
;;
3)
cd $DIR_MAIN/${arr[2]}
echo "Welcome to $(pwd)"
;;
esac
I've tried the following, but it doesn't like the syntax (to someone experienced I'm sure these case statements look like a grenade went off in vim).
I'm beginning to wonder if the SELECT CASE road I'm going down is appropriate, or if there is an entirely better way.
#User Input Start
echo "What is the secret number?"
while :
do
read STRING1
case $STRING1 in
1)
echo "Enter the number matching the directory you want and I will go there"
echo "1 - ${arr[0]}"
echo "2 - ${arr[1]}"
echo "3 - ${arr[2]}"
read STRING2
case $STRING2 in
1)
cd $DIR_MAIN/${arr[0]}
echo "Welcome to" $(pwd)
2)
cd $DIR_MAIN/${arr[1]}
echo "Welcome to" $(pwd)
3)
cd $DIR_MAIN/${arr[2]}
echo "Welcome to" $(pwd)
*)
echo "Thats not an option and you know it"
*)
echo "1 is the secret number, enter 1 or nothing will happen"
;;
esac
#break needs to be down here somewhere
done
Ultimately I know I'll need to variabilize a local array once I'm in Test2 for example (since in practice, this could descend as far as /Test2/Test2-9 and there would be tons of redundant code to account for this manually).
For now, I'm just looking for the best way to present the /Test2-1 and /Test2-2 filepaths to the user and allow them to make that selection after navigating to /Test2/
This might do what you wanted.
#!/usr/bin/env bash
shopt -s nullglob
n=1
for i in /home/admin/Testhome/Test[0-9]*/*; do
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
done
(( ${#array[*]} )) || {
printf 'It looks like there is/are no directory listed!\n' >&2
printf 'Please check if the directories in question exists!\n' >&2
return 1
}
dir_pattern_indices=$(IFS='|'; printf '%s' "#(${!array[*]})")
printf '\n'
read -rp "Select a folder from the list: " answer
if [[ -z $answer ]]; then
printf 'Please select a number and try again!' >&2
exit 1
elif [[ $answer != $dir_pattern_indices ]]; then
printf 'Invalid option %s\n' "$answer" >&2
exit 1
fi
for j in "${!array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "${array[j]}" || exit
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
The script needs to be sourced e.g.
source ./myscript
because of the cd command. See Why can't I change directory using a script.
Using a function instead of a script.
Let's just name the function list_dir
list_dir() {
shopt -s nullglob
declare -a array
local answer dir_pattern_indices i j n
n=1
for i in /home/admin/Testhome/Test[0-9]*/*; do
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
done
(( ${#array[*]} )) || {
printf 'It looks like there is/are no directory listed!\n' >&2
printf 'Please check if the directories in question exists!\n' >&2
return 1
}
dir_pattern_indices=$(IFS='|'; printf '%s' "#(${!array[*]})")
printf '\n'
read -rp "Select a folder from the list: " answer
if [[ -z $answer ]]; then
printf 'Please select a number and try again!' >&2
return 1
elif [[ $answer != $dir_pattern_indices ]]; then
printf 'Invalid option %s\n' "$answer" >&2
return 1
fi
for j in "${!array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "${array[j]}" || return
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
}
All of the array names and variables are declared local to the function in order not to pollute the interactive/enviromental shell variables.
Put that somewhere in your shellrc file, like say in ~/.bashrc then source it again after you have edited that shellrc file.
source ~/.bashrc
Then just call the function name.
list_dir
I took what #Jetchisel wrote and ran with it - I see they updated their code as well.
Between that code and what I hacked together piggybacking off what he wrote, I'm hoping future viewers will have what they need to solve this problem!
My code includes a generic logging function (can write to a log file if you define it and uncomment those logging lines, for a script this size I just use it to output debugging messages), everything below is the sequence used.
As he mentioned the "0" element needs to be removed from the array for this to behave as expected, as a quick hack I ended up assigning array element 0 as null and adding logic to ignore null.
This will also pull pretty much anything in the filepath, not just directories, so more tweaking may be required for future uses but this serves the role I need it for!
Thank you again #Jetchisel !
#hopt -s nullglob
DIR_MAIN='/home/admin/Testhome'
Dir_Cur="$DIR_MAIN"
LOG_LEVEL=1
array=(NULL $(ls $DIR_MAIN))
########FUNCTION LIST#########
####Generic Logging Function
Log_Message()
{
local logLevel=$1
local logMessage=$2
local logDebug=$3
local dt=$(date "+%Y-%m-%d %T")
##Check log level
if [ "$logLevel" == 5 ]
then local logLabel='INFO'
elif [ "$logLevel" == 1 ]
then local logLabel='DEBUG'
elif [ "$logLevel" == 2 ]
then local logLabel='INFO'
elif [ "$logLevel" == 3 ]
then local logLabel='WARN'
elif [ "$logLevel" == 4 ]
then local logLabel='ERROR'
fi
##Check conf log level
if [ "$LOG_LEVEL" == 1 ]
then #echo "$dt [$logLabel] $logMessage" >> $LOG_FILE ##Log Message
echo "$dt [$logLabel] $logMessage" ##Echo Message to Terminal
##Check if Debug Empty
if [ "$logDebug" != "" ]
then #echo "$dt [DEBUG] $logDebug" >> $LOG_FILE ##Extra Debug Info
echo "$dt [DEBUG] $logDebug" ##Extra Debug Info
fi
elif [ "$logLevel" -ge "$LOG_LEVEL" ]
then #echo "$dt [$logLabel] $logMessage" >> "$LOG_FILE" ##Log Message
echo "$dt [$logLabel] $logMessage"
fi
}
####
####Function_One
##Removes 0 position in array by marking it null, generates [1-X] list with further filepaths
Function_One()
{
Log_Message "1" "entered Function_One"
local local_array=("$#")
Log_Message "1" "${local_array[*]}"
n=1
for i in "${local_array[#]}"; do
if [ "$i" != "NULL" ]
then
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
fi
done
printf '\n'
read -rp "Select a folder from the list: " answer
for j in "${!local_array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "$Dir_Cur/${local_array[j]}" || exit
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
}
####
########FUNCTION LIST END#########
########MAIN SEQUENCE########
echo "Script start"
Function_One "${array[#]}"
Dir_Cur="$(pwd)"
array2=(NULL $(ls $Dir_Cur))
Function_One "${array2[#]}"
Dir_Cur="$(pwd)"
$Dir_Cur/test_success.sh
echo "Script end"
########

Terminate shell script after three invalid input

Restricting user from trying multiple invalid attempt in shell scripting. I wrote the below script but somehow it's not getting me desire output. I have shared the script and script output both. Kindly help. Here I wanted script to terminate if user tried more than 3 times.
While true
do
echo -n "Enter yes or no"
read opt
case $opt in
yes) break ;;
no) break ;;
*) echo "Invalid input"
while [[ $err -le 3 ]]
do
If [[ $err -le 3 ]]
then
echo "err: $err"
((err++))
break
else
echo "Max limit crossed"
exit 1
fi
done
;;
esac
done
This was a nice question and I had a lot of fun solving it. I have to mention that I'm new to shell programming.
n=0
until [ $n -ge 3 ]
do
read line
if [ "$line" = "XYZ" ]; then
echo "Accepted"
break
else
n=$[$n+1]
echo " trying " $n "times "
fi;
done
This article helped me a lot to solve it.
Try:
#!/bin/bash
ANSWER=
max=3
while true; do
echo "Enter yes or no:"
read -r ANSWER
[[ $ANSWER == "yes" || $ANSWER == "no" ]] && break
echo Invalid Input
ANSWER=
((--max))
[[ $max -le 0 ]] && { echo "Max limit crossed"; exit 1; }
done

Using a pipe inside a for loop. How do I preserve the value of the variable inside the for loop

I've been stuck for quite some time with the following code. It works but the variable loses the value that was set during the iterations.
I have the following code
mistakes=0
entered_chars=()
word_length=0
answer=""
answer_guess=""
checkIfLetterInsideWord(){
exists=0
letter=$2
word_array=`echo $1 | grep -o . `;
for (( i=1; i <= $word_length; i++))
do
if [[ "${1:$i-1:1}" = ${letter} ]]; then
exists=1
answer_guess=$(echo $answer_guess | sed "s/-/${letter}/{i}" )
fi
done
echo $exists
}
askUserInput(){
answer=$answer
echo $answer
echo "Please type a letter"
read user_input
if [ ! -z $user_input ]; then
user_input=$(echo $user_input | tr '[:upper:]' '[:lower:]')
if [ $(checkIfAlreadyEntered "$user_input") -eq 0 ]; then
if [ $(checkIfLetterInsideWord $answer $user_input) -eq 0 ]; then
mistakes=$((mistakes + 1)); fi
echo "Current mistake count; $mistakes "
entered_chars+=($user_input)
else
echo "Char has already been entered"
fi
else
echo "You haven't entered any input!"
fi
}
guessTheWord() {
answer=$OPTARG
word_length=$(printf $answer | wc -m)
temp=$(echo $answer | sed 's/\(.\)/\1 /g')
array=($temp)
echo "The chosen word is $word_length long"
gameOngoing=true
for(( i=1; i<=$word_length; i++)) do
answer_guess="$answer_guess-"
done
while $gameOngoing
do
echo $answer_guess
askUserInput $answer
done
}
I want to preserve the value of the variable answer_guess. I understand that it loses the value because of the usage of a pipeline inside the loop but I don't know to approach this problem.
The problem has nothing do to with the pipe. Rather, it is that you call checkIfLetterInsideWord inside a command-substitution ($(...)). Command substitution executes in a subshell so environment changes in the function will not persist.
It would be better to rewrite checkIfLetterInsideWord so that it returns an exit status. Something like:
if [[ $exists ]]; then
return 0 # Success
else
return 1 # Failure
end
Then you could simply call it without worrying about a subshell:
if checkIfLetterInsideWord "$answer" "$user_input"; then
# letter is in word
else
# letter is not in word
fi
There are other issues with the code. I've limited this answer to the question about preserving the value of variables.
answer_guess=$(echo $answer_guess | sed "s/-/${letter}/{i}" )
replace the - with .
so your code becomes
answer_guess=$(echo $answer_guess | sed "s/./${letter}/{i}" )

How to check if multiple variables are defined or not in bash

I want to check, if multiple variable are set or not, if set then only execute the script code, otherwise exit.
something like:
if [ ! $DB=="" && $HOST=="" && $DATE=="" ]; then
echo "you did not set any variable"
exit 1;
else
echo "You are good to go"
fi
You can use -z to test whether a variable is unset or empty:
if [[ -z $DB || -z $HOST || -z $DATE ]]; then
echo 'one or more variables are undefined'
exit 1
fi
echo "You are good to go"
As you have used the bash tag, I've used an extended test [[, which means that I don't need to use quotes around my variables. I'm assuming that you need all three variables to be defined in order to continue. The exit in the if branch means that the else is superfluous.
The standard way to do it in any POSIX-compliant shell would be like this:
if [ -z "$DB" ] || [ -z "$HOST" ] || [ -z "$DATE" ]; then
echo 'one or more variables are undefined'
exit 1
fi
The important differences here are that each variable check goes inside a separate test and that double quotes are used around each parameter expansion.
If you are ok with writing a function for this purpose, it can be pretty convenient.
This solution uses the ${!VAR_NAME} syntax to check whether the variable is empty and has the added benefit of telling you which variable names are empty.
check_vars()
{
var_names=("$#")
for var_name in "${var_names[#]}"; do
[ -z "${!var_name}" ] && echo "$var_name is unset." && var_unset=true
done
[ -n "$var_unset" ] && exit 1
return 0
}
# Usage for this case
check_vars DB HOST DATE
echo "You are good to go"
I wound up using variable-variables to loop through an easily managed HEREDOC list of variable names:
# Ensure non-empty values.
# Loop through HEREDOC, test variable-variable isn't blank.
while read var; do
[ -z "${!var}" ] && { echo "$var is empty or not set. Exiting.."; exit 1; }
done << EOF
KUBE_NAMESPACE
DOCKER_REGISTRY
DOCKER_DEPLOY_USER
DOCKER_DEPLOY_PASSWORD
DOCKER_DEPLOY_EMAIL
EOF
You can check it also by put the variables name in a file
DB=myDB
HOST=myDB
DATE=myDATE
then test them if currently empty or unset
#!/bin/bash
while read -r line; do
var=`echo $line | cut -d '=' -f1`
test=$(echo $var)
if [ -z "$(test)" ]; then
echo 'one or more variables are undefined'
exit 1
fi
done <var.txt
echo "You are good to go"
Nice solution from #joe.still !
improvement is to exit after checking all variables
i=0
while read var; do
[ -z "${!var}" ] && { echo "$var is empty or not set. Exiting.."; let i=i+1; }
done << EOF
KUBE_NAMESPACE
DOCKER_REGISTRY
DOCKER_DEPLOY_USER
DOCKER_DEPLOY_PASSWORD
DOCKER_DEPLOY_EMAIL
EOF
if [ $i -gt 0 ]; then
echo $i
echo "exiting"
exit 1
fi
Good Day Everyone.
I've personally used this method in my bash scripts. Verified works on bash 4.4 and later in Ubuntu, openSUSE, and ClearLinux.
Can RHEL|CentOS|Alma and Arch Based users let me know it it works fine for you?
( [ "$VAR1""$VAR2""$VAR3""$VAR4""$VAR5" ] && echo -e " Warning: StackIsNotClear" ) || { echo -e " GoodNews: StackIsClear"; }

If multiple IFs are null, do something

I am looking for suggestions for a solution and on best approaches to handle figuring out if multiple IFs are null.
I have:
if [ -n "$sfcompname" ]; then
echo $sfcompname
fi
if [ -n "$sfcompip" ]; then
echo $sfcompip
fi
if [ -n "$lacompname" ]; then
echo $lacompname
fi
if [ -n "$lacompip" ]; then
echo $lacompip
fi
.. I'm sure that can be done better, but my main problem at the moment is trying to then say:
if (all those IFs) = null
echo "Please check the name you entered and try again"
Somewhat silly, but should work
if ! [[ ${sfcompname}${sfcompip}${lacompname}${lacompip} ]]
then
echo "Please check the name you entered and try again"
fi
You can use another variable for this which you initialise to a value and then change if any of the if statements fire. Then at the end, if it hasn't changed, then you know that none of them fired. Something like this will suffice:
fired=0
if [ -n "$sfcompname" ]; then
echo $sfcompname
fired=1
fi
if [ -n "$sfcompip" ]; then
echo $sfcompip
fired=1
fi
if [ -n "$lacompname" ]; then
echo $lacompname
fired=1
fi
if [ -n "$lacompip" ]; then
echo $lacompip
fired=1
fi
if [[ ${fired} -eq 0 ]] ; then
echo 'None were fired'
fi
Another possibility is to use the variable check short-cut:
name="$sfcompname$sfcompip$lacompname$lacompip"
${name:?"Please check the name you entered and try again"}
This will exit the program if none of the variables are set. The message is optional, it overrides the standard "parameter null or not set".

Resources