Cannot get if to evaluate the result of $? correctly - bash

So here's what I'm trying to do. I want to grep through a file for two strings and then print out to the user whether the strings are present.
grep rn.jar $start_catalina #2&>1
echo $? #2&>1
if [ $? -eq 0 ]; then
printf "%s\n" "RN_JAR variable set correctly."
else printf "%s\n" "RN_JAR variabe not correctly set. Please set it."
fi
grep $\RN_JAR $start_catalina 2&>1
echo $? 2&>1
if [ $? = 0 ]; then
printf "%s\n" "RN_JAR variable set correctly."
else printf "%s\n" "RN_JAR variable not set correctly."
fi
I thought I had this working but then I realized that the second test was always evaluating true. At one point I had all of the above flowing together in one big if construction but I could never get the second statement to evaluate as false. I broke them out into two separate statements and now neither evaluates as false (in other words, if I pull the strings out of catalina.sh even though the return is 1 it still prints the statement as if it were true). In pulling them apart I managed, somehow, to break both.
I know that it's ugly and that there's a simpler way to accomplish this but right now, I'm just having a blind spot as to why I cannot get these strings to evaluate as false. I'm sure I'm doing something obvious that I just can't see but for the life of me, I cannot figure out what it is.

Rather than:
Running grep and discarding it's output and
Evaluating $? later
You can use grep -q like this:
if grep -Fq "rn.jar" $start_catalina; then
printf "%s\n" "RN_JAR variable set correctly."
else
printf "%s\n" "RN_JAR variabe not correctly set. Please set it."
fi
As per man grep:
-q, --quiet, --silent
Quiet mode: suppress normal output. grep will only search a file until a match has been found, making searches
potentially less expensive.

echo $? #2&>1 will reset the value of $?
You need
grep rn.jar $start_catalina #2&>1
exitval=$?
echo $exitval #2&>1
if [ $exitval -eq 0 ]; then
printf "%s\n" "RN_JAR variable set correctly."
else
printf "%s\n" "RN_JAR variabe not correctly set. Please set it."
fi
grep $\RN_JAR $start_catalina 2&>1
exitval=$?
echo $exitval 2&>1
if [ $exitval = 0 ]; then # this should not work as it evaluates as string comparison
printf "%s\n" "RN_JAR variable set correctly."
else
printf "%s\n" "RN_JAR variable not set correctly."
fi

The thing is that $? give you the exit value of the last command. In the case of both if tests in your script, this will be the immediately previous echo command (NOT the grep), which should always succeed...

When you do echo $? you are printing the result of the last command, but destroying it, because just after that, $? will be the result of echo, not the previous command. And echo will likely succeed.
The solution is to use a variable:
RES=$?
echo $RES
if [ $RES -eq 0 ]; then
...

Related

Shell script with absolute path and control errors

I was doing this little script in which the first argument must be a path to an existing directory and the second any other thing.
Each object in the path indicated in the first argument must be renamed so that the new
name is the original that was added as a prefix to the character string passed as the second argument. Example, for the string "hello", the object OBJECT1 is renamed hello.OBJECT1 and so on
Additionally, if an object with the new name is already present, a message is shown by a standard error output and the operation is not carried out continuing with the next object.
I have the following done:
#! /bin/bash
if [ "$#" != 2 ]; then
exit 1
else
echo "$2"
if [ -d "$1" ]; then
echo "directory"
for i in $(ls "$1")
do
for j in $(ls "$1")
do
echo "$i"
if [ "$j" = "$2"."$i" ]; then
exit 1
else
mv -v "$i" "$2"."$i"
echo "$2"."$i"
fi
done
done
else
echo "no"
fi
fi
I am having problems if I run the script from another file other than the one I want to do it, for example if I am in /home/pp and I want the changes to be made in /home/pp/rr, since that is the only way It does in the current.
I tried to change the ls to catch the whole route with
ls -R | sed "s;^;pwd;" but the route catches me badly.
Using find you can't because it puts me in front of the path and doesn't leave the file
Then another question, to verify that that object that is going to create new is not inside, when doing it with two for I get bash errors for all files and not just for coincidences
I'm starting with this scripting, so it has to be a very simple solution thing
An obvious answer to your question would be to put a cd "$2 in the script to make it work. However, there are some opportunities in this script for improvement.
#! /bin/bash
if [ "$#" != 2 ]; then
You might put an error message here, for example, echo "Usage: $0 dir prefix" or even a more elaborate help text.
exit 1
else
echo $2
Please quote, as in echo "$2".
if [ -d $1 ]; then
Here, the quotes are important. Suppose that your directory name has a space in it; then this if would fail with bash: [: a: binary operator expected. So, put quotes around the $1: if [ -d "$1" ]; then
echo "directory"
This is where you could insert the cd "$1".
for i in $(ls $1)
do
It is almost always a bad idea to parse the output of ls. Once again, this for-loop will fail if a file name has a space in it. A possible improvement would be for i in "$1"/* ; do.
for j in $(ls $1)
do
echo $i
if [ $j = $2.$i ]; then
exit 1
else
The logic of this section seems to be: if a file with the prefix exists, then exit instead of overwriting. It is always a good idea to tell why the script fails; an echo before the exit 1 will be helpful.
The question is why you use the second loop? a simple if [ -f "$2.$i ] ; then would do the same, but without the second loop. And it will therefore be faster.
mv -v $i $2.$i
echo $2.$i
Once again: use quotes!
fi
done
done
else
echo "no"
fi
fi
So, with all the remarks, you should be able to improve your script. As tripleee said in his comment, running shellcheck would have provided you with most of the comment above. But he also mentioned basename, which would be useful here.
With all that, this is how I would do it. Some changes you will probably only appreciate in a few months time when you need some changes to the script and try to remember what the logic was that you had in the past.
#!/bin/bash
if [ "$#" != 2 ]; then
echo "Usage: $0 directory prefix" >&2
echo "Put a prefix to all the files in a directory." >&2
exit 1
else
directory="$1"
prefix="$2"
if [ -d "$directory" ]; then
for f in "$directory"/* ; do
base=$(basename "$f")
if [ -f "Sdirectory/$prefix.$base" ] ; then
echo "This would overwrite $prefix.$base; exiting" >&2
exit 1
else
mv -v "$directory/$base" "$directory/$prefix.$base"
fi
done
else
echo "$directory is not a directory" >&2
fi
fi

Arithmetic comparison does not work in Bash

So, I have a script for moving files from one directory to another. I want to check whether the moving was successful or not, so I use this block of code:
...
mv "${!i}" "$dirname" 2>/dev/null
echo $? # Check MV exit code
if (( $?==1 ))
then
...
The problem is that whether moving was successful or not, then does not work. If I do this instead
if (( $?==0 ))
it instead works in any case. I have read that it may be because $? is treated like a string, and strings have 0 value. However, if I change it to this
if (( $?=="1" ))
it does not work either. I have tried using [[ ... ]] and [ ... ] instead of (( ... )), and -eq instead of ==, removing and adding spaces, adding and removing quotes, but nothing worked.
What am I doing wrong? Maybe there is another way of responding to certain exit code?
The problem is here:
echo $?
if (( $?==1 ))
The first echo $? will echo the return value of your mv command; however, in the if statement, using $? again will give you the return value of the echo command! $? is always the return value of the last command. The last command is the echo and it is always succeeding so you are always getting a 0 return value in that if statement.
What you should do instead is save the value into a variable and then compare things to that variable:
mv "${!i}" "$dirname" 2>/dev/null
ret_val=$?
echo ${ret_val}
if (( ${ret_val}==1 ))
You can check the exit status of your command directly:
if mv "${!i}" "$dirname" 2>/dev/null; then
# Code for successful move
else
# Code for unsuccessful move
fi
Or, to keep the happy path less indented:
if ! mv "${!i}" "$dirname" 2>/dev/null; then
# Code for unsuccessful move
return 1 # Or maybe exit 1 if in a script, not a function
fi
# Code for successful move
As for how the exit status of echo messes up your code, Tyler's answer has that covered.

How to use contents of text file as input to shell script?

I'm tasked with writing a shell script that takes in a string of 6 letters and numbers and checks if they meet a certain criteria.
This is that script
FILE=$1
var=${#FILE}
if [ $var -gt 6 ] || [ $var -lt 6 ]
then
#echo $FILE "is not a valid NSID"
exit 1
else if ( echo $1 | egrep -q '^[A-Za-z]{3}\d{3}$' )
then
#echo $1 "is a valid NSID"
exit 0
else
#echo $1 "is not a valid NSID"
exit 1
fi
fi
It works. so that isn't where the problem is.
What I am trying to do is use a "wrapper" script to gather potential valid NSID's from a text file and call that script on the list of inputs. so that if I call that script within my wrapper script it will step through the text file I have given my wrapper and check if each line is valid.
FILE=$1
YES= more $FILE
if ( exec /path/to/file/is_nsid.sh $YES -eq 0 )
then
echo $FILE "is a valid NSID"
else
echo $FILE "is not a valid NSID"
fi
so if I called it with a text file called file1.txt which contained
yes123
yess12
ye1243
it would output whether each was valid or not.
The line
YES= more $FILE
Sets YES in the environment passed to the command more $FILE. That's probably not what you intended.
The line
if ( exec /path/to/file/is_nsid.sh $YES -eq 0 )
starts a subshell to execute exec /path/to/file/is_nsid.sh $YES -eq 0. (That's what the parentheses do.) exec then replaces the subshell with a process which executes
/path/to/file/is_nsid.sh $YES -eq 0
which in turn runs the script at is_nsid.sh, passing it two or three command line arguments:
the value of $YES. This could be several arguments if the value of the shell variable includes whitespace or a glob symbol, but in this case it is more likely to be nothing since $YES has not been defined.
-eq
0
Since your script only examines its first argument, that's probably equivalent to
/path/to/file/is_nsid.sh -eq
That will, presumably, terminate with a failure status code, and since the subshell has been replaced with the script execution, that will also be the return status of the subshell. (Without exec, there would be essentially no difference; the subshell's return status is that of the last command executed in the subshell. Without either the parentheses or the exec, the result would also be the same. So you could have just written if /path/to/file/is_nsid.sh $YES -eq 0 and it would produce the same incorrect result.)
What you presumably wanted to do was to read each line in the file whose name is passed as the first command-line argument to the script. You could do that as follows:
while read -r line; do
if /path/to/file/is_nsid.sh "$line"; then
echo "$line is a valid NSID"
else
echo "$line is not a valid NSID"
fi
done < "$1"
You could simplify your is_nsid script considerably. The following is equivalent:
[ $#1 -eq 6 ] && echo "$1" | egrep -q '^[A-Za-z]{3}\d{3}$'
Note that \d is a Gnu extension to egrep and should not be relied on in portable code (which I assume this is trying to be). You should use [0-9] or [[:digit:]] instead.
The length check is actually unnecessary since the regex can only match six-character lines. Personally, I'd leave it out and just use
echo "$1" | egrep -q '^[[:alpha:]]{3}[[:digit:]]{3}$'
I removed all the unnecessary if statements. If I had left them in, I would have changed else if ... then ... fi to simply elif ... then ... to avoid unnecessary if nesting.

Getting piped data to functions

Example output
Say I have a function, a:
function a() {
read -r VALUE
if [[ -n "$VALUE" ]]; then # empty variable check
echo "$VALUE"
else
echo "Default value"
fi
}
So, to demonstrate piping to that function:
nick#nick-lt:~$ echo "Something" | a
Something
However, piping data to this function should be optional. So, this should also be valid. and give the following output:
nick#nick-lt:~$ a
Default value
However, the function hangs, as the read command waits for data from stdin.
What I've tried
Honestly not a lot, because I don't know much about this, and searching on Google returned very little.
Conceptually, I thought there might be a way to "push" an empty (or whitespace, whatever works) value to the stdin stream, so that even empty stdin at least has this value appended/prepended, triggering read and then simply trim off that first/last character. I didn't find a way to do this.
Question
How can I, if possible, make both of the above scenarios work for function a, so that piping is optional?
EDIT: Apologies, quickly written question. Should work properly now.
One way is to check whether standard input (fd 0) is a terminal. If so, don't read, because that will cause the user to have to enter something.
function a() {
value=""
if [ \! -t 0 ] ; then # read only if fd 0 is a pipe (not a tty)
read -r value
fi
if [ "$value" ] ; then # if nonempty, print it!
echo "$value"
else
echo "Default value"
fi
}
I checked this on cygwin: a prints "Default value" and echo 42 | a prints "42".
Two issues:
Syntactic, You need a space, before closing ]]
Algorithmic, You need the -n (non-zero length) variable test, not -z (zero length)
So:
if [[ -n "$VALUE" ]]; then
Or simply:
if [[ "$VALUE" ]]; then
As [[ is a shell builtin, you don't strictly need the double quotes:
if [[ $VALUE ]]; then
Also refrain from using all uppercases as variable name, as these are usually used for denoting environment variables, and your defined one might somehow overwrite already existing one. So use lowercase variable name:
if [[ $value ]]; then
unless you are export-ing your variable, and strictly need it to be uppercased, also make sure it is not overwriting any already existing one.
Also, i would add a timeout to read e.g. -t 5 for 5 seconds, and if no input is entered, print the default value. Also change the function name to something more meaningful.
Do:
function myfunc () {
read -rt5 value
if [[ "$value" ]]; then
echo "$value"
else
echo "Default value"
fi
}
Example:
$ function myfunc () { read -rt5 value; if [[ "$value" ]]; then echo "$value"; else echo "Default value"; fi ;}
$ myfunc
Default value
$ echo "something" | myfunc
something
$ myfunc
foobar
foobar

I want a to compare a variable with files in a directory and output the equals

I am making a bash script where I want to find files that are equal to a variable. The equals will then be used.
I want to use "mogrify" to shrink a couple of image files that have the same name as the ones i gather from a list (similar to "dpkg -l"). It is not "dpkg -l" I am using but it is similar. My problem is that it prints all the files not just the equals. I am pretty sure this could be done with awk instead of a for-loop but I do not know how.
prog="`dpkg -l | awk '{print $1}'`"
for file in $dirone* $dirtwo*
do
if [ "basename ${file}" = "${prog}" ]; then
echo ${file} are equal
else
echo ${file} are not equal
fi
done
Could you please help me get this working?
First, I think there's a small typo. if [ "basename ${file}" =... should have backticks inside the double quotes, just like the prog=... line at the top does.
Second, if $prog is a multi-line string (like dpkg -l) you can't really compare a filename to the entire list. Instead you have to compare one item at a time to the filename.
Here's an example using dpkg and /usr/bin
#!/bin/bash
progs="`dpkg -l | awk '{print $2}'`"
for file in /usr/bin/*
do
base=`basename ${file}`
for prog in ${progs}
do
if [ "${base}" = "${prog}" ]; then
echo "${file}" matches "${prog}"
fi
done
done
The condition "$file = $prog" is a single string. You should try "$file" = "$prog" instead.
The following transcript shows the fix:
pax> ls -1 qq*
qq
qq.c
qq.cpp
pax> export xx=qq.cpp
pax> for file in qq* ; do
if [[ "${file} = ${xx}" ]] ; then
echo .....${file} equal
else
echo .....${file} not equal
fi
done
.....qq equal
.....qq.c equal
.....qq.cpp equal
pax> for file in qq* ; do
if [[ "${file}" = "${xx}" ]] ; then
echo .....${file} equal
else
echo .....${file} not equal
fi
done
.....qq not equal
.....qq.c not equal
.....qq.cpp equal
You can see in the last bit of output that only qq.cpp is shown as equal since it's the only one that matches ${xx}.
The reason you're getting true is because that's what non-empty strings will give you:
pax> if [[ "" ]] ; then
echo .....equal
fi
pax> if [[ "x" ]] ; then
echo .....equal
fi
.....equal
That's because that form is the string length checking variation. From the bash manpage under CONDITIONAL EXPRESSIONS:
string
-n string
True if the length of string is non-zero.
Update:
The new code in your question won't quite work as expected. You need:
if [[ "$(basename ${file})" = "${prog}" ]]; then
to actually execute basename and use its output as the first part of the equality check.
you can use case/esac
case "$file" in
"$prog" ) echo "same";;
esac

Resources