Nested if in shell scripting - shell

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

Related

Match if variable has WORD repeated more than once

I have this variable:
>echo $br_name
srxa wan-a1 br-wan3-xa1 0A:AA:DD:C1:F1:A3 ge-0.0.3 srxa wan-a2 br-wan3-xa2 0A:AA:DD:C1:F2:A3 ge-0.0.3
I am trying to create a conditional where it detects whether ge-0.0.3 is repeated more than 1 time in my variable $br_name
For example:
if [[ $br_name has ge-0.0.3 repeated more than one time ]]
then
echo "ge-0.0.3 is shown more than once"
else
:
fi
Bash's =~ is using extended RE.
[Bash-5.2] % check() { local s='(ge-0\.0\.3.*){2,}'; [[ "$1" =~ $s ]] && echo yes || echo no; }
[Bash-5.2] % check 'xxx'
no
[Bash-5.2] % check 'ge-0.0.3'
no
[Bash-5.2] % check 'ge-0.0.3 ge-0.0.3 '
yes
[Bash-5.2] % check 'ge-0.0.3 ge-0.0.3 ge-0.0.3 '
yes
You can use grep -o to print only the matched phrase. Also use -F to make sure that it matches literal characters instead of a regex where . and - are special
if [[ $(echo "$br_name" | grep -Fo ge-0.0.3 | wc -l) -gt 1 ]]; then
echo "ge-0.0.3 is shown more than once"
else
echo "only once"
fi
For more complex patterns of course you can drop -F and write a proper regex for grep
simple word
If your word would be "easy", you can detect the occurrences count with:
echo "123 123 123" | sed "s/123 /123\n/g" | wc -l
In which the word is replace for the same but with \n and then wc count the lines
or you can try one of these:
Count occurrences of a char in a string using Bash
How to count number of words from String using shell
complex
Since your word is "complex" or you will want a pattern, you will need regex:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
https://linuxconfig.org/advanced-bash-regex-with-examples
script.sh
count=0
for word in $1; do
if [[ "$word" =~ .*ge-0\.0\.3.* ]]
then
count=$(($count+1))
fi
done
if [ "$count" -gt "1" ];
then
echo "ge-0.0.3 is shown more than once"
else
if [ "$count" -eq "0" ];
then
echo "ge-0.0.3 is not shown"
else
echo "ge-0.0.3 is shown once"
fi
fi
execution
bash script.sh "srxa wan-a1 br-wan3-xa1 0A:AA:DD:C1:F1:A3 ge-0.0.3 srxa wan-a2 br-wan3-xa2 0A:AA:DD:C1:F2:A3 ge-0.0.3"
grep
With grep you can get the ocurrence count
ocurrences=( $(grep -oE '(ge-0\.0\.3)' <<<$1) )
ocurrences_count=${#ocurrences[*]}
echo $ocurrences_count
Assuming you want to do a literal string match on whole words, not substrings, then this might be what you want:
$ if (( $(grep -oFw 'ge-0.0.3' <<<"$br_name" | wc -l) > 1 )); then echo 'yes'; else echo 'no'; fi
yes

bash comparing variables that are integers with -gt -lt

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

How to check for space in a variable in bash?

I am taking baby steps at learning bash and I am developing a piece of code which takes an input and checks if it contains any spaces. The idea is that the variable should NOT contain any spaces and the code should consequently echo a suitable message.
Try this:
#!/bin/bash
if [[ $1 = *[[:space:]]* ]]
then
echo "space exist"
fi
You can use grep, like this:
echo " foo" | grep '\s' -c
# 1
echo "foo" | grep '\s' -c
# 0
Or you may use something like this:
s=' foo'
if [[ $s =~ " " ]]; then
echo 'contains space'
else
echo 'ok'
fi
You can test simple glob patterns in portable shell by using case, without needing any external programs or Bash extensions (that's a good thing, because then your scripts are useful to more people).
#!/bin/sh
case "$1" in
*' '*)
printf 'Invalid argument %s (contains space)\n' "$1" >&2
exit 1
;;
esac
You might want to include other whitespace characters in your check - in which case, use *[[:space:]]* as the pattern instead of *' '*.
You can use wc -w command to check if there are any words. If the result of this output is a number greater than 1, then it means that there are more than 1 words in the input. Here's an example:
#!/bin/bash
read var1
var2=`echo $var1 | wc -w`
if [ $var2 -gt 1 ]
then
echo "Spaces"
else
echo "No spaces"
fi
Note: there is a | (pipe symbol) which means that the result of echo $var1 will be given as input to wc -w via the pipe.
Here is the link where I tested the above code: https://ideone.com/aKJdyN
You could use parameter expansion to remove everything that isn't a space and see if what's left is the empty string or not:
var1='has space'
var2='nospace'
for var in "$var1" "$var2"; do
if [[ ${var//[^[:space:]]} ]]; then
echo "'$var' contains a space"
fi
done
The key is [[ ${var//[^[:space:]]} ]]:
With ${var//[^[:space:]]}, everything that isn't a space is removed from the expansion of $var.
[[ string ]] has a non-zero exit status if string is empty. It's a shorthand for the equivalent [[ -n string ]].
We could also quote the expansion of ${var//[^[:space:]]}, but [[ ... ]] takes care of the quoting for us.

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

How to include bash command line arguments in an echo statement

I'm writing a script that asks the user for several options and then, via a series of echo statements, creates and writes to a separate script file. That script will also be dependent on at least one command line argument when executed.
Given the original statement
if [ "` echo $1 | egrep ^[[:digit:]]+$`" = "" ]
that determines if the first argument ($1) is an integer, how can I include that in the echo statement to be written to the new file while maintaining the command line argument access?
I tried to escape the double quotes and dollar signs like
echo "if [ \"` echo \$1 | egrep ^[[:digit:]]+\$`\" = \"\" ]" >> generatePanos
but that just resulted in
if [ "" = "" ]
However, echo "\$1" results in $1 being printed in the file.
Since you are using bash, you can use bash's builtin regex:
if [[ $1 =~ ^[[:digit:]]+$ ]]; then
...
fi
Even without bash's builtin regex, there is no need for the test command ( [ ), or echo:
if grep -q -E '^[[:digit:]]+$' <<< "$1"; then
...
fi
If this must also work on shells other than bash, then you can keep the echo:
if echo "$1" | grep -q -E '^[[:digit:]]+$'; then
...
fi
The last two work because if tests the exit status of a command. The grep command returns non-zero if a match is not found.
I'm not entirely certain what you mean, but here are two things you can try:
If you're trying to print the entire line as-is (including the $1), use single-quotes to tell echo to not interpret anything:
$ echo 'if [ "` echo $1 | egrep ^[[:digit:]]+$`" = "" ]'
if [ "` echo $1 | egrep ^[[:digit:]]+$`" = "" ]
If you're trying to print the entire line but substitute in the current value for $1:
$ echo "if [ \"\` echo $1 | egrep ^[[:digit:]]+\$\`\" = \"\" ]"
if [ "` echo <<some value>> | egrep ^[[:digit:]]+$`" = "" ]
If you're trying to substitute the entire portion of the command that's in backticks (evaluated using the current value of $1), it's probably best to use an intermediate variable:
temp=$(echo $1 | egrep ^[[:digit:]]+$)
echo "if [ \"$temp\" = \"\" ]"
put a back slash before every offending character: echo if [ \"` echo $1 \| egrep ^[[:digit:]]+$`\" = \"\" ]
echo "\$1" should do exactly what you said it did.
To echo the CONTENTS of '1' then echo $1 (without the backslash).
When using variables in bash scripts, it's often good practice to double quote them (echo "$1", func_name "$1", etc) or to escape them ( echo "${1}" ).
As to the first part, are you wanting to echo the entire 'if' statement to the file? That's what it looks like. If not, then you should do this:
if [ conditions ]; then echo "$1" >> $filename

Resources