Breaking loop in shell script - linux

The user inputs sentences for which the number of words has to be counted. If the number is greater than three the sentence should be considered Long. If three or fewer then Short.
I know how to do that, but the rest of the task says make a loop so the user can input sentences until he inputs "n". The problem is I when I input "n" it counts it, but that "n" shouldn't be counted. (It should be a break statement.) How can I break that loop before counting the letter "n"?
#!/bin/bash
recenica=0
while [ $recenica != "n" ]
do
echo Unesite recenicu:
read recenica
if [ $recenica = "n" ]; then # ->>>>> this doesn't work
break
fi
echo $recenica > datoteka
br=$(wc -l datoteka | cut -c1-2)
echo recenica ima $br rijeci
if [ $br -gt 3 ]
then
echo $recenica > Duge.Recenice.IB
elif [ $br -le 3 ]
then
echo $recenica > Kratke.Recenice.IB
else
echo Molimo unijeti nešto
fi
done

You need to use quotes around symbols that expand to more than one word (contain spaces)... this works:
#!/bin/bash
recenica=0
while [ "$recenica" != "n" ] # koristiti dvostruke navodnike ovdje
do
echo Unesite recenicu:
read recenica
if [ "$recenica" = "n" ]; then # koristiti dvostruke navodnike ovdje
break
fi
echo $recenica > datoteka
br=$(wc -l datoteka | cut -c1-2)
echo recenica ima $br rijeci
if [ $br -gt 3 ]
then
echo $recenica > Duge.Recenice.IB
elif [ $br -le 3 ]
then
echo $recenica > Kratke.Recenice.IB
else
echo Molimo unijeti nešto
fi
done

The problem with read is that it will read the entire line and place the contents in recenica. With one word this is fine for the test:
while [ $recenica != "n" ]
However, if you have read more than one word, then the test will fail due to [: too many arguments (as noted by amdn in his answer regarding quoting)
When you enter n you care only about the first word, so limit your test accordingly using parameter expansion/substring extraction:
while [ ${recenica%% *} != "n" ]; do
That allows you to let the while loop control the break logic reducing the entire structure of your code to:
#!/bin/bash
recenica=0
while [ ${recenica%% *} != "n" ]
do
echo Unesite recenicu:
read recenica
echo $recenica > datoteka
br=$(wc -l datoteka | cut -c1-2)
echo recenica ima $br rijeci
if [ $br -gt 3 ]
then
echo $recenica > Duge.Recenice.IB
elif [ $br -le 3 ]
then
echo $recenica > Kratke.Recenice.IB
else
echo Molimo unijeti nešto
fi
done
There are a number of ways to do this, but this approach is fine.

Related

Why "[ 1 > 2 ]" evaluates to True?

I have two files:
f1.txt:
1
dest/f1.txt:
1
2
When I run wc -l on both of those files in linux terminal - I get my expected results:
$ wc -l < f1.txt
$ 1
$ wc -l < dest/f1.txt
$ 2
But when I run the following .sh file:
#!/bin/bash
if [ $(wc -l < f1.txt) > $(wc -l < dest/f1.txt) ]; then
echo -e "f1 has more lines"
else
echo -e "f1 doesn't have more lines"
fi
The output is:
f1 has more lines
Can you explian how could this be possible?
You should use the -gt for integer comparison in a if clause.
If you use > or < you will end up doing ASCII alphabetic order comparison.
integer comparison
-eq
is equal to
if [ "$a" -eq "$b" ]
-ne
is not equal to
if [ "$a" -ne "$b" ]
-gt
is greater than
if [ "$a" -gt "$b" ]
-ge
is greater than or equal to
if [ "$a" -ge "$b" ]
-lt
is less than
if [ "$a" -lt "$b" ]
-le
is less than or equal to
if [ "$a" -le "$b" ]
<
is less than (within double parentheses)
(("$a" < "$b"))
<=
is less than or equal to (within double parentheses)
(("$a" <= "$b"))
>
is greater than (within double parentheses)
(("$a" > "$b"))
>=
is greater than or equal to (within double parentheses)
(("$a" >= "$b"))
string comparison
=
is equal to
if [ "$a" = "$b" ]
Caution
Note the whitespace framing the =.
if [ "$a"="$b" ] is not equivalent to the above.
==
is equal to
if [ "$a" == "$b" ]
This is a synonym for =.
Note
The == comparison operator behaves differently within a double-brackets test than within single brackets.
[[ $a == z* ]] # True if $a starts with an "z" (pattern matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).
[ $a == z* ] # File globbing and word splitting take place.
[ "$a" == "z*" ] # True if $a is equal to z* (literal matching).
!=
is not equal to
if [ "$a" != "$b" ]
This operator uses pattern matching within a [[ ... ]] construct.
<
is less than, in ASCII alphabetical order
if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]
Note that the "<" needs to be escaped within a [ ] construct.
>
is greater than, in ASCII alphabetical order
if [[ "$a" > "$b" ]]
if [ "$a" \> "$b" ]
Note that the ">" needs to be escaped within a [ ] construct.
-z
string is null, that is, has zero length
String='' # Zero-length ("null") string variable.
if [ -z "$String" ]
then
echo "\$String is null."
else
echo "\$String is NOT null."
fi # $String is null.
-n
string is not null.
Source: http://tldp.org/LDP/abs/html/comparison-ops.html
[ is also a command in Bash so [ 1 > 2 ] is the same as [ 1 ] > 2 which would succeed and create a file named 2.
As others pointed out you need to use the following syntax to compare integers:
[ 1 -gt 2 ]
[[ 1 -gt 2 ]]
(( 1 > 2 ))
Try with this code:
#!/bin/bash
if [ $(wc -l < f1.txt) -gt $(wc -l < dest/f1.txt) ]; then
echo -e "f1 has more lines"
else
echo -e "f1 doesn't have more lines"
fi
The -gt will proceed with a numeric compare and not ASCII.

How to compare two stat values of the same file?

Screenshot of the my code
I am trying to make a shell program that tells me when a file has been created, when it has been modified, and when it has been deleted. I think I can solve this but my only issue is that I cant compare the stat values. It tells me that I have "too many arguments". Any help would be much appreciated :)
#!/bin/bash
run=yes
if [ -f $1 ]
then
while [ run=yes ]
do
time1=$(stat -c %y $1)
time2=$(stat -c %y $1)
if [ ! $time2 ]
then
echo "The file "$1" has been deleted."
run=no
elif [ $time2 -gt $time1 ]
then
echo "The file "$1" has been modified."
run=no
fi
done
else
while [ run=yes ]
do
sleep 2
if [ -f $1 ]
then
echo "The file "$1" has been created."
run=no
fi
done
fi
The output of static -c %y ... includes spaces, which is what the shell uses to separate arguments. When you then run:
if [ ! $time2 ]; then
This translates into something like:
if [ ! 2017-09-02 08:57:19.449051182 -0400 ]; then
Which is an error. The ! operator only expects a single argument. You could solve it with quotes:
if [ ! "$time2" ]; then
Or by using the bash-specific [[...]]] conditional:
if [[ ! $time2 ]]; then
(See the bash(1) man page for details on that second solution).
Separately, you're not going to be able to compare the times with -gt as in:
elif [ $time2 -gt $time1 ]
This (a) has the same problem as the earlier if statement, and (b) -gt can only be used to compare integers, not time strings.
If you were to use %Y instead of %y, you would get the time as an integer number of seconds since the epoch, which would solve all of the above problems.
The code is now working and I thought I would share the final result if anyone wanted to know.
#!/bin/bash
run=true
if [ -f $1 ]
then
while [ "$run" = true ]
do
time1=$(stat -c %Y $1 2>/dev/null)
sleep $2
time2=$(stat -c %Y $1 2>/dev/null)
if [ ! "$time2" ]
then
echo "The file "$1" has been deleted."
run=false
elif [ $time2 -gt $time1 ]
then
echo "The file "$1" has been modified."
run=false
fi
else
while [ "$run" = true ]
do
sleep 2
if [ -f $1 ]
then
echo "The file "$1" has been created."
run=false
fi
done
fi

Bash while if function

I'm try to run this bash script.
#!/bin/sh
MAX=5
j=1
while [ $((1+$j)) -le $MAX ] do
input=$j
if [ $input -le $j ] then
echo "input=$j,$j,$((j+1)),$((j+2)),$((j+3)),$((j+4))"
else
echo "$input"
fi j=$((j+1))
done
I am writing a bash script and trying to check the order list provided in the argument of the shell values. The ouput has the content as:
input=1,1,2,3,4,5
input=2,2,3,4,5,6
input=3,3,4,5,6,7
input=4,4,5,6,7,8
As what I expect it should give the list in increase order at the each line but the result that i'm looking for is:
input=1,2,3,4,5
input=2,1,3,4,5
input=3,1,2,4,5
input=4,1,2,3,5
input=5,1,2,3,4
Please help me, thanks.
In your script, when you are iterating on variable j, only the while loop is keeping track of the range {1 .. MAX}. Hence, if you are at j=5 in your loop, then running echo on $j,$((j+1)),$((j+2)),$((j+3)),$((j+4)) results 5,6,7,8,9 respectively, which is not what you are looking for.
One approach at a solution is, given a number i, creating a range {1 .. MAX} with i removed. For example, given i=2, creating the list 1,3,4,...,MAX. This can then be concatenated to the final output format as echo "input=$i,$list".
The following range routine creates such a list:
# range() outputs a range of numbers 1 to MAX, but with
# the number 'num' removed from the range.
# Usage: range num MAX
# Example: [ input: range 2 5 ] [ output: 1,3,4,5 ]
range() {
num="$1"
MAX="$2"
for i in $(eval echo {1..$MAX}); do
if [ "$num" -eq "$MAX" ]; then
if [ "$i" -eq $((MAX-1)) ]; then
printf "$i"
break
else
printf "$i,"
fi
elif [ "$i" -eq "$MAX" ]; then
printf "$i"
elif [ "$i" -eq "$num" ]; then
continue
else
printf "$i,"
fi
done
printf "\n"
}
Then your while loop becomes,
j=1
MAX=5
while [ "$j" -le "$MAX" ]; do
list=$(range "$j" "$MAX")
echo "input=$j,$list"
j=$((j+1))
done
whereby list variable is assigned the values of the range created with range "$j" "$MAX", then list is concatenated to final output.
Tests: Assuming the above script is named permute,
# when j=1 and MAX=5
$ ./permute
input=1,2,3,4,5
input=2,1,3,4,5
input=3,1,2,4,5
input=4,1,2,3,5
input=5,1,2,3,4
# when j=1 and MAX=10
$ ./permute
input=1,2,3,4,5,6,7,8,9,10
input=2,1,3,4,5,6,7,8,9,10
input=3,1,2,4,5,6,7,8,9,10
input=4,1,2,3,5,6,7,8,9,10
input=5,1,2,3,4,6,7,8,9,10
input=6,1,2,3,4,5,7,8,9,10
input=7,1,2,3,4,5,6,8,9,10
input=8,1,2,3,4,5,6,7,9,10
input=9,1,2,3,4,5,6,7,8,10
input=10,1,2,3,4,5,6,7,8,9

Why this command not found when i'm comparing strings in an array?

My sample code is here
#!/bin/bash
file="output2.txt"
numbers="$(cut -d',' -f2 output2.txt)"
lines="$(cut -f2 output2.txt)"
hours="$(cut -d',' -f1 output2.txt)"
array_numbers=( $numbers )
lines_array=( $lines )
hours_array=( $hours )
difference=$1
let range=$1-1000
for (( i = 0 ; i < ${#array_numbers[#]} ; i++ ))
do
let num=$(( 10#${array_numbers[$i+1]} - 10#${array_numbers[$i]} ))
if [ $num -gt $1 ]
then
echo ${lines_array[$i+1]} "and" ${lines_array[$i]} "has a difference more than $1"
elif [ $num -ge 0 ] && [ $num -lt $range ]
then
echo ${lines_array[$i+1]} "and" ${lines_array[$i]} "has a difference more than $1"
elif [ $num -le $1 ]
then
if [${hours_array[$i+1]} != ${hours_array[$i]}]
then
echo ${lines_array[$i+1]} "and" ${lines_array[$i]} "has a difference more than one second"
fi
fi
done
I'm working with the same output2.txt again:
12:43:40,317
12:43:40,318
12:43:40,332
12:43:40,333
12:43:40,334
12:43:40,335
12:43:40,336
12:43:40,337
12:43:40,338
12:43:40,339
12:43:40,353
12:43:40,354
12:43:40,356
12:43:40,358
12:43:40,360
12:43:40,361
12:43:40,362
12:43:40,363
12:43:40,364
12:43:40,365
12:43:40,382
12:43:40,384
12:43:40,385
12:43:40,387
12:43:40,388
12:43:40,389
12:43:40,390
12:43:40,391
12:43:40,404
12:43:40,405
12:43:40,406
12:43:40,407
12:43:40,408
12:43:40,409
12:43:40,410
12:43:40,412
12:43:40,413
12:43:40,414
12:43:40,415
12:43:40,428
12:43:40,429
12:43:40,431
12:43:40,432
12:43:40,433
12:43:40,434
12:43:40,435
12:43:40,436
12:43:40,437
12:43:40,438
12:43:40,440
12:43:40,443
12:43:40,458
12:43:40,459
12:43:40,460
12:43:40,461
12:43:40,462
12:43:40,463
12:43:40,464
12:43:40,465
12:43:40,466
12:43:40,479
12:43:40,480
12:43:40,481
12:43:40,482
12:43:40,483
12:43:40,484
12:43:40,485
12:43:40,486
12:43:40,487
12:43:40,501
12:43:40,503
12:43:40,504
12:43:40,505
12:43:40,506
12:43:40,509
12:43:40,510
12:43:40,511
12:43:40,512
12:43:40,513
12:43:40,514
12:43:40,515
12:43:40,517
12:44:40,518
What I want to do is take the difference as parameter and if there is a value difference more than 100 miliseconds than I'm wanna print output. The parts
for (( i = 0 ; i < ${#array_numbers[#]} ; i++ ))
do
let num=$(( 10#${array_numbers[$i+1]} - 10#${array_numbers[$i]} ))
if [ $num -gt $1 ]
then
echo ${lines_array[$i+1]} "and" ${lines_array[$i]} "has a difference more than $1"
elif [ $num -ge 0 ] && [ $num -lt $range ]
then
echo ${lines_array[$i+1]} "and" ${lines_array[$i]} "has a difference more than $1"
are actually working well , but i realized that if input has such a columns in order like the last part
12:43:40,517
12:44:40,518
it won't print anything so i put the last elif statement to my code but even it prints hours_array good, it doesn't work with while i'm comparing them. The output is always :
script.sh: line 22: [12:43:00: command not found
Why doesn't it accept this compare or is the problem is about my bash version ?
Thank you in advance for your help.
Add space before and after [. It is an 'alias' to the test buitin command.
You should also add double quote " around your variable. Because if they are empty, bash won't recognize them as a empty word.
And I generally use double brackets [[ for test condition which is more safer and has more features.
Example:
if [[ "${hours_array[$i+1]}" != "${hours_array[$i]}" ]]
You need a space here (the [ is a command)
if [ ${hours_array[$i+1]} != ${hours_array[$i]} ]
Missing space after [. [ is a command, so it needs to be separated from its arguments.
if [ ${hours_array[$i+1]} != ${hours_array[$i]} ]
I find few things that can be changed in this code.
Add space after [ and before ].
Add double quotes so that in case if variable is empty, script does not throw error.
if [ "${hours_array[$i+1]}" != "${hours_array[$i]}" ]
Also when you reach the last line, $i + 1 will fail. Hence, following would be better.
for (( i = 0 ; i < ${#array_numbers[#]} - 1 ; i++ ))

When/how to use "==" or "-eq" operator in test?

In the following code I want to compare the command line arguments with the parameters but I am not sure what is the current syntax to compare the arguments with parameters..i.e "==" or "-eq".
#!/bin/bash
argLength=$#
#echo "arg = $1"
if [ argLength==0 ]; then
#Running for the very first
#Get the connected device ids and save it in an array
N=0
CONNECTED_DEVICES=$(adb devices | grep -o '\b[A-Za-z0-9]\{8,\}\b'|sed -n '2,$p')
NO_OF_DEVICES=$(echo "$CONNECTED_DEVICES" | wc -l)
for CONNECTED_DEVICE in $CONNECTED_DEVICES ; do
DEVICE_IDS[$N]="$CONNECTED_DEVICE"
echo "DEVICE_IDS[$N]= $CONNECTED_DEVICE"
let "N= $N + 1"
done
for SEND_DEVICE_ID in ${DEVICE_IDS[#]} ; do
callCloneBuildInstall $SEND_DEVICE_ID
done
elif [ "$1" -eq -b ]; then
if [ $5 -eq pass ]; then
DEVICE_ID=$3
./MonkeyTests.sh -d $DEVICE_ID
else
sleep 1h
callCloneBuildInstall $SEND_DEVICE_ID
fi
elif [ "$1" -eq -m ]; then
echo "Check for CloneBuildInstall"
if [ "$5" -eq pass ]; then
DEVICE_ID=$3
callCloneBuildInstall $SEND_DEVICE_ID
else
echo "call CloneBuildInstall"
# Zip log file and save it with deviceId
callCloneBuildInstall $SEND_DEVICE_ID
fi
fi
function callCloneBuildInstall {
./CloneBuildInstall.sh -d $SEND_DEVICE_ID
}
From help test:
[...]
STRING1 = STRING2
True if the strings are equal.
[...]
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
But in any case, each part of the condition is a separate argument to [.
if [ "$arg" -eq 0 ]; then
if [ "$arg" = 0 ]; then
Why not use something like
if [ "$#" -ne 0 ]; then # number of args should not be zero
echo "USAGE: "
fi
When/how to use “==” or “-eq” operator in test?
To put it simply use == when doing lexical comparisons a.k.a string comparisons but use -eq when having numerical comparisons.
Other forms of -eq (equal) are -ne (not equal), -gt (greater than), -ge (greater than or equal), -lt (lesser than), and -le (lesser than or equal).
Some may also suggest preferring (( )).
Examples:
[[ $string == "something else" ]]
[[ $string != "something else" ]] # (negated)
[[ $num -eq 1 ]]
[[ $num -ge 2 ]]
(( $num == 1 ))
(( $num >= 1 ))
And always use [[ ]] over [ ] when you're in Bash since the former skips unnecessary expansions not related to conditional expressions like word splitting and pathname expansion.

Resources