Grepping correct value in 'else' - bash

The following is a snippet from a daily report script that checks on the SMART status of disks.
I'd like the full value of $STATUS to be printed in the 'else' clause, however, but it's truncating it now to only the first word ($3).
How can I overcome this?
echo "SMART STATUS" >> $LOGS
echo "--------------------------" >> $LOGS
DISKS=( 0 1 2 3 4 5 6 7 )
for i in "${DISKS[#]}" ;
do
STATUS=`diskutil info /dev/disk$i | grep SMART | awk '{ print $3 }'`
if [ "$STATUS" = "Verified" ]; then
echo "SMART STATUS OK FOR DISK $i" >> $LOGS
else
echo "** SMART STATUS $STATUS FOR DISK $i **" >> $LOGS
fi
done
echo " " >> $LOGS
echo " " >> $LOGS
For example:
$ diskutil info /dev/disk8 | grep SMART
SMART Status: Not Supported
$ diskutil info /dev/disk2 | grep SMART
SMART Status: Verified
Ideally the script would echo "Not Supported" for cases like disk8. I think the value for "SMART Status:" may vary between one word and several. I don't see all the possibilities on the man page, so I don't have a definitive answer for this.

You can save the grep, and put all things in awk:
try this:
STATUS=$(diskutil info /dev/disk$i|awk -F': *' '/SMART/{print $2}')

Is this fits your needs ?
disks="0 1 2 3 4 5 6 7"
for i in $disks; do
status=$(diskutil info /dev/disk$i | awk '/SMART/{$1=$2="";print}')
if [[ $status == *Verified* ]]; then
echo "SMART status OK for disk $i"
else
echo "** SMART status $status for disk $i **"
fi
done

cut solution
If you can rely on the number of spaces printed by diskutil, you can just begin cutting at the first char of the status:
STATUS=`diskutil info /dev/disk$i | grep SMART | cut -c 30-`
awk solution
The word Supported is caught as $4, so we can use an if statement to check, whether the word is filled:
STATUS=`diskutil info /dev/disk$i | grep SMART | awk '{ if($4 != "") print $3,$4; else print $3; }'`

If you have GNU grep, you can do:
STATUS=$(diskutil info /dev/disk$i | grep -oP '(?<=SMART Status:).*')
The contents of the variable will contain leading whitespace. However, your if condition can accomodate that:
shopt -s extglob
if [[ ${STATUS##*([[:space:]])} == Verified ]]; then

Related

Integer Expression Expected

Code sample :
declare -i a=1
echo "The number of NMON instances running in Performance VM"
ps -ef | grep nmon | awk '{ print $2 }' | wc -l
echo "---------------------------------------------"
num2= ps -ef | grep nmon | awk '{ print $2 }' | wc -l
#num1=${num2%%.*}
#num2 = $(ps -ef | grep nmon | awk '{ print $2 }' | wc -l)
echo "---------------------------------------------"
echo "${num2}"
while [ "$a" -lt "$num2" ]
do
kill -USR2 $(ps -ef | grep nmon | awk '{ print $2 }' | head -1)
a=`expr $a + 1`
done
In the Output i am getting the following error
[: : integer expression expected
in the debug it shows
++ '[' 1 -lt '' ']'
that num2 is empty but when i echo the num2 value i am getting the value correctly.
Output:
The number of NMON instances running in Performance VM
1
1
thanks in advance
The 1 you see in the output is not from echo "${num2}". Like the diagnostics already tell you, this variable is empty.
The general syntax of shell scripts is
[ variable=value ...] command parameters ...
which will assign value to variable for the duration of command, then restore its original value. So the pipeline you are running temporarily sets num2 to the empty string (which apparently it already contained anyway), then runs the pipeline without storing the output anywhere (such as, I imagine you expected, in num2).
Here is a fixed version of your script, with the additional change that the Awk scripts handle stuff you used grep and head and wc for. Because the functionality of these commands is easily replaced within Awk, using external utilities is doubtful (especially so for grep which really is useless when you just run it as a preprocessor for a simple Awk script).
countnmon () {
ps -ef | awk '/[n]mon/ { ++n } END { print n }'
}
declare -i a=1
echo "The number of NMON instances running in Performance VM"
countnmon
echo "---------------------------------------------"
num2=$(countnmon)
#num1=${num2%%.*}
#num2 = $(countnmon)
echo "---------------------------------------------"
echo "${num2}"
while [ "$a" -lt "$num2" ]
do
kill -USR2 $(ps -ef | awk '/[n]mon/ { print $2; exit }')
a=`expr $a + 1`
done
The repeated code could be refactored even further to avoid all code duplication but that will somewhat hamper the readability of this simple script so I have not done that.
Whitespaces matter in bash.
The syntax for command execution is:
command arg1 arg2 ...
So,
var = value # command: var, arg1: =, arg2: value
There's are two exceptions to this rule
exporting variables to an executed command (the variables vanish after the command finishes):
var1=value1 var2=value2 .. command arg1 arg2 ...
assigning a variable (you want this one):
var=value

Bash - Memory usage

I have a problem that I can't solve, so I've come to you.
I need to write a program that will read all processes and a program must sort them by users and for each user it must display how much of a memory is used.
For example:
user1: 120MB
user2: 300MB
user3: 50MB
total: 470MB
I was thinking to do this with ps aux command and then get out pid and user with awk command. Then with pmap I just need to get total memory usage of a process.
it's just a little update, users are automatically selected
#!/bin/bash
function mem_per_user {
# take username as only parameter
local user=$1
# get all pid's of a specific user
# you may elaborate the if statement in awk obey your own rules
pids=`ps aux | awk -v username=$user '{if ($1 == username) {print $2}}'`
local totalmem=0
for pid in $pids
do
mem=`pmap $pid | tail -1 | \
awk '{pos = match($2, /([0-9]*)K/, mem); if (pos > 0) print mem[1]}'`
# when variable properly set
if [ ! -z $mem ]
then
totalmem=$(( totalmem + $mem))
fi
done
echo $totalmem
}
total_mem=0
for username in `ps aux | awk '{ print $1 }' | tail -n +2 | sort | uniq`
do
per_user_memory=0
per_user_memory=$(mem_per_user $username)
if [ "$per_user_memory" -gt 0 ]
then
total_mem=$(( $total_mem + $per_user_memory))
echo "$username: $per_user_memory KB"
fi
done
echo "Total: $total_mem KB"
Try this script, which may solve your problem:
#!/bin/bash
function mem_per_user {
# take username as only parameter
local user=$1
# get all pid's of a specific user
# you may elaborate the if statement in awk obey your own rules
pids=`ps aux | awk -v username=$user '{if ($1 == username) {print $2}}'`
local totalmem=0
for pid in $pids
do
mem=`pmap $pid | tail -1 | \
awk '{pos = match($2, /([0-9]*)K/, mem); if (pos > 0) print mem[1]}'`
# when variable properly set
if [ ! -z $mem ]
then
totalmem=$(( totalmem + $mem))
fi
done
echo $totalmem
}
total_mem=0
for i in `seq 1 $#`
do
per_user_memory=0
eval username=\$$i
per_user_memory=$(mem_per_user $username)
total_mem=$(( $total_mem + $per_user_memory))
echo "$username: $per_user_memory KB"
done
echo "Total: $total_mem KB"
Best regards!
You can access the shell commands in python using the subprocess module. It allows you to spawn subprocesses and connect to the out/in/error. You can execute the ps -aux command and parse the output in python.
check out the docs here
Here is my version. I think that Tim's version is not working correctly, the values in KB are too large. I think the RSS column from pmap -x command should be used to give more accurate value. But do note that you can't always get correct values because processes can share memmory. Read this A way to determine a process's "real" memory usage, i.e. private dirty RSS?
#!/bin/bash
if [ "$(id -u)" != "0" ]; then
echo "WARNING: you have to run as root if you want to see all users"
fi
echo "Printing only users that current memmory usage > 0 Kilobytes "
all=0
for username in `ps aux | awk '{ print $1 }' | tail -n +2 | sort | uniq`
do
pids=`ps aux | grep $username | awk -F" " '{print $2}'`
total_memory=0
for pid in $pids
do
process_mem=`pmap -x $pid | tail -1 | awk -F" " '{print $4}'`
if [ ! -z $process_mem ]
then #don't try to add if string has no length
total_memory=$((total_memory+$process_mem))
fi
done
#print only those that use any memmory
if [ $total_memory -gt 0 ]
then
total_memory=$((total_memory/(1024)))
echo "$username : $total_memory MB"
all=$((all+$total_memory))
fi
done
echo "----------------------------------------"
echo "Total: $all MB"
echo "WARNING: Use at your own risk"

bash script and greping with command line

new to bash scripting so just wondering if i am doing this code right at all. im trying to search /etc/passwd and then grep and print users.
usage ()
{
echo "usage: ./file.sk user"
}
# test if we have two arguments on the command line
if [ $# != 1 ]
then
usage
exit
fi
if [[ $# < 0 ]];then
usage
exit
fi
# Search for user
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
#check if there. if name is founf: print msg and line entry
not sure as how to this or if im doing this right...
am i doing this right?
grep $1 /etc/passwd | while IFS=: read -r username passwd uid gid info home shell
do
echo $username: $info
done
This might work for you:
fullname=$(awk -F: '/'$1'/{print $5}' /etc/passwd)
firstname=${fullname/ *}
You're on the right track.
But I think the 2nd if [[ $# < 0 ]] .... fi block doesn't get you much. Your first test case gets the situation right, 'This script requires 1 argument or quits'.
Also, I don't see what you need firstname for, so a basic test is
case "${fullname:--1}" in
-[1] ) printf "No userID found for input=$1\n" ; exit 1 ;;
* )
# assume it is OK
# do what every you want after this case block
;;
esac
You can of course, duplicate this using "${firstname}" if you really need the check.
OR as an equivalent if ... fi is
if [[ "${fullname}" == "" ]] ; then
printf "No userID found for input=$1\n" ; exit 1
fi
note to be more efficient, you can parse ${fullname} to get firstname without all the calls to grep etc, i.e.
firstname=${fullname%% *}
Let me know if you need for me to explain :--1} and %% *} variable modifiers.
I hope this helps.
Instead of this:
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
Try this:
fullname=$(cut -f5 -d: /etc/passwd | grep "$1")
if [[ $? -ne 0 ]]; then
# not found, do something
fi
firstname=${fullname%% *} # remove the space and everything after
Note that I changed my answer to cut before grep so that it doesn't get false positives if some other field matches the full name you are searching for.
You can simply by reading your input to an array and then printing out your desired fields, something like this -
grep $1 /etc/passwd | while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
Test:
jaypal:~/Temp] echo "root:*:0:0:System Administrator:/var/root:/bin/sh" |
while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
root:System Administrator

How to egrep variable-Unix shell script

I’m trying to validate input by using egrep and regex.Here is the line from script (c-shell):
echo $1 | egrep '^[0-9]+$'
if ($status == 0) then
set numvar = $1
else
echo "Invalid input"
exit 1
endif
If I pipe echo to egrep it works, but it also prints the variable on the screen, and this is something I don't need.
To simply suppress output you can redirect it to the null device.
echo $1 | egrep '^[0-9]+$' >/dev/null
if ($status == 0) then
set numvar = $1
else
echo "Invalid input"
exit 1
endif
You might also want to consider using the -c option to get the count of matches instead of using using the status.
Also, unless you are using csh, the status is stored in $? not in $status
grep has a -q option that suppresses output
So:
egrep -q '^[0-9]+$'
you can use awk
$ echo "1234" | awk '{print $1+0==$1?"ok":"not ok"}'
ok
$ echo "123d4" | awk '{print $1+0==$1?"ok":"not ok"}'
not ok

How to verify information using standard linux/unix filters?

I have the following data in a Tab delimited file:
_ DATA _
Col1 Col2 Col3 Col4 Col5
blah1 blah2 blah3 4 someotherText
blahA blahZ blahJ 2 someotherText1
blahB blahT blahT 7 someotherText2
blahC blahQ blahL 10 someotherText3
I want to make sure that the data in 4th column of this file is always an integer. I know how to do this in perl
Read each line, Store value of 4th column in a variable
check if that variable is an integer
if above is true, continue the loop
else break out of the loop with message saying file data not correct
But how would I do this in a shell script using standard linux/unix filter? My guess would be to use grep, but I am not sure how?
cut -f4 data | LANG=C grep -q '[^0-9]' && echo invalid
LANG=C for speed
-q to quit at first error in possible long file
If you need to strip the first line then use tail -n+2 or you could get hacky and use:
cut -f4 data | LANG=C sed -n '1b;/[^0-9]/{s/.*/invalid/p;q}'
awk is the tool most naturally suited for parsing by columns:
awk '{if ($4 !~ /^[0-9]+$/) { print "Error! Column 4 is not an integer:"; print $0; exit 1}}' data.txt
As you get more complex with your error detection, you'll probably want to put the awk script in a file and invoke it with awk -f verify.awk data.txt.
Edit: in the form you'd put into verify.awk:
{
if ($4 !~/^[0-9]+$/) {
print "Error! Column 4 is not an integer:"
print $0
exit 1
}
}
Note that I've made awk exit with a non-zero code, so that you can easily check it in your calling script with something like this in bash:
if awk -f verify.awk data.txt; then
# action for success
else
# action for failure
fi
You could use grep, but it doesn't inherently recognize columns. You'd be stuck writing patterns to match the columns.
awk is what you need.
I can't upvote yet, but I would upvote Jefromi's answer if I could.
Sometimes you need it BASH only, because tr, cut & awk behave differently on Linux/Solaris/Aix/BSD/etc:
while read a b c d e ; do [[ "$d" =~ ^[0-9] ]] || echo "$a: $d not a numer" ; done < data
Edited....
#!/bin/bash
isdigit ()
{
[ $# -eq 1 ] || return 0
case $1 in
*[!0-9]*|"") return 0;;
*) return 1;;
esac
}
while read line
do
col=($line)
digit=${col[3]}
if isdigit "$digit"
then
echo "err, no digit $digit"
else
echo "hey, we got a digit $digit"
fi
done
Use this in a script foo.sh and run it like ./foo.sh < data.txt
See tldp.org for more info
Pure Bash:
linenum=1; while read line; do field=($line); if ((linenum>1)); then [[ ! ${field[3]} =~ ^[[:digit:]]+$ ]] && echo "FAIL: line number: ${linenum}, value: '${field[3]}' is not an integer"; fi; ((linenum++)); done < data.txt
To stop at the first error, add a break:
linenum=1; while read line; do field=($line); if ((linenum>1)); then [[ ! ${field[3]} =~ ^[[:digit:]]+$ ]] && echo "FAIL: line number: ${linenum}, value: '${field[3]}' is not an integer" && break; fi; ((linenum++)); done < data.txt
cut -f 4 filename
will return the fourth field of each line to stdout.
Hopefully that's a good start, because it's been a long time since I had to do any major shell scripting.
Mind, this may well not be the most efficient compared to iterating through the file with something like perl.
tail +2 x.x | sort -n -k 4 | head -1 | cut -f 4 | egrep "^[0-9]+$"
if [ "$?" == "0" ]
then
echo "file is ok";
fi
tail +2 gives you all but the first line (since your sample has a header)
sort -n -k 4 sorts the file numerically on the 4th column, letters will rise to the top.
head -1 gives you the first line of the file
cut -f 4 gives you the 4th column, of the first line
egrep "^[0-9]+$" checks if the value is a number (integers in this case).
If egrep finds nothing, $? is 1, otherwise it's 0.
There's also:
if [ `tail +2 x.x | wc -l` == `tail +2 x.x | cut -f 4 | egrep "^[0-9]+$" | wc -l` ] then
echo "file is ok";
fi
This will be faster, requiring two simple scans through the file, but it's not a single pipeline.
#OP, use awk
awk '$4+0<=0{print "not ok";exit}' file

Resources