printf output convert from one row to number of arguments - bash

this is part of my code:
FCWWN=`fcinfo hba-port | grep HBA | awk '{print $4}'`
for i in ${FCWWN}
do
FCREMOTE=`fcinfo remote-port -p ${i} | grep Remote | nawk '{print $4}'`
PRODUCT=`/usr/sbin/luxadm display ${FCREMOTE} | grep Product | nawk -F: '{print $2}'`
CAPACITY=`/usr/sbin/luxadm display ${FCREMOTE} | grep capacity | nawk -F: '{print $2}'`
LUNNAME=`/usr/sbin/luxadm display ${FCREMOTE} | grep /dev/rdsk | grep -i -v DEVICE`
FCSTATE=`fcinfo hba-port ${i} | grep State: | nawk '{print $2}'`
echo ""
echo -e The FC Port WWN "\033[1m \E[36;40m ${i} \033[0m is \033[1m \E[37;42m ${FCSTATE} \033[0m"; echo -ne "\E[0m"
printf "################################################################################## \n"
printf "%-6s %9s %18s \n" "LUN:" "Storage" "Disk"
printf "%-6s %9s %18s \n" " " "Type" "Size"
printf "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- \n"
printf "%-6s\n %9s\n %2s\n" "${LUNNAME}" "${PRODUCT}" "${CAPACITY}"
done
The output is :
The FC Port WWN 10000000c9822976 is online
##################################################################################
LUN: Storage Disk
Type Size
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
/dev/rdsk/c4t60060E80102A8AF005119C37000000C2d0s2
/dev/rdsk/c4t60060E80102A8AF005119C37000000C0d0s2
/dev/rdsk/c4t60060E80102A8AF005119C370000016Cd0s2
/dev/rdsk/c4t60060E80102A8AF005119C3700000028d0s2
/dev/rdsk/c4t60060E80102A8AF005119C37000000E1d0s2
DF600F
DF600F
DF600F
DF600F
DF600F
131072.000 MBytes
131072.000 MBytes
65536.000 MBytes
262144.000 MBytes
65536.000 MBytes
And the desired output is :
The FC Port WWN 10000000c9822976 is online
##################################################################################
LUN: Storage Disk
Type Size
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
/dev/rdsk/c4t60060E80102A8AF005119C37000000C2d0s2 DF600F 131072.000 MBytes
/dev/rdsk/c4t60060E80102A8AF005119C37000000C0d0s2 DF600F 131072.000 MBytes
/dev/rdsk/c4t60060E80102A8AF005119C370000016Cd0s2 DF600F 65536.000 MBytes
/dev/rdsk/c4t60060E80102A8AF005119C3700000028d0s2 DF600F 262144.000 MBytes
/dev/rdsk/c4t60060E80102A8AF005119C37000000E1d0s2 DF600F 65536.000 MBytes
Any help would be appreciate.

One quick way is to convert your three multi-line strings to arrays:
# bash 4 or later
mapfile -t LUN_ARR <<< "$LUNNAME"
mapfile -t PROD_ARR <<< "$PRODUCT"
mapfile -t CAP_ARR <<< "$CAPACITY"
# bash 3
IFS=$'\n' read -d '' -a LUN_ARR <<< "$LUNNAME"
IFS=$'\n' read -d '' -a PROD_ARR <<< "$PRODUCT"
IFS=$'\n' read -d '' -a CAP_ARR <<< "$CAPACITY"
Then simply iterate through the arrays with a C-style loop
for (( idx=0; idx < ${#LUN_ARR[#]}; idx++ )); do
printf "%-40s %9s %s\n" "${LUN_ARR[idx]}" "${PROD_ARR[idx]}" "${CAP_ARR[idx]}"
done

You're very close. I don't have resources to test, but I really thing this will solve your problem.
not
printf "%-6s\n %9s\n %2s\n" "${LUNNAME}" "${PRODUCT}" "${CAPACITY}"
but
printf "%-6s %9s %2s\n" "${LUNNAME}" "${PRODUCT}" "${CAPACITY}"
The extra '\n's will always put in a line breaks, right?
Also you could write this as one awk script.
OR at least reduce the number of procesess that you're starting by re-writting the front part of your script like
PRODUCT=`/usr/sbin/luxadm display ${FCREMOTE} | nawk -F: '/Product/{print $2}'`
And, as you're using nawk, that says solaris to me. Maybe you're writting /bin/sh (bourne shell scripts),
but if not, back-ticks have been deprecated since 1995 (at least). Use modern command-substitution like
PRODUCT=$(/usr/sbin/luxadm display ${FCREMOTE} | nawk -F: '/Product/{print $2}')
IHTH

Related

Bash math - Dividing a bunch of rows using for statement

Example file:
25 Firstname1 Lastname1 domain1.com planname #1.00 USD Monthly Active 04/24/2016 Edit
1068 Firstname2 Lastname2 domain2.com planname #7.95 USD Annually Active 05/09/2016 Edit
3888 Firstname3 Lastname3 domain3.com planname #19.95 USD Biennially Active 05/04/2016 Edit
I am extracting just the price and billing cycle and am converting the billing cycles into numerical value this way I can divide the price by the billing cycle to get a cost per month.
When using the for statement, its adding line breaks which is breaking the math.
Code:
for i in `cat asd | cut -d "#" -f 2 | awk '{print $1, $3}' | sed 's/Monthly/\/ 1/g' | sed 's/Annually/\/ 12/g' | sed 's/Biennially/\/ 24/g' |grep -Ev 0.00` ; do echo $i | bc -l' ; done
I would prefer to be able to get 1 answer meaning all the rows get divided up then added together to get one final answer.
All those calls to cat, cut, awk, sed, grep and bc - what a waste.
This is a mis-named post, because you are not using Bash to do any calculations. The reason is that bash, unlike korn shell (ksh), does not support floating point. So you fall back to utilities like bc. Hold on though, awk supports floating point as well.
awk is a programming language in its own right. This just uses one instance of awk. I have embedded it inside a bash script because you are probably doing other stuff, but with a little adjustment it could be stand-alone with #!/bin/awk at the top:
infile='asd'
# -f - means "read the program from stdin"
# << '_END_' is a here document. Redirect stdin from here to the label _END_
awk -f - "$infile" << '_END_'
BEGIN {
# an associative array for the billing cycles
cycles["Monthly"] = 1
cycles["Annually"] = 12
cycles["Biennially"] = 24
}
{
sub(/#/,"",$6) # Remove the # from the amount
total += $6/cycles[$8] # divide amount by the billing cycle, add to total
}
END { print total }
_END_
Don't you think this is simpler to understand and maintain? It's also more efficient. This awk script is probably a good exercise for an awk 101 training course.
You could do something like this: (If you are totally set on a single line)
cat asd | cut -d "#" -f 2 | awk '{print $1, $3}' | sed 's/Monthly/\/ 1/g' | sed 's/Annually/\/ 12/g' | sed 's/Biennially/\/ 24/g' | grep -Ev 0.00 | while IFS= read -r line; do echo "$line" | bc -l; done | tr '\n' '+' | sed 's/+$/\n/' | bc -l
But this would be way more clear:
tmp=$(mktemp)
cat asd | cut -d "#" -f 2 | awk '{print $1, $3}' | sed 's/Monthly/\/ 1/g' | sed 's/Annually/\/ 12/g' | sed 's/Biennially/\/ 24/g' | grep -Ev 0.00 > $tmp
tmp2=$(mktemp)
cat $tmp | while IFS= read -r line; do
echo "$line" | bc -l >> $tmp2
done
# Actual output
cat $tmp2 | tr '\n' '+' | sed 's/+$/\n/' | bc -l
rm $tmp $tmp2

Bash Script with IF Conditions for Numbers with Comma

i'm new into bash scripts and i really need your help.
The following Script gets humidity and temperature from a DHT22 sensor on my BananaPi and sends it to my HomeAutomation.
#!/bin/bash
cd /opt/lol_dht22
WERTE=$(./loldht 7 | grep "Humidity")
Temp=( $(echo $WERTE | awk '{ print $ 7}'))
Hum=( $(echo $WERTE | awk '{ print $ 3}'))
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 temperature $Temp"
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 humidity $Hum"
The results are like 30.00 (With a Comma), and sometimes the sensor fails and gives an unrealistic value like -3000.00 or similar.
So i wanted to implement a IF Condition which checks if it's greater or equal 0 or less or equal 100.
I tried things like:
#!/bin/bash
cd /opt/lol_dht22
WERTE=$(./loldht 7 | grep "Humidity")
Temp=( $(echo $WERTE | awk '{ print $ 7}' | tr '.' ','))
Hum=( $(echo $WERTE | awk '{ print $ 3}' | tr '.' ','))
if [[ $Temp -ge 0 && $Temp -le 100 && $Hum -ge 0 && $Hum -le 100 ]]; then
Temp1=( $(echo $Temp | tr ',' '.'))
Hum1=( $(echo $Hum | tr ',' '.'))
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 temperature $Temp1"
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 humidity $Hum1"
else
exit;
fi
It seemed like the comma (.) was the problem, so i tried switching it to a (,) and back. But there are sometimes still values which are not between 0 and 100.
I hope someone can help me,
thank you!
Regards,
Klaus
awk uses doubles, and can easily be used to test the range of decimal numbers.
...
Temp=$(echo $WERTE | tr , . | awk '$7>=0&&$7<=100{print$7}')
Hum=$(echo $WERTE | tr , . | awk '$3>=0&&$3<=100{print$3}')
if [ -n "$Temp" -a -n "$Hum" ]; then
...
fi
-lt, -ge, etc. only work with integers. You can use <, >=, etc. inside [[, and it should work on floating point numbers too.
Some good practices in bash:
#!/bin/bash
set -e #autoexit if you run into an error
cd /opt/lol_dht22
#Reserve all-caps variables for exported variables and bash config variables
werte="$(./loldht 7 | grep "Humidity")" #always double-quote $ expressions
#foo=( bar ) creates arrays, you don't want that
#printf "%s\n" is more robust than echo if you're printing the contents of variables
Temp="$(printf '%s\n' "$werte" | awk '{ print $7 }' | tr . ,)"
Hum="$(printf '%s\n' "$werte" | awk '{ print $3 }' | tr . ,)"
#-lt etc. only work with integers
if [[ "$Temp" >= 0 && "$Temp" < 100 && "$Hum" >= 0 && "$Hum" < 100 ]]; then
Temp1="$(printf '%s\n' "$Temp" | tr , .)"
Hum1="$(printf '%s\n' "$Hum" | tr , .)"
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 temperature $Temp1"
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 humidity $Hum1"
else
exit 1 #return nonzero codes for errors
fi
Please mention what is content of WERTE. It will help in answering the question because awk FS by default is space. We need to properly understand your content of $7 and $3
Thank your Guys,
i've learned a lot.
and it seems to work:
#!/bin/bash
set -e
cd /opt/lol_dht22
WERTE=$(./loldht 7 | grep "Humidity")
Temp=$(echo $WERTE | awk '$7>=0&&$7<=100{print$7}')
Hum=$(echo $WERTE | awk '$3>=0&&$3<=100{print$3}')
if [ -n "$Temp" -a -n "$Hum" ]; then
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 temperature $Temp"
perl /opt/fhem/fhem.pl 7072 "setreading DHT22 humidity $Hum"
else
exit 1
fi
You've saved my Sunday =)
Best Regards,
Klaus

Bash- Converting a variable to human readable format (KB, MB, GB)

In my bash script, I run through a list of directories and read in the size of each directory to a variable using the du command. I also keep a running total of the total size of the directories. The only problem is that after I get the total size, it's in an unreadable format (ex. 64827120). How can I convert the variable containing this number into GBs, MBs, etc?
You want to use du -h which gives you a 'human readable' output ie KB, MB, GB, etc.
You can use numfmt to convert raw (decimal) numbers
to human-readable form. 
Use --to=iec to output binary-prefix numbers
(i.e., K=1024, M=220, etc.)
$ printf '%s %s\n' 1000000 foo 1048576 bar | numfmt --to=iec
977K foo
1.0M bar
and use --to=si to output metric-prefix numbers
(i.e., K=1000, M=106, etc.)
$ printf '%s %s\n' 1000000 foo 1048576 bar | numfmt --to=si
1.0M foo
1.1M bar
If you specifically want to get “MB”, “GB”, etc., use --suffix:
$ printf '%s %s\n' 1000000 foo 1048576 bar | numfmt --to=si --suffix=B
1.0MB foo
1.1MB bar
If your numbers are in a column other than the first
(as in Mik R’s answer), use --field:
$ printf '/home/%s %s\n' foo 1000000 bar 1048576 | numfmt --to=si --field=2
/home/foo 1.0M
/home/bar 1.1M
Or you can convert numbers on the command line (instead of using a pipe):
$ numfmt --to=si 1000000 1048576
1.0M
1.1M
Try using du -sh for getting summarise size in human readable, also you can find the command related help in manual.
Try below command, it will give you the size in Human readable format
du | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024 ; print total "MB" }'
du | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024/1024 ; print total "GB" }'
This is a combination of #Mahattam response and some others I combined which tallys the total amount in the standard format and then formats the output in human readable.
for path in $(awk -F: '{if ($3 >= 1000) print $6}' < /etc/passwd); do disk_usage=0; disk_usage=$(du -s ${path} | grep -oE '[[:digit:]]+'); echo "$path: $(echo $disk_usage | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024/1024 ; printf("%.2fGB\n", total) }')"; myAssociativeArray[${path}]=${disk_usage}; done ; total=$(IFS=+; echo "$((${myAssociativeArray[*]}))"); echo "Total disk usage: $(echo $total | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024/1024 ; printf("%.2fGB\n", total) }')"; unset total; unset disk_usage ;
How it works.
This could be anything you want to iterate through path list but in this example its just using the /etc/pass to loop over users paths source is here
for path in $(awk -F: '{if ($3 >= 1000) print $6}' < /etc/passwd)
It then calculates the usage per folder and extracts only digits from the output in the loop
disk_usage=0; disk_usage=$(du -s ${path} | grep -oE '[[:digit:]]+')
It outputs the nice formatting rounded to 2 decimal points
echo "$path: $(echo $disk_usage | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024/1024 ; printf("%.2fGB\n", total) }')";
Adds this to the bash associative array
myAssociativeArray[${path}]=${disk_usage}
then it sums the total value in the original amount from the array
total=$(IFS=+; echo "$((${myAssociativeArray[*]}))")
then we use the same fancy output formatting to show this nicely
echo "Total disk usage: $(echo $total | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024/1024 ; printf("%.2fGB\n", total) }')";
I used a variation of this for calculating cPanel Resellers accounts disk usage in the below monster oneliner.
Reseller="CPUsernameInputField"; declare -A myAssociativeArray ; echo "==========================================" | tee -a ${Reseller}_disk_breakdown.txt ; echo "Reseller ${Reseller}'s Disk usage by account"| tee -a ${Reseller}_disk_breakdown.txt; for acct in $(sudo grep ${Reseller} /etc/trueuserowners | cut -d: -f1); do disk_usage=0; disk_usage=$(du -s /home/${acct} | grep -oE '[[:digit:]]+'); echo "$acct: $(echo $disk_usage | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024/1024 ; printf("%.2fGB\n", total) }')" | tee -a ${Reseller}_disk_breakdown.txt ; myAssociativeArray[${acct}]=${disk_usage}; done ; total=$(IFS=+; echo "$((${myAssociativeArray[*]}))"); echo "Total disk usage: $(echo $total | tail -1 | awk {'print $1'} | awk '{ total = $1 / 1024/1024 ; printf("%.2fGB\n", total) }')" | tee -a ${Reseller}_disk_breakdown.txt; unset total; unset disk_usage;echo "==========================================" | tee -a ${Reseller}_disk_breakdown.txt ; echo "Sorted by top users" | tee -a ${Reseller}_disk_breakdown.txt; for key in "${!myAssociativeArray[#]}"; do printf '%s:%s\n' "$key" "${myAssociativeArray[$key]}"; done | sort -t : -k 2rn | tee -a ${Reseller}_disk_breakdown.txt;echo "==========================================" | tee -a ${Reseller}_disk_breakdown.txt ;for key in "${!myAssociativeArray[#]}"; do USER_HOME=$(eval echo ~${key}); echo "Disk breakdown for $key" | tee -a ${Reseller}_disk_breakdown.txt ; sudo du -h ${USER_HOME} --exclude=/app --exclude=/home/virtfs| grep ^[0-9.]*[G,M] | sort -rh|head -n20 | tee -a ${Reseller}_disk_breakdown.txt;echo "=======================================" | tee -a ${Reseller}_disk_breakdown.txt; done

Make the times from a log file relative to the starttime

I have a logfile with this format:
10:33:56 some event occurs
10:33:57 another event occurs
10:33:59 another one occurs
I want to make the times relative to the start time:
00:00:00 some event occurs
00:00:01 another event occurs
00:00:03 another one occurs
using a bash script. That would allow me to compare better different execution delays.
One can make this script rebase_time.sh:
adddate() {
while IFS= read -r line; do
log_file_hours=`echo $line | awk 'BEGIN{FS="[ [/:]+"}; {print $1}'`
log_file_minutes=`echo $line | awk 'BEGIN{FS="[ [/:]+"}; {print $2}'`
log_file_seconds=`echo $line | awk 'BEGIN{FS="[ [/:]+"}; {print $3}'`
log_date="$log_file_hours:$log_file_minutes:$log_file_seconds"
if [[ -z "$first_date" ]]; then
first_date=$log_date
fi
StartDate=$(date -u -d "$first_date" +"%s")
FinalDate=$(date -u -d "$log_date" +"%s")
diff=$(date -u -d "0 $FinalDate sec - $StartDate sec" +"%H:%M:%S")
echo $diff ${line#$log_date}
done
}
cat "$1" | adddate
and call it this way:
./rebase_time events.log

I don't want array to do word splitting

Below is my code. But bash is doing word splitting therefore I have failure. How to make my script so that there is no word splitting.
namaSensor=$(sensors | egrep "°C" | awk '{print $0}' | awk -F ':' '{print $1}')
for sensor in $namaSensor
do
if [ $(sensors | grep -c "$sensor") -ne 0 ]
then
currentTemperature=$(sensors | egrep "$sensor" | awk '{print $0}' | awk -F '+' '{print $2}' | cut -c 1-4 | awk -F '.' '{print $1}')
maxTemperature=$(sensors | egrep "$sensor" | awk '{print $0}' | awk -F '+' '{print $3}' | cut -c 1-4 | awk -F '.' '{print $1}')
if [ $currentTemperature -lt $maxTemperature ]
then
printf "current temperature is %d°C and the maximum allowed temperature is %d°C\n" "$currentTemperature" "$maxTemperature"
printf "temperature is within the maximum allowed temperature\n"
echo "$sensor"
else
printf "current temperature is %d°C and the maximum allowed temperature is %d°C\n" "$currentTemperature" "$maxTemperature"
printf "temperature is more than the maximum allowed temperature\n"
#exit 255
fi
fi
done.
This is the output of sensors for my unit.
acpitz-virtual-0
Adapter: Virtual device
temp1: +40.0°C (crit = +111.0°C)
temp2: +40.0°C (crit = +111.0°C)
coretemp-isa-0000
Adapter: ISA adapter
Physical id 0: +34.0°C (high = +87.0°C, crit = +105.0°C)
Core 0: +31.0°C (high = +87.0°C, crit = +105.0°C)
Core 1: +22.0°C (high = +87.0°C, crit = +105.0°C)
Please help
As I understand you have your array splitted as following:
Physical
id
0
and so on. To change this behaviour you need to change your Internal Field Separator (IFS) to a newline. So your code should look like this:
IFS=$'\n'
nameSensor=$(sensors | egrep "°C" | awk '{print $0}' | awk -F ':' '{print $1}')
for sensor in $nameSensor
do
if [ $(sensors | grep -c "$sensor") -ne 0 ]; then
currentTemperature=$(sensors | egrep "$sensor" | awk '{print $0}' | awk -F '+' '{print $2}' | cut -c 1-4 | awk -F '.' '{print $1}')
maxTemperature=$(sensors | egrep "$sensor" | awk '{print $0}' | awk -F '+' '{print $3}' | cut -c 1-4 | awk -F '.' '{print $1}')
if [ $currentTemperature -lt $maxTemperature ]; then
printf "current temperature is %d°C and the maximum allowed temperature is %d°C\n" "$currentTemperature" "$maxTemperature"
printf "temperature is within the maximum allowed temperature\n"
echo "$sensor"
else
printf "current temperature is %d°C and the maximum allowed temperature is %d°C\n" "$currentTemperature" "$maxTemperature"
printf "temperature is more than the maximum allowed temperature\n"
#exit 255
fi
fi
done

Resources