Count occurences of a special character in a file - bash

I am writing a bash script that will count occurences of [], (), {}, '', "", `` and // so that i can debug other scripts for missing one of them.
Here is the code:
a=0; b=0; cc=0; d=0; e=0; f=0; g=0; h=0; i=0; j=0 # set default values
squote=`echo -e "\x27"`
dquote=`echo -e "\x22"`
while IFS= read -r -n1 c; do
[ "$c" == "[" ] && (( a++ ))
[ "$c" == "]" ] && (( b++ ))
[ "$c" == "(" ] && (( cc++ )) # i imported this line from another online script, is there a reason why the author avoids (( c++ )) ?
[ "$c" == ")" ] && (( d++ ))
[ "$c" == "{" ] && (( e++ ))
[ "$c" == "}" ] && (( f++ ))
[ "$c" == "\x27" ] && (( g++ )) # single quote
[ "$c" == "$dquote" ] && (( h++ )) # double quote
[ "$c" == '`' ] && (( i++ )) # back tick
[ "$c" == '/' ] && (( j++ )) # forward slash
done < "$1"
echo '[]'="$a,$b"
echo '()'="$cc,$d"
echo '{}'="$e,$f"
echo "$squote" ="$g"
echo "$dquote" ="$h"
echo '``'="$i"
echo '/'="$j" # when used this way: /hello/
Questions:
1) why the cc++ instead of c++
2) why some results don't come out right at all.
3) How to annotate the special characters the right way.
Thank you in advance for your help.

some simplifications and functional script
#!/bin/bash
a=0; b=0; c=0; d=0; e=0; f=0; g=0; h=0; i=0; j=0
while read -r -n1 char; do
case "$char" in
"[" ) (( a++ )) ;;
"]" ) (( b++ )) ;;
"(" ) (( c++ )) ;;
")" ) (( d++ )) ;;
"{" ) (( e++ )) ;;
"}" ) (( f++ )) ;;
"'" ) (( g++ )) ;;
'"' ) (( h++ )) ;;
'`' ) (( i++ )) ;;
'/' ) (( j++ )) ;;
esac
done <<< "[]{}()/"$'\x60'$'\x22'$'\x27' # $'\x60' == backtick
echo '[]'="$a,$b"
echo '()'="$c,$d"
echo '{}'="$e,$f"
echo "'" ="$g"
echo '"' ="$h"
echo '`'="$i"
echo '/'="$j"
output
[]=1,1
()=1,1
{}=1,1
' =1
" =1
`=1
/=1
nota:
prefer: dquote=$( echo -e "\x22" ) # and not ``
better: dquote=$'\x22' # or \042 octal
simpler: dquote='"'
to check script and learn, I suggest to install shellcheck or use https://www.shellcheck.net/
initially:
[ "$char" == "[" ] && (( a++ ))
[ "$char" == "]" ] && (( b++ ))
[ "$char" == "(" ] && (( c++ ))
[ "$char" == ")" ] && (( d++ ))
[ "$char" == "{" ] && (( e++ ))
[ "$char" == "}" ] && (( f++ ))
[ "$char" == "'" ] && (( g++ ))
[ "$char" == '"' ] && (( h++ ))
[ "$char" == '`' ] && (( i++ ))
[ "$char" == '/' ] && (( j++ ))
but unnecessary tests are systematically performed.
we can also use:
if [[ $char == "[" ]]; then (( a++ ))
elif [[ $char == "]" ]]; then (( b++ ))
...
but performances with 10,000 iterations:
if > if elif > case
6720 ms > 2895 ms > 2073 ms

Related

Values of an array not comparing to numbers correctly

Im trying to get an array from grades.txt, and determine what letter grade it should be assigned.
I either get
hw4part2.sh: line 26: [: : integer expression expected
If i use -ge or
hw4part2.sh: line 26: [: : unary operator expected
If i use >=
Below is the code im trying to get working
mapfile -t scores < grades.txt
numOScores=0
numOA=0
numOB=0
numOC=0
numOD=0
numOF=0
DoneWScores=0
A=90
B=80
C=70
D=60
F=59
while [ $DoneWScores -eq 0 ]
do
numOScores=$((numOScores + 1))
if [ "${scores[$numOScores]}" -ge "$A" ]
then
echo "A"
elif [ "${scores[$numOScores]}" -ge "$B" ]
then
echo "B"
elif [ "${scores[$numOScores]}" -ge "$C" ]
then
echo "C"
elif [ "${scores[$numOScores]}" -ge "$D" ]
then
echo "D"
elif [ "${scores[$numOScores]}" -le "$F" ]
then
echo "F"
else
echo "Done/error"
DoneWScores=1
fi
done
If anyone knows what my problem is, that'd be greatly appreciated
Consider this:
#!/usr/bin/env bash
if (( ${BASH_VERSINFO[0]} < 4 )); then
echo "Bash version 4+ is required. This is $BASH_VERSION" >&2
exit 1
fi
letterGrade() {
if (( $1 >= 90 )); then echo A
elif (( $1 >= 80 )); then echo B
elif (( $1 >= 70 )); then echo C
elif (( $1 >= 60 )); then echo D
else echo F
fi
}
declare -A num
while read -r score; do
if [[ $score == +([[:digit:]]) ]]; then
grade=$(letterGrade "$score")
(( num[$grade]++ ))
echo "$grade"
else
printf "invalid score: %q\n" "$score"
fi
done < grades.txt
for grade in "${!num[#]}"; do
echo "$grade: ${num[$grade]}"
done | sort

Bash if else if conditions issue

I am trying some simple bash scripting, and I am working on a simple comparison script, where the program reads a character char, and if it is "Y" or "y", it prints "YES", and if it is "N" or "n", it is "NO". The letters Y, y, N, n are supposedly the only ones that will be used as input.
#!/bin/sh
read char
if (( $char = "Y" )) || (( $char = "y" ))
then
echo "YES"
else
echo "NO"
fi
The above script works with Y or y, but when I try to put N or n, it still prints "YES", and I'm trying to figure out what I am doing wrong. I know it's something to do with the parentheses, because when I use a syntax with brackets, like this
if [ "$char" == "Y" ] || [ "$char" == "y" ]
it works just fine.
I have tried all the combinations for the parentheses, like
if (( "$char" = "Y" )) || (( "$char" = "y" ))
or
if (( $char == "Y" )) || (( $char == "y" ))
or
if (( "$char" == "Y")) || (("$char" == "y" ))
or
if (( "$char" == "Y" || "$char" == "y" ))
but none of them work.
Can you please tell me what is the mistake I am making?
(( ... )) is for arithmetic only; you are performing string comparison. Use [[ instead. You can use pattern matching to check for either upper- or lowercase at the same time.
if [[ $char = [Yy] ]]; then
If you really want to use /bin/sh for portability, then use two [ commands:
if [ "$char" = Y ] || [ "$char" = y ]; then

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 programming: evaluation in conditional statements

In bash scripting the if condition statement is not working properly with using "&&"
ARGCOUNT=$#
if (( "$ARGCOUNT" != "2" )) ;then
echo "number of arguments must be two"
fi
DFLAG=$1
HFLAG=$2
if (((( $DFLAG = "Mon" )) || (( $DFLAG = "MON" )) || (( $DFLAG = "mon" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
echo " CS599 "
cd CS599
elif (((( $DFLAG = "Wed" )) || (( $DFLAG = "WED" )) || (( $DFLAG = "wed" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS699
echo " CS699 "
elif (((( $DFLAG = "Fri" )) || (( $DFLAG = "FRI" )) || (( $DFLAG = "fri" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS799
echo " CS799 "
else
echo "."
fi
my program is executing only else statement irrespective of arguments. means it evaluating if block false.
What is the problem ?
The parenthesis you use are for arithmetic evaluation. I think you are over using them, and it makes your script complicated.
This snippet below does work:
#!/bin/bash
ARGCOUNT=$#
if [ "$ARGCOUNT" -ne 2 ] ;then echo "number of arguments must be two"; fi
# put DFLAG in lower case (see man bash).
DFLAG=${1,,}
HFLAG=$2
if [ "$DFLAG" = 'mon' -a "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
echo ok
else
echo failed
fi
As you can see, I optimized your expression:
Except for the case of $ARGCOUNT (which is safe because you initialized it to $#), don't forget to encase variable with double quote to avoid expansion.
In the declaration of DFLAG, I used the convert to lower case string operator (?). With that you won't have to check for each permutation of case in DFLAG. This might not work in bash3.
If you use the test or [ builtin, you can use -a between each expression to do a and.
Arithmetic evaluation with the test/[ builtin use the following operators: -ne (inequality), -eq (equality)-ge (greater or equal), -le (lesser or equals), -lt (lesser), -gt (greater).
As said in another answer, you can replace "$DFLAG" = 'mon' by "$DFLAG" == 'mon'. But this is not POSIX conformant (as said in my comment below) and I'm not enough knowledgeable on that to know if it's a good idea or not.
On a side note, if $HFLAG condition should always be the same, you can write your code like this:
if [ "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
case "$DFLAG" in
mon|Mon|MON)
echo "monday";
;;
fry|Fry|FRY)
echo "friday";
;;
*)
echo "other"
;;
esac
fi
If that case, I putted back all permutation of case in case you were in bash3, to show you an example to do without ${DFLAG,}.
If you are looking for what mistake you did which is making condition to go to else part ...then it's simple mistake which almost every programmer do once in life ... using single "=" instead of "==" during comparison. Modify it accordingly and you should get expected flow/result in your script.
One eg.
$DFLAG = "Mon"
change to below notice the double equal sign
"$DFLAG" == "Mon"
First, you should go easy on the parenthesis. Those are fragile things.
Using Bash syntax (non-POSIX, less portable), you can write:
ARGCOUNT=$#
DFLAG=${1,,} # lower case
HFLAG=$2
# don't need the quotes in (( )) as we test arithmetic value
(( $ARGCOUNT != 2 )) && echo "number of arguments must be two"
shopt -s extglob # for #(1|2|3) below, see http://mywiki.wooledge.org/glob#extglob
if [[ $DFLAG = "mon" && $HFLAG == #(2|3|4) ]]; then
echo " CS599 "
cd CS599
if [[ $DFLAG = "wed" && $HFLAG == #(2|3|4) ]]; then
cd CS699
echo " CS699 "
if [[ $DFLAG = "fri" && $HFLAG == #(2|3|4) ]]; then
cd CS799
echo " CS799 "
else
echo "."
fi
Now you see how much you repeat yourself and can improve the algorithm. For instance, test HFLAG, if valid test DFLAG otherwise …
Read
Tests And Conditionals
Arithmetic Expression
Globbing extglob

bash for with numerical comparison and file existence

I tried to do a for loop with 2 conditions but I didn't succeed in any way:
for (( i=0 ; -e /file && i < 10 ; i++ ))
of course I tried any combination of parentheses like:
for (( i=0 ; [ -e /file ] && [ i < 10 ] ; i++ ))
for (( i=0 ; [ -e /file -a i < 10 ] ; i++ ))
What's wrong on this?
I googled a lot for this, but I didn't find any suggestion.
You have to do some subshell trickery to pull this off:
for (( i=0 ; $([ -e /file -a $i -lt 10 ]; echo "$?") == 0; i++ ))
Or probably better:
for (( i=0 ; $([ -e /file ]; echo "$?") == 0 && i < 10; i++ ))
What's happening here is that $(...) is being run and placed into the mathematical expression ... == 0. When it's run the echo "$?" spits out the return code for [ which is 0 for no-error (i.e. expression is true), and 1 for error (i.e. expression is false) which then gets inserted as 0 == 0 or 1 == 0.

Resources