Bash script - a puzzle - bash

The script takes only one argument (call it n). It then alternately adds and subtracts each digit of the numbers from 1 through n.
For example:
n = 12
1 - 2 + 3 - 4 + 5 - 6 + 7 - 8 + 9 - 1 + 0 - 1 + 1 - 1 + 2
output: 5
#!/bin/bash
number=$1
result=1
if (( number == 1 || number == 0 )); then
echo
echo number: $number
echo
else
for (( i = 1; i < number; i++ )); do
if (( i < 10 )); then
if (( i % 2 == 0 )); then
let "result = result + i + 1"
else
let "result = result - i - 1"
fi
else
for (( i = 10; i <= number; i++ )); do
if (( i < 100 && i >= 10)); then
let "result = result - i/10 + i%10"
else
let "result = result - i/100 + (i%100)/10 - (i/100)%10"
fi
done
fi
done
echo
echo result: $result
echo number: $number
echo
fi
It works fine for ints 0 to 9, but for n>9 it gives the wrong answer.
Please correct my script and explain to me why it's behaving like this.
PS I'm new to bash scripting, so I'd love to see another solution to this puzzle
PPS I'm familiar to java
EDIT
I fixed the code, and it works correct now
#!/bin/bash
number=$1
result=1
if (( number == 1 || number == 0 )); then
echo
echo number: $number
echo
else
for (( i = 1; i < number; i++ )); do
if (( i < 9 )); then
if (( i % 2 == 0 )); then
let "result = result + i + 1"
else
let "result = result - i - 1"
fi
fi
done
for (( i = 10; i <= number; i++ )); do
if (( i < 100 )); then
let "result = result - i/10 + i%10"
else
if (( i % 2 == 0 )); then
let "result = result - i/100 + (i%100)/10 - (i%100)%10"
else
let "result = result + i/100 - (i%100)/10 + (i%100)%10"
fi
fi
done
echo
echo result: $result
echo number: $number
echo
fi

Ok this should work apologies that I miss-read the question earlier
number=$1
MATSTR="";
for((i=1; i <= number; i++)); do
MATSTR="$MATSTR$i";
done;
echo $MATSTR | sed -e 's/\(.\)\(.\)/\1-\2+/g' | sed 's/+$//' | bc
Explanation:
For loop to create string concatenating all numbers from 1 to the number.
sed statement replaces the every two digits with 1st digit minus second digit plus
sed removes any trailing plus
bc calculates the statement

Here's your original solution, but with bug fixes (in comments). It still won't work for number > 99, of course.
#!/bin/bash
number=$1
result=0 # Don't start at 1!!
if (( number == 1 || number == 0 )); then
echo
echo number: $number
echo
else
for (( i = 1; i <= number; i++ )); do # <=, not <
if (( i < 10 )); then # < 10, not < 9
if (( i % 2 == 0 )); then
let "result = result - i" # subtraction! Also, the +1 was unnecessary
else
let "result = result + i" # addition!
fi
else
if (( i < 100 && i >= 10)); then
let result=result-i/10+i%10
else
let "result=result-i/100+(i%100)/10-(i/100)%10"
fi
fi
done
echo
echo result: $result
echo number: $number
echo
fi

After misreading the question, (or the question being unclear), the answer took only minor adjustments. The sting solution is probably the simplest approach using modulo to togget +/-:
#!/bin/bash
declare -i num=$1
declare -i res=0
str=""
[ "$num" -gt 1 ] || {
printf "\n Error: invalid input. usage: %s int (greater than 1)\n\n" "${0//*\//}"
exit 1
}
for ((i = 1; i <= $num; i++)); do # create the string of digits
str="${str}${i}"
done
printf "\n Calculations:\n\n"
for ((i = 0; i < ${#str}; i++)); do # walk down the string adding/subtracting each digit
if [ $((i % 2)) -eq 0 ]; then
((res+=${str:$i:1}))
printf " res + %3s = %3s\n" "${str:$i:1}" "$res"
else
((res-=${str:$i:1}))
printf " res - %3s = %3s\n" "${str:$i:1}" "$res"
fi
done
printf "\n Final Result: %s\n\n" "$res"
exit 0
output:
$ ./puzzle.sh 12
Calculations:
res + 1 = 1
res - 2 = -1
res + 3 = 2
res - 4 = -2
res + 5 = 3
res - 6 = -3
res + 7 = 4
res - 8 = -4
res + 9 = 5
res - 1 = 4
res + 0 = 4
res - 1 = 3
res + 1 = 4
res - 1 = 3
res + 2 = 5
Final Result: 5

Related

Breaking out of a nested loop in bash

How do you break out of a nested loop in bash?
Tried continue and break. break worked. But want to learn more.
for i in 1 2 3; do
if [[ $flag -eq 1 ]]; then
break
fi
done
How does break actually know the loop is nested?
Instead of break, can I use i=4 or something out of range to exit from a loop.
Use break followed by a number, to break out of that many levels of nesting. Example:
for i in 1 2 3 4 5; do
echo
echo i = $i
for j in 1 2 3 4 5; do
echo j = $j
if [ $j -eq 4 ]; then break; fi
if [ $j -eq 3 ] && [ $i -eq 4 ]; then break 2; fi
done
done
Result:
i = 1
j = 1
j = 2
j = 3
j = 4
i = 2
j = 1
j = 2
j = 3
j = 4
i = 3
j = 1
j = 2
j = 3
j = 4
i = 4
j = 1
j = 2
j = 3

How to process value from for loop in shell

I have to a perform logic like this.
I have a array.
expression for this in shell
[(first no + arrlen) - ( index +1 ) - ge 10 ]
I have code this like this but it's not working
#!/bin/bash
array=(4 5 6 7 8 9)
for i in ${array[#]}
do
echo $i
done
echo "${#array[#]}"
l=${#array[#]}
count=0
for (( i=0; i < ${#array[#]}; i++ ))
do
if [ ($(`expr $i + $l`) - $(`expr ${!array[#]} + 1`)) -ge 10 ]
then
count=`expr $count + 1`
else
echo
fi
done
Your code could look like this:
#!/bin/bash
array=(4 5 6 7 8 9)
for i in "${array[#]}"; do
echo "$i"
done
length=${#array[#]}
first=${array[0]}
count=0
for (( i=0; i < length; i++ )); do
if (( (first + length) - (i + 1) >= 10 )); then
((count++))
else
echo "something"
fi
done
Don't use expr, use (( )) for arithmetic expressions
Quote expansions: "$i", "${array[#]}", ...
${!array[#]} expands to ALL indexes of your array, not the current index

Shell output to text file

So, I have the following shell:
#!/bin/bash
for (( i = 1; i <= 6; i++ ))
do
printf in_port=$i,actions=
for (( j = 1 ; j <= 6; j++ ))
do
if [ $i != $j ]; then
printf output:$j,
fi
done
printf "\n"
done
Which, produces the following output:
home#mininet:~$ ./hostsScript.sh
in_port=1,actions=output:2,output:3,output:4,output:5,output:6,
in_port=2,actions=output:1,output:3,output:4,output:5,output:6,
in_port=3,actions=output:1,output:2,output:4,output:5,output:6,
in_port=4,actions=output:1,output:2,output:3,output:5,output:6,
in_port=5,actions=output:1,output:2,output:3,output:4,output:6,
in_port=6,actions=output:1,output:2,output:3,output:4,output:5,
How would I go about appending each line of this output to a txt file, line-by-line?
option 1 inside the script:
#!/bin/bash
for (( i = 1; i <= 6; i++ ))
do
printf in_port=$i,actions=
for (( j = 1 ; j <= 6; j++ ))
do
if [ $i != $j ]; then
printf output:$j,
fi
done
printf "\n"
done >> output_file
option 2 inside the script:
#!/bin/bash
exec >> output_file
for (( i = 1; i <= 6; i++ ))
do
printf in_port=$i,actions=
for (( j = 1 ; j <= 6; j++ ))
do
if [ $i != $j ]; then
printf output:$j,
fi
done
printf "\n"
done
option 3 in run command:
./hostsScript.sh >> output_file

Extract data from file with awk in bash script and store

I am new here and also a starter on programming so please handel me with care .
Filename = Datafile.tmp
I get a log file with 1 line of data like this (comma seperated)
24092014,17:21,357.7,5.1,49.96,228.6,7.9,1757.5,3378200.0,28.2,20680.0,846.0,1800.0,2
Maby i must explain shomething. I have a script writing in BASH that read out the data from the inverter by inside routine. I want to change that script so it read data out of the data file like above. The script is verry long but a place only the reading part of it.
So i think i can del all the reading port routines and just focus on the
convert data from array into readable data in my case from file.
and at the bottum storing variables to working file
#Ask data from inverter
get_data() {
printf $GetDataCMD > $PORT
dd if=$PORT of=$OUT bs=1 count=$dataLengthTotal &
PID=$!
sleep 3
if [ -d /proc/$PID ]
then
rcvd="no"
kill $PID
else
rcvd="yes"
fi
}
#Put received data in array and check for errors
receive_data() {
(( dataLengthTotal = $ANS_OFFSET+$dataLengthOutput+1 ))
retry_counter=$RETRY
while (( retry_counter )); do
get_data < $PORT 2>/dev/null
if [ $rcvd = "yes" ]; then
data=(`od -b -v $OUT | sed -e 's/^[0-9]*//g' | sed -e 's/[ ]\([0-9]\)/ 0\1/g'`)
if [ ${#data[#]} -ne $dataLengthTotal ]; then
rcvd="nochk"
echo -e `date -R`": Wrong amount of data received\r" >> $LOG
init_comport
(( retry_counter -= 1 ))
else
# Check CRC
sumstr="${data[#]:$ANS_OFFSET:$dataLengthOutput}"
let sum=${sumstr// /+}
if ((sum%256 == data[9+$dataLengthOutput])); then
rcvd="chk"
retry_counter=0
else
rcvd="nochk"
echo -e `date -R`": Checksum error in received data\r" >> $LOG
init_comport
(( retry_counter -= 1 ))
fi
fi
else
init_comport
(( retry_counter -= 1 ))
fi
done
}
#convert data from array into readable data
get_current_data() {
dataLengthOutput=30
GetDataCMD=$GetCurrentDataCMD
receive_data
if [ $rcvd = "chk" ]; then
#errorbits
(( errbits = data[9+7] * 256 + data[9+6] ))
#Voltage Solarpanels (Usol)
(( Usol = data[9+9] * 256 + data[9+8] ))
#Current Solarpanels (Isol)
(( Isol = data[9+11] * 256 + data[9+10] ))
#AC Frequency (Fnet)
(( Fnet = data[9+13] * 256 + data[9+12] ))
#AC Voltage (Unet)
(( Unet = data[9+15] * 256 + data[9+14] ))
#AC Current (Inet)
(( Inet = data[9+17] * 256 + data[9+16] ))
#AC Power (Pnet)
(( Pnet = data[9+19] * 256 + data[9+18] ))
#Total delivered (Etot)
(( Etot = data[9+22] * 65536 + data[9+21] * 256 + data[9+20] + WtotOffset ))
#Temp Inverter (Tinv)
(( Tinv = data[9+23] ))
#Total runtime (Htot)
(( Htot = data[9+26] * 65536 + data[9+25] * 256 + data[9+24] + HtotOffset ))
#Total CO2 (Ctot)
(( Ctot = Etot * $CO2perc / 100 ))
fi
}
#check, convert and write error messages
convert_errorbits() {
ErrorStr="Systeem_OK"
if (( errbits )); then
if (( errbits & 0x01 )); then
ErrorStr="Usolar too high (`print_float $Usol 1` V). ${ErrorStr}"
fi
if (( errbits & 0x02 )); then
ErrorStr="Usolar too low (`print_float $Usol 1` V). ${ErrorStr}"
fi
if (( errbits & 0x04 )); then
ErrorStr="No mains detected. ${ErrorStr}"
fi
if (( errbits & 0x08 )); then
ErrorStr="Uac too high (${Unet} V). ${ErrorStr}"
fi
if (( errbits & 0x10 )); then
ErrorStr="Uac too low (${Unet} V). ${ErrorStr}"
fi
if (( errbits & 0x20 )); then
ErrorStr="FreqAC too high (`print_float $Fnet 2` Hz). ${ErrorStr}"
fi
if (( errbits & 0x40 )); then
ErrorStr="FreqAC too low (`print_float $Fnet 2` Hz). ${ErrorStr}"
fi
if (( errbits & 0x80 )); then
ErrorStr="Temperature error (${Tinv}ÂșC). ${ErrorStr}"
fi
if (( errbits & 0x100 )); then
ErrorStr="Hardware error. ${ErrorStr}"
fi
if (( errbits & 0x200 )); then
ErrorStr="Starting. ${ErrorStr}"
fi
if (( errbits & 0x400 )); then
ErrorStr="Max output (${Pnet} W). ${ErrorStr}"
fi
if (( errbits & 0x800 )); then
ErrorStr="I max (`print_float $Isol 2` A). ${ErrorStr}"
fi
echo -e `date -R`": Error message: $ErrorStr\r" >> $LOG
fi
}
#Get current day output
get_current_day() {
dataLengthOutput=8
(( daynr = 0 ))
(( crc = $GetDailyDataCRC+$daynr ))
GetDataCMD=$GetDailyDataCMD1\\x`printf "%02X" $1 $daynr`$GetDailyDataCMD2\\x`printf "%02X" $1 $crc`
receive_data
if [ $rcvd = "chk" ]; then
# Profit [kWH]
(( Etoday = data[9+7] * 256 + data[9+6] ))
# Runtime [h:mm]
(( Htoday = data[9+5] * 5 ))
# CO2
(( CO2 = Etoday * $CO2perc / 100 ))
fi
}
# Main Program
main(){
#set time variables
MTIME=`date +%M`
HTIME=`date +%H`
# Configure serial port
init_comport
#Store file contains data needed for calculating totals etc
#Internally an array is used, values are:
#0=Active flag
#1=WtotAct
#2=HtotAct
#3=WtotLog
#4=HtotLog
#fill array
store=(`cat $STORE`)
get_current_data
if [ $rcvd = "chk" ]; then
if (( ! store[0] )); then
echo -e `date -R`": Waking up; Inverter started.\r" >> $LOG
store[0]=1
if (( USE_30DAYS )); then
get_last_30_days
fi
if [ ! -f $PVDIARYCSV ]
then
create_pvdiary
fi
if (( USE_EMAIL )) && (( USE_EMAwake )); then
email_send "Bericht van uw zonnepanelen : ontwaken." "I'm awake now !\n\nUw Mastervolt Omvormer\n"
fi
fi
# Remember 'old-values' for calculating PVDiary inputs
(( store[5] = store[1] ))
(( store[6] = 0 ))
# Put total values into array for calculating daily profit
store[1]=$Etot
store[2]=$Htot
convert_errorbits
get_current_day
create_actual_page
rrd_update
fill_pvdiary
fill_sql
pvoutput_live_update
fi
#Write message before sleep
if [ $rcvd = "no" ] && (( ! retry_counter )) && (( store[0] )); then
echo -e `date -R`": No reaction from Inverter; entering sleep\r" >> $LOG
create_offline_page
store[0]=0
fi
#Write 0-values in PVDiary-file
if [ $rcvd = "no" ] && (( ! retry_counter )) ; then
(( store[5] = store[1] ))
(( store[6] = 0 ))
fill_pvdiary
(( store[5] = store[1] ))
(( store[6] = 0 ))
fill_sql
(( store[5] = 0 ))
fi
#Run timebased scripts (internal cron)
if [ $HTIME = 23 ] && [ $MTIME = 00 ]; then
fill_csv
fi
if [ $MTIME = 00 ] && (( USE_RRD )); then
graph
fi
if [ $MTIME = 00 ] && (( store[0] )) && (( USE_EMAIL )) && (( USE_EMAhour )); then
email_send_actual "Bericht van uw zonnepanelen : productie tot nu toe."
fi
#storing variables to working file
echo ${store[0]} ${store[1]} ${store[2]} ${store[3]} ${store[4]} ${store[5]}${store[6]}> $STORE
}
#What to do ?
case $1 in
"help" ) help;;
"create" ) create;;
"draw" ) graph;;
* ) main;;
esac
$ awk -F, '{print "Today = " $1 "\nTime = " $2 "\nHertz = " $5}' file
Today = 24092014
Time = 17:21
Hertz = 49.96

bash's arcana with loop

Have some function with loop:
runcfg () {
o=0
while (( o<3 )); do
echo $o
(( o++ ))
done
}
It calls from another script manager.sh by:
...
-C|--runconfig)
source $APP_BASEDIR/app-install/bin/config_functions; runcfg;
shift;;
...
Let's run it:
$ bash -x manager.sh -run
And look at output:
...
+ runcfg
+ o=0
+ (( o<3 ))
+ echo 0
0
+ (( o++ ))
That's all lines... Nothing else happens.
But! If make little chage and add && after arithmetic call:
runcfg () {
o=0
while (( o<3 )); do
echo $o
(( o++ )) && echo ++OK || echo ++ER
done
}
It works!
...
+ runcfg
+ o=0
+ (( o<3 ))
+ echo 0
0
+ (( o++ ))
+ echo ++ER
++ER
+ (( o<3 ))
+ echo 1
1
+ (( o++ ))
+ echo ++OK
++OK
+ (( o<3 ))
+ echo 2
2
+ (( o++ ))
+ echo ++OK
++OK
+ (( o<3 ))
+ shift
...
What is this?
Your script is configured to exit on error (note that it prints ++ER the first time), and ((o++)) == ((0)), which returns 1 (an error). Options:
Use let o++ instead.
Ignore the error (((o++))||:)
Use a for loop (for ((o = 0; o < 3; o++)))
Turn off exit-on-error (set +e).

Resources