I am trying to check for duplicate records in my database using shell scripting.
For this, I have created a function named "check()" which echo's True or False and is stored in variable "result". But while evaluating using if statement it is always returning "True".
#redundancy check function
check() {
temp=$(grep -w -c "$1" database.dat)
echo $temp
if [ "$temp" != 0 ]
then
echo True
else
echo False
fi
}
insert() {
option="y"
while [ "$option" == "y" ]
do
echo "Rollno: \c"
read roll
result="$(check $roll)"
echo $result
if [ "$result" == "False" ]
then
echo Do something
else
echo "ERROR: Duplicate record found...\nEXITING...\n"
option="n"
fi
done
}
If you're using a shell that doesn't support the == extension to test, then your tests will always, unconditionally fail simply on account of invalid syntax. Use = for string comparisons to be portable to all POSIX-compliant implementations.
Moreover, there's no point to storing and then comparing the output from grep at all: Use the exit status of grep -q when your only goal is to check whether the number of matches is zero or more-than-zero; this allows grep to exit immediately when a match is seen, rather than needing to read the rest of the file.
# with -q, this emits no stdout, but exits w/ status 0 (matches exist) or 1 (otherwise)
check() { grep -q -w -e "$1" database.dat; }
insert() {
option=y
while [ "$option" = y ]; do
printf '%b\n' "Rollno: \c"
read -r roll
if check "$roll"; then
printf "ERROR: Duplicate record found...\nEXITING...\n"
option=n
else
echo "Check failed; do something"
fi
done
}
Related
I'm new to bash so assume that I don't understand everything in this simple script as I've been putting this together as of today with no prior experience with bash.
I get this error when I run test.sh:
command substitution: line 29: syntax error near unexpected token `$1,'
./f.sh: command substitution: line 29: `index_of($1, $urls))'
FILE: f.sh
#!/bin/bash
urls=( "example.com" "example2.com")
error_exit()
{
echo "$1" 1>&2
exit 1
}
index_of(){
needle=$1
haystack=$2
for i in "${!haystack[#]}"; do
if [[ "${haystack[$i]}" = "${needle}" ]]; then
echo "${i}"
fi
done
echo -1
}
validate_url_param(){
index=-2 #-2 as flag
if [ $# -eq 0 ]; then
error_exit "No url provided. Exiting"
else
index=$(index_of($1, $urls)) #ERROR POINTS TO THIS LINE
if [ $index -eq -1 ]; then
error_exit "Provided url not found in list. Exiting"
fi
fi
echo $index
}
FILE: test.sh
#!/bin/bash
. ./f.sh
index=$(validate_url_param "example.com")
echo $index
echo "${urls[0]}"
I've lost track of all of the tweaks I tried but google is failing me and I'm sure this is some basic stuff so... thanks in advance.
The immediate error, just like the error message tells you, is that shell functions (just like shell scripts) do not require or accept commas between their arguments or parentheses around the argument list. But there are several changes you could make to improve this code.
Here's a refactored version, with inlined comments.
#!/bin/bash
urls=("example.com" "example2.com")
error_exit()
{
# Include script name in error message; echo all parameters
echo "$0: $#" 1>&2
exit 1
}
# A function can't really accept an array. But it's easy to fix:
# make the first argument the needle, and the rest, the haystack.
# Also, mark variables as local
index_of(){
local needle=$1
shift
local i
for ((i=1; i<=$#; ++i)); do
if [[ "${!i}" = "${needle}" ]]; then
echo "${i}"
# Return when you found it
return 0
fi
done
# Don't echo anything on failure; just return false
return 1
}
validate_url_param(){
# global ${urls[#]} is still a bit of a wart
if [ $# -eq 0 ]; then
error_exit "No url provided. Exiting"
else
if ! index_of "$1" "${urls[#]}"; then
error_exit "Provided url not found in list. Exiting"
fi
fi
}
# Just run the function from within the script itself
validate_url_param "example.com"
echo "${urls[0]}"
Notice how the validate_url_param function doesn't capture the output from the function it is calling. index_of simply prints the result to standard output and that's fine, just let that happen and don't intervene. The exit code tells us whether it succeeded or not.
However, reading the URLs into memory is often not useful or necessary. Perhaps you are simply looking for
grep -Fx example.com urls.txt
getAnimalFolder() {
local animal=""
if [[ ${ANIMAL} == "lion" ]]; then
animal = "./animals/lion/"
elif [[ ${ANIMAL} == "tiger" ]]; then
animal = "./animals/tiger/"
elif [[ ${ANIMAL} == "cheetah" ]]; then
animal = "./animals/cheetah/"
else
echo "inavalid animal"
exit 1`enter code here`
fi
echo $animal
}
result=$(getAnimalFolder)
cd ../result/age/
If the animal is not lion, tiger or cheetah, the function returns invalid animal and hence gives an error 'No such file or directory', instead I need to do an exit with code = 1. Hence I went for the second option -
if [[ ${ANIMAL} != "lion" && ${ANIMAL} != "tiger" && ${ANIMAL} != "cheetah" ]]; then
echo "Invalid animal"
exit 1
fi
getAnimalFolder() {
local animal=""
if [[ ${ANIMAL} == "lion" ]]; then
animal = "./animals/lion/"
elif [[ ${ANIMAL} == "tiger" ]]; then
animal = "./animals/tiger/"
elif [[ ${ANIMAL} == "cheetah" ]]; then
animal = "./animals/cheetah/"
fi
echo $animal
}
result=$(getAnimalFolder)
cd ../result/age/
This looks like a fix to my problem but if in the future more animals are added, then I need to remember to make changes in 2 places for every new animal added. So is there a better way to do this?
There are a number of problems here; #1 and #3 are the ones that directly address your question.
When a function/command/whatever may need to print both regular output (e.g. the path to an animal directory) and error/status output (e.g. "inavalid animal"), it should send the regular output to standard output (aka stdout aka FD #1, the default), and error/status output to standard error (aka stderr aka FD #2), like this:
echo "Invalid animal" >&2 # This is sent to stderr
Generally, functions should return rather than exiting. If a function does exit, it exits the entire shell, but in this case the function is running in a subshell due to $( ), so it only exits that. Using return avoids this inconsistency.
When a function/command/whatever may fail, you should check its exit status; there are a number of ways to do this:
if result=$(getAnimalFolder); then
: # command succeeded!
else
echo "OMG it failed!" >&2
exit 1
fi
or
result=$(getAnimalFolder)
if [ $? -ne 0 ]; then # $? is the status of the last command
echo "OMG it failed!" >&2
exit 1
fi
or
result=$(getAnimalFolder) || {
echo "OMG it failed!" >&2
exit 1
}
I use the last form a lot, since there are a lot of steps in a script might fail, and having a simple & compact way to include the failure handing code makes the overall script more readable.
In general, functions should take their input as arguments rather than via global variables. So in the function you'd refer to $1 instead of $ANIMAL, and you'd run the function with something like:
result=$(getAnimalFolder "$ANIMAL")
There are also a number of basic syntax errors and bad scripting practices in the script: don't put spaces around the equal sign in assignments; do put double-quotes around variable references; don't use all-caps variable names (to avoid conflicts with the many all-caps variables that have special meanings); do check for errors on cd commands (if they fail, the rest of the script will run in the wrong place); and when comparing a single variable against a bunch of values, use case instead of a bunch of if elseif etc.
shellcheck.net is good at recognizing many of these common mistakes. Strongly recommended.
Here's what I get with all fixes in place:
#!/bin/bash
getAnimalFolder() {
local animalname=$1
local animaldir=""
case "$animalname" in
lion ) animaldir="./animals/lion/" ;;
tiger ) animaldir="./animals/tiger/" ;;
cheetah ) animaldir="./animals/cheetah/" ;;
* )
echo "Invalid animal: $animalname" >&2
return 1 ;;
esac
echo "$animaldir"
}
read -p "Give me an animal: " animalname
result=$(getAnimalFolder "$animalname") || {
exit 1 # Appropriate error message has already been printed
}
cd "../$result/age/" || {
echo "Error changing directory to ../$result/age/ -- aborting" >&2
exit 1
}
Put the animals in an array:
#!/bin/bash
animals=(lion tiger cheetah)
getAnimalFolder() {
local i
for i in "${animals[#]}"; do
if [ "$i" == "${1}" ] ; then
animaldir="./animals/${1}"
return 0
fi
done
exit 1
}
read -rp "Give me an animal: " animalname
getAnimalFolder "${animalname}"
echo "Animaldir=${animaldir}"
EDIT:
I did not use the construction result=$(getAnimalFolder), assuming the OP wants to use the new path once. When needed, the function can be changed into
echo "./animals/${1}"
When the function is called with result=$(getAnimalFolder), OP needs to look at the line
cd ../result/age/
Is resulta fixed path or does he want to use the path from the function:
cd ../${result}/age/
I don't know what is wrong with my function; it is not returning value properly.
function validate_directory_isempty {
retval=""
NumOfFiles=`ls -l $input | egrep -c "^-"`
if [ "$NumOfFiles" == "0" ];then
retval=true
else
retval=false
fi
echo $retval
}
retval=$(validate_directory_isempty /opt/InstallationManager)
echo "retval : " $retval
if [ "$retval" == "true" ]; then
echo ""
echo "Installing Installation Manager"
# Install_IM
else
echo ""
echo "DIRECTORY is not empty. Please make sure install location $DIRECTORY is empty before installing PRODUCT"
fi
The idiomatic way to have a function return true or false is to use the return keyword. A return value of 0 means success, a non-zero value means failure. Also note that a function will return with a status of the last command executed if return is not present.
I would write your function like this
is_dir_empty() {
shopt -s nullglob
local -a files=( "$1"/* )
(( ${#files[#]} == 0 ))
}
directory=/opt/InstallManager
if is_dir_empty "$directory"; then
echo "$directory" is empty
fi
The first line sets a shell option that pattern matching no files expands to null instead of the pattern itself as a string.
The second line fills an array with the filenames in the given directory.
The last line tests the number of elements in the array. If zero entries, return success else return failure.
i just replaced my script as below and it worked
removed the below commands
retval=$(validate_directory_isempty /opt/InstallationManager)
echo "retval : " $retval
added
input=/opt/InstallationManager
validate_directory_isempty
and it worked.
Thanks again for your valuable inputs
I'm writing a script where I need to use the output of a file test in several places, including inside a shell function. I would like to assign the file existence to a shell variable, like this: file_exists=[ -f $myfile ].
Just to make sure that I've got my bases covered, I start by touching a file, and testing its existance:
file='a'
touch $file
if [ -f $file ]
then
echo "1 -- '$file' exists"
fi
Output:
1 -- 'a' exists
The file was created successfully -- no surprises, but at least I know that I'm not dealing with any permissions issues or anything.
Next I test to make sure that I can store a boolean expression in a variable:
mytest=/bin/true
if $mytest
then
echo "2 -- \$mytest is true"
fi
Output:
2 -- $mytest is true
So I've got the basics covered -- conditional expressions should emit the same output as /bin/true or /bin/false... but that's not what I'm seeing:
mytest=[ -f $file ]
if $mytest
then
echo "3 -- \$mytest is true [expect true]"
else
echo "3 -- \$mytest is false [expect true]"
fi
This fails with the following error:
-f: command not found
I get the same error message if i use test -f $file rather than [ -f $file ].
If I put a space in front of the [, the error goes away...
mytest= [ -f $file ]
if $mytest
then
echo "4 -- \$mytest is true [expect true]"
else
echo "4 -- \$mytest is false [expect true]"
fi
The output appears to be correct:
4 -- $mytest is true [expect true]
... but if I remove the file, I should get the opposite result:
rm $file
mytest= [ -f $file ]
if $mytest
then
echo "5 -- \$mytest is true [expect false]"
else
echo "5 -- \$mytest is false [expect false]"
fi
... and I don't:
5 -- $mytest is true [expect false]
To be fair, I expected the space to mess with the truth value:
mytest= /bin/false
if $mytest
then
echo "6 -- \$mytest is true [expect false]"
else
echo "6 -- \$mytest is false [expect false]"
fi
Outputs:
6 -- $mytest is true [expect false]
So, how do I store the output from the test builtin in a shell variable?
As others have documented here, using the string "true" is a red herring; this is not an appropriate way to store boolean values in shell scripts, as evaluating it means dynamically invoking a command rather than simply inspecting the stored value using shell builtins hardcoded in your script.
Instead, if you really must store an exit status, do so as a numeric value:
[ -f "$file" ] # run the test
result=$? # store the result
if (( result == 0 )); then # 0 is success
echo "success"
else # nonzero is failure
echo "failure"
fi
If compatibility with set -e is desired, replace the first two lines of the above with:
result=0
[ -f "$file" ] || result=$?
...as putting the test on the left-hand side of || marks it as "checked", suppressing errexit behavior. (That said, see BashFAQ #105 describing the extent to which set -e harms predictable, portable behavior; I strongly advise against its use).
You need to quote whitespace:
mytest='[ -f $file ]'
if $mytest; then echo yes; fi
However, this is extremely brittle and potentially insecure. See http://mywiki.wooledge.org/BashFAQ/050 for a detailed discussion and some better ways to accomplish something similar.
If you want to encapsulate a complex piece of code, a function is usually the way to go:
mytest () { [ -f "$file" ]; }
if mytest; then echo yes; fi
If you want to run the code once and store its result so you can examine it later, I would rephrase it like this:
if [ -f "$file" ]; then
mytest=true
else
mytest=false
fi
if $mytest; then echo yes; fi
A old one but left this here for reference for people that might need it. Not the most beautiful solution but it works in bash:
mytest=$( [ -f $file ] ; echo $? )
More portable, using the test command, and the backticks:
set mytest=`test -f $file ; echo $?`
In a subprocess (<!> system load), the condition is evaluated, and then the result echoed to the output that is captured by the variable $mytest.
mytest=/bin/true is storing the string /bin/true in the $mytest variable.
mytest=[ -f $file ] is setting the $mytest variable to the value [ for the duration of the command -f $file ] (which as your output indicates fails as there is no -f command available).
mytest= [ -f $file ] (like the above) sets the value of the $mytest variable to blank for the duration of the [ -f $file ] command (and returns whatever [ returns).
mytest= /bin/false this is the same as the above case only the command being run is /bin/false.
If you want to store the return code from a command in a variable you can do
/bin/true
ret=$?
if you want to store the output from a command in a variable you can do
out=$(/bin/true)
(though with /bin/true that variable will be empty as it outputs no text.
For your case you want the former $? model.
Also, using set -x (and/or set -v) in your scripts might have helped you diagnose this.
Different version with test command.
fileExists=$(test -f /path/to/file && echo true || echo false)
if [[ ${fileExists} == "true" ]]; then
# Your code here
fi
Or even more simplier version.
fileExists=$(test -f /path/to/file && echo 1)
if [[ -z ${fileExists} ]]; then
# Your code here
fi
I want to push my input parameters through two sets of checks. Each of these checks is a function. So case would not work because in KSH.Case will "esac" after satisfying the 1st condition.
die () {
echo "ERROR: $*. Aborting." >&2
exit 1
}
var=$1
var1=$2
[ -z "$var1" ] && var1=$var
echo "var is $var . Var1 is $var1 "
# test $var1 != $var2 ||
dtc() {
# small function where I am checking if the input parameter is a valid date
}
vlc(){
# function where I am checking if the input parameters year is after 2012, because earlier years cannot exist
}
if dtc $var && dtc $var1
then
if vlc $var && vlc $var1
then
stuff
else
die "message"
fi
else
die "message"
fi
The nested if looks a bit clumsy. If there is a more elegant way convey this to shell.
Improving your indentation will go a long way toward readable code.
If your die message are different, then you have no choice. If they are the same, then you can combine the conditions:
If you just want to die if not all the commands are successful, you can write
dtc "$var" && dtc "$var1" &&
vlc "$var" && vlc "$var1" || die "message"
# OK, all passed
stuff
One style tip for if-else is to put the shorter block first
if ! { dtc "$var" && dtc "$var1"; }
then
die "message 1"
else
if ! { vlc "$var" && vlc "$var1"; }
then
die "message 2"
else
we will do
lots of stuff
here
fi
fi
and of course, functions to encapsulate code
stuff() {
we will do
lots of stuff
here
}