Arithmetic comparison does not work in Bash - 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.

Related

Bash Script stuck looping

I'm trying to write a script that runs another script which fails rarely until it fails.
Here is the script that fails rarely:
#!/usr/bin/bash
n=$(( RANDOM % 100 ))
if (( n == 42 )) ; then
echo "$n Something went wrong"
>&2 echo "The error was using magic numbers"
exit 1
fi
echo "$n Everything went according to plan"
Here is the script that should run the previous script until it fails:
#!/usr/bin/bash
script_path="/tmp/missing/l2q3.sh"
found=0
counter=0
while (( $found == 0 )); do
output=(bash $script_path)
if (( $output == 42 Something went wrong )); then
found=1
fi
((counter++))
if (( $found == 1 )); then
echo "Number 42 was found after $counter tries"
fi
done
when I try running the second script I get stuck in an infinite loop saying there is a syntax error on line 11 and that something is wrong with 42 Something went wrong. I've tried with "42 Something went wrong" aswell and still stuck in a loop.
The form (( )) is arithemetic only, so you cannot test a string inside.
To test a string, you have to use the [[ ]] version:
[[ $output == "42 Something went wrong" ]] && echo ok
ok
You can use the program execution as the test for a while/until/if (etc.)
Assuming your script returns a valid 0 error code on success, and nonzero on any other circumstance, then -
$: cat tst
#!/bin/bash
trap 'rm -fr $tmp' EXIT
tmp=$(mktemp)
while /tmp/missing/l2q3.sh >$tmp; do let ++ctr; done
grep -q "^42 Something went wrong" $tmp &&
echo "Number 42 was found after $ctr tries"
In use:
$: ./tst
The error was using magic numbers
Number 42 was found after 229 tries
here are 3 steps to move forward.
add a return value at the end of the first script
exit 0
make your first script has executable rights
$ chmod a+x /tmp/missing/12q3.sh
instead of while loop you may use until, which would run until it returns success i.e. 0
until /tmp/missing/l2q3.sh; do ((counter++)) done
for other if statements please use square brackets [ single or double [[.

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

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.

How can I test if files given as an argument exist?

I am making a bash script that you have to give 2 files or more as arguments.
I want to test if the given files exist. I'm using a while loop because I don't know how many files are given. The problem is that the if statement sees the $t as a number and not as the positional parameter $number. Does somebody have a solution?
t=1
max=$#
while [ $t -le $max ]; do
if [ ! -f $t ]; then
echo "findmagic.sh: $t is not a regular file"
echo "Usage: findmagic.sh file file ...."
exit
fi
t=`expr $t + 1`
done
You can do it with the bash Special parameter # in this way:
script_name=${0##*/}
for t in "$#"; do
if [ ! -f "$t" ]; then
echo "$script_name: $t is not a regular file"
echo "Usage: $script_name file file ...."
exit 1
fi
done
With "$#" you are expanding the positional parameters, starting from one as separate words (your arguments).
Besides, remember to provide a meaningful exit status (e.g. exit 1 instead of exit alone). If not provided, the exit status is that of the last command executed (echo in your case, which succes, so you're exiting with 0).
And for last, instead of write the script name (findmagic.sh in your case), you can set a variable at the beginning in your script:
script_name=${0##*/}
and then use $script_name when necessary. In this way you don't need to update your script if it changes its name.

Cannot get if to evaluate the result of $? correctly

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
...

Resources