bash comparing variables that are integers with -gt -lt - bash

I'm trying to make a bash script that reads integers from a file (one number per line, name of the file is passed as the script argument), finds maximum, minimum and sum. I've got a problem with the part, where I'm comparing variables, though. Code below (I've skipped here the part which checks whether the file exists or is empty):
#!/bin/bash
min=`cat "$1" | head -n 1`
max=$min
sum=0
lw=`cat "$1" | wc -l`
while [ $lw -gt 0 ];
do
num=`cat "$1" | tail -n $lw | head -n 1`
if [ "$num" -gt "$max" ]
then
max=$num
elif [ "$num" -lt "$min" ]
then
min=$num
fi
sum=$[sum+num]
lw=$[$lw-1]
done
echo "Maximum: $max"
echo "Minimum: $min"
echo "Sum: $sum"
With this code I'm getting errors in lines 13 and 16: [: : integer expression expected
If I change the comparision part inside the while loop to:
if [ $num -gt $max ]
then
max=$num
elif [ $num -lt $min ]
then
min=$num
fi
I'm getting errors:
line 13: [: -gt: unary operator expected
line 16: [: -lt: unary operator expected
What am I doing wrong? I'm a total newbie in bash, so I'll be extremely grateful for any help.
Data that I used for testing:
5
6
8
2
3
5
9
10

Probably your input file contains DOS line endings or other improper formatting. Your code should work for well-formed inputs.
However, the proper way to loop over the lines in a file is
#!/bin/bash
min=$(sed 1q "$1")
max=$min
sum=0
while read -r num; do
if [ "$num" -gt "$max" ]
then
max=$num
elif [ "$num" -lt "$min" ]
then
min=$num
fi
((sum+=num))
done<"$1"
echo "Maximum: $max"
echo "Minimum: $min"
echo "Sum: $sum"
Notice also that backticks and $[[...]]] use syntax which has been obselescent for decades already.

My guess would be that the expression
num=`cat "$1" | tail -n $lw | head -n 1`
assigns to num some value that is not a number in one of the iterations. I would suggest adding echo "$num" in the prev line to check this assumption

Another thing: instead of reading lines using cat | tail | head it is easier to read file line by line using the following syntax
while IFS= read -r line
do
echo "$line"
done < "$input"
This will read contents of input file into line variable.
See here for explanations about IFS= and -r https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ - both of them not really necessary in your case

Related

-eq unary operator expected

I have this code here.
But I get the error:
./concatconvert: line 9: [: -eq: unary operator expected
./concatconvert: line 18: [: -eq: unary operator expected
This is my code:
#!/bin/bash
param=$#
if [ $# -le 2 ]
then
echo "Usage: concatconvert [-u|-1] FILE ...
Description: concatenates FILE(s) to standard output separating them with divider -----. Optional first argument -u or -l converts contents to uppercase or lowercase,$
fi
if [ $1 -eq "-u" ]
then
while [ $param -ge 1 ]
do
./concat | awk '{print toupper($0)}'
param=$(( param-1 ))
shift
done
fi
if [ $1 -eq "-l" ]
then
while [ $param -ge 1 ]
do
./concat | awk '{print tolower($0)}'
param=$(( param-1 ))
shift
done
fi
Why am I getting this error? I thought that -eq is a unary operator?
You have missed few things eg--> " for echo command was NOT closed. Then in if condition since you are comparing a string so change it to if [[ "$1" = "-u" ]] so following could be the script(I haven't tested it since no samples were there).
#!/bin/bash
param=$#
if [ $# -le 2 ]
then
echo "Usage: concatconvert [-u|-1] FILE ...
Description: concatenates FILE(s) to standard output separating them with divider -----. Optional first argument -u or -l converts contents to uppercase or lowercase,$"
fi
if [[ "$1" = "-u" ]]
then
while [ $param -ge 1 ]
do
./concat | awk '{print toupper($0)}'
param=$(( param-1 ))
shift
done
fi
if [[ $1 -eq -l ]]
then
while [ $param -ge 1 ]
do
./concat | awk '{print tolower($0)}'
param=$(( param-1 ))
shift
done
fi

Shell Error while trying to perform a IF statement

Good day.
I am trying to create a script to read each line of a specific file and then send each line to a second file depending on how many characters that line contains.
For example i have a file called numbers which contain the following information:
numbers.txt
5196803638
31995500317
5196396080
51999205240
5198158891
As you can see i have number with 8 or 9 numbers on each, i was trying to create a script that read a file line and send each number to its specific files.
!#/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
echo $line
if [ "${#line}"= 8 ]; then
$line> file8.txt
elif [ "${#line}"= 9 ]; then
$line> file9.txt
else
echo "Error"
fi
done < "$1"
But the only thing i get is the following
51996803638
My.sh: line 7: [: 11=: unary operator expected
My.sh: line 9: [: 11=: unary operator expected
Error
Your numbers have 10 or 11 characters. When you compare numerical values, use -eq, -ne, -lt, -gt, -le, -ge.
while IFS='' read -r line || [[ -n "$line" ]]; do
echo $line
echo "Length = ${#line}"
if [ ${#line} -eq 10 ]; then
echo $line >> file10.txt
elif [ ${#line} -eq 11 ]; then
echo $line >> file11.txt
else
echo "Error"
fi
done < "$1"
The shell's tokenizer is white-space sensitive. Use spaces around =, as in
if [ "${#line}" = 8 ]; then
$line> file8.txt
elif [ "${#line}" = 9 ]; then
$line> file9.txt
else
echo "Error"
fi
Or, what about this, which I find more readable/maintainable/extendable:
case ${#line} in
(8) $line > file8.txt;;
(9) $line > file9.txt;;
(*) echo Error;;
esac

Nested if in shell scripting

I want to write a script that take 1 command line argument( a directory) and then prompt for 2 number, it will then print out any file(each in a different line) that has the size between that 2 number, this is my script
echo -n "Enter the first number: "
read a
echo -n "Enter the second, bigger number: "
read b
if
[ $b -lt $a ]
then
echo 'The first number must be smaller'
else
echo The files in $1 that are between $a and $b bytes are the following
echo
for var in 'ls $1'
do
if
[ -f $var ]
then
size='ls -l $var | '{ print $5 }''
if
[ $size -le $b && $size -ge $a ]
then
echo $var is $size bytes
fi
fi
done
fi
The problem is after I enter the numbers, it will print out "The files..." and then nothing else. Also, I use Vi to edit it,but the color of last three lines is not quite right(the color should match the first "fi" but it not). Can anyone show me what was wrong? Thank you.
Your immediate problem is that you used single quotes where you wanted command substitution. However, this is the wrong way to iterate over files. You should use pattern matching instead. Your for loop should read
for var in $1/*
do
if [ -f "$var" ]
then
# Check 'man stat' for the correct format string on your system
size=$(stat +%s "$var")
if [ $size -le $b ] && [ $size -ge $a ]
then
echo $var is $size bytes
fi
fi
done
There are a couple of problems here, but the one that I think has you stuck is that the single-quote character (') is used in a couple of places where the backtick character (`) should be used. This is a subtle typographical distinction, so sometimes people that haven't encountered it before don't pick up on the distinction. On my keyboard, you get a backtick character by hitting the key just to the left of the number 1, it is paired with the tilde (~), but your keyboard may be different.
The backtick allows you to assign the output of a command to a variable, for example:
my_variable=`ls - l` # <- uses backtick, assigns output of 'ls -l' command to $my_variable
#As opposed to:
my_variable='ls -l' # <- uses single-quote, makes $my_variable equal to the text "ls -l"
Note, this will also fix your vi issue if you replace the correct single-quotes w/backticks.
As stated by others, use a shebang and use backticks for your commands. Other things that were wrong, ls -l $var | '{ print $5 }' should be ls -l "$1$var" | awk '{ print $5 }' (awk command was missing), and when testing the files you should use the full path to the file like [ -f "$1$var" ] since the user may not be in the same directory as the path they provide as an argument to the script. Another problem is [ $size -le $b && $size -ge $a ]. You can't use the && operator that way, instead use [ $size -le $b ] && [ $size -ge $a ].
These are all the changes I made to your code. Hope it works for you.
echo -n "Enter the first number: "
read a
echo -n "Enter the second, bigger number: "
read b
if [ $b -lt $a ]
then
echo 'The first number must be smaller'
else
echo The files in "$1" that are between "$a" and "$b" bytes are the following
echo
for var in `ls "$1"`
do
if [ -f $1$var ]
then
size=`ls -l "$1$var" | awk '{ print $5 }'`
if [ $size -le $b ] && [ $size -ge $a ]
then
echo "$var" is "$size" bytes
fi
fi
done
fi

iterating through each line and each character in a file

i'm trying to get a file name, and a character index, and to print me the characters with that index from each line (and do it for each character index the user enters if such character exists).
This is my code:
#!/bin/bash
read file_name
while read x
do
if [ -f $file_name ]
then
while read string
do
counter=0
while read -n char
do
if [ $counter -eq $x ]
then
echo $char
fi
counter=$[$counter+1]
done < $(echo -n "$string")
done < $file_name
fi
done
But, it says an error:
line 20: abcdefgh: No such file or directory
line 20 is the last done, so it doesn't help me figure out where is the error.
So what's wrong in my code and how do I fix it?
Thanks a lot.
I think "cut" might fit the bill:
read file_name
if [ -f $file_name ]
then
while read -n char
do
cut -c $char $file_name
done
fi
This line seems to be problematic:
done < $(echo -n "$string")
Replace that with:
done < <(echo -n "$string")
replace
counter=0
while read -n char
do
if [ $counter -eq $x ]
then
echo $char
fi
counter=$[$counter+1]
done < $(echo -n "$string")
with
if [ $x -lt ${#string} ]
then
echo ${line:$x:1}
fi
It does the same, but allows to avoid such errors.
Another approach is using cut
cut -b $(($x+1)) $file_name | grep -v "^$"
It can replace two inner loops

Calculate maximum argument passed to a bash script

I'm trying to calculate maximum argument passed to a bash script. Here's the code:
#!/bin/sh
max=$1
for var in "$#"
do
if ($var>$max)
then
max=$var
fi
done
echo $max
Here is what I get:
$ /bin/sh my_script 1 2 3
rgz: 11: 1: not found
rgz: 11: 2: not found
rgz: 11: 3: not found
1
What am I doing wrong?
This might work for you -
#!/bin/bash
max="$1"
for var in "$#"
do
if [ "$var" -gt "$max" ] # Using the test condition
then
max="$var"
fi
done
echo "$max"
This is mine. Minor improvement...
#!/bin/bash
max="$1"
for v in "$#"
do
[[ $v -gt $max ]] && max=$v
done
echo "$max"
You can pipe the results to sort and find the maximum (the last item in the sorted list) with tail:
your stuff | sort -n | tail -1
May be this is not the computationally most efficient way to get the maximum, but gets the job done.

Resources