Nested pipe output in function - bash

I am trying to make a script that will output system information using several functions that call each other. Can someone tell me what's wrong with how the piped commands' I/O is handled?
#!/bin/bash
function lyellow() {
lyellow="$1"
echo -e -n "\033[0;33m$lyellow"
echo -e -n '\033[0m \n'
}
function red() {
RED="$1"
echo -e -n "\033[0;31m$RED"
echo -e -n '\033[0m \n'
}
function lcyan() {
LCYAN="$1"
echo -e -n "\033[0;36m$LCYAN"
echo -e -n '\033[0m \n'
}
function lgreen() {
LGREEN="$1"
echo -e -n "\033[1;32m$LGREEN"
echo -e -n '\033[0m \n'
}
field ()
{
HEADER="$1"
SUB1="$2"
COM1="$3"
SUB2="$4"
COM2="$5"
echo -e "$(red "$(echo -e "### $HEADER ###")")"
echo -e "$(lyellow "$(echo -e "$SUB1")")\n$(lcyan "$(echo -e "$($COM1)")")"
echo -e "$(lyellow "$(echo -e "$SUB2")")\n$(lcyan "$(echo -e "$($COM2)")")"
}
#set -x pipefail
SEP=$(seq -s= 40|tr -d '[:digit:]')
echo $SEP
echo -e "$(lgreen "$(hostname -f) :: $(hostname -i)")"
echo $SEP
#OS
field "Operating System" \
"Kernel:" "/bin/uname -srp" \
"Release:" "cat /etc/redhat-release"
echo $SEP
#DISK
field "Storage Devices" \
"Mounted Devices:" "mount|column -t" \
"Disk Free:" "df -kh|column -t"
echo $SEP
#Example
lcyan "$(echo -e "$(df -kh | column -t)")"
exit 0
The output from the "#OS" "field" call works. But the "#DISK" call doesn't like the pipes to "column -t". Under "#Example" the color function calls a literal piped "column -t" fine. Here is what the output looks like:
[root#CLFT1Q ~]# sh sysinfo.sh
=======================================
CLFT1Q.local :: 10.9.19.70
=======================================
### Operating System ###
Kernel:
Linux 2.6.18-348.3.1.el5 i686
Release:
Red Hat Enterprise Linux Server release 5.9 (Tikanga)
=======================================
### Storage Devices ###
sysinfo.sh: line 36: /bin/mount|column: No such file or directory
Mounted Devices:
df: invalid option -- |
Try `df --help' for more information.
Disk Free:
=======================================
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vgsystem-lv_root
3.9G 3.3G 421M 89% /
/dev/mapper/vgsystem-lv_var
4.9G 2.3G 2.4G 49% /var
/dev/mapper/vgsystem-ora
3.0G 1008M 1.9G 36% /ora
/dev/sda1 99M 25M 69M 27% /boot
tmpfs 1014M 0 1014M 0% /dev/shm
clnsa05:/vol/ftpnfsqa1/ftp
29G 25G 4.2G 86% /ftp

Change "$($COM1)" to "$(eval "$COM1")", and similarly for $COM2. Variable expansions are only scanned for word splitting and wildcard expansion, not for command metacharacters like pipelines. You need to use eval to process it recursively as a command line.

Related

Grep not acting as expected, expression resolves correctly in bash conditional check for input 10-100 and 0-6, but not 7-9

I have this conditional running in a script:
if [[ $(df -h /data --output=pcent | tail -1 | grep -o -E [0-9]+) > 60 ]]; then echo "Not enough space on box"; else echo "Enough space on box"; fi
The check is supposed to bring up the disk free output, validate the mount /data has more than >40% free, and print out the result of the check. This works for all but the cases I list below. I believe the problem lies in the usage of grep (GNU 2.20) or the comparison to 60 but I can't understand how it fails considering it works for (some) single digit entries, and all double digits.
Running the following in a CentOS 7 box:
if [[ $(df -h /data --output=pcent | tail -1 | grep -o -E [0-9]+) > 60 ]]; then echo "Not enough space on box"; else echo "Enough space on box"; fi
(The output of df -h /data --output=pcent is a number and %, i.e. "X%")
When running tests such as
if [[ $(echo 100% | tail -1 | grep -o -E [0-9]+) > 60 ]]; then echo "Not enough space on box"; else echo "Enough space on box"; fi
if [[ $(echo 100% | tail -1 | grep -o -E [0-9]*) > 60 ]]; then echo "Not enough space on box"; else echo "Enough space on box"; fi
the expected output is "Not enough space on box", however it is "Enough space on box", and running:
if [[ $(echo 7% | tail -1 | grep -o -E [0-9]+) > 60 ]]; then echo "Not enough space on box"; else echo "Enough space on box"; fi
the expected output is "Enough space on box", however the output is "Not enough space on box".
It's doing a string comparison. 7 is greater than 6. Use mathematic evaluation with ((...)) instead of [[...]].
$: if [[ $(echo 7% | tail -1 | grep -o -E [0-9]+) > 60 ]]; then echo "Not enough space on box"; else echo "Enough space on box"; fi
Not enough space on box
$: if (( $(echo 7% | tail -1 | grep -o -E [0-9]+) > 60 )); then echo "Not enough space on box"; else echo "Enough space on box"; fi
Enough space on box
You could also do something like this -
$: not=( '' "Not " )
$: echo "${not[$(echo 7% | tail -1 | grep -o -E [0-9]+) > 60]}enough space on box"
enough space on box
$: echo "${not[$(echo 70% | tail -1 | grep -o -E [0-9]+) > 60]}enough space on box"
Not enough space on box

Named pipe swallowing first field of Linux command output

I'm trying to parse the output of the Linux df tool for use in a machine status report. I'm using almost identical code to parse the output of the ps tool and it works fine (all fields are available in read loop) but in the code below the first field output from awk (percentUsed) is missing when I read from the named pipe.
#!/bin/sh
mkfifo dfPipe0
IFS=" "
df -h | awk '{ print $6" "$3" "$7" "$1 }' > dfPipe0 &
while read -r percentUsed size mountedOn fileSystem
do
printf "%s\n" "${percentUsed} | ${size} | ${mountedOn} | ${fileSystem}"
done < dfPipe0
rm dfPipe0
Sample df + awk output
$ df -h | awk '{ print $6" "$3" "$7" "$1 }'
Use% Size Mounted Filesystem
0% 1.9G /dev devtmpfs
- 0 /sys/kernel/security securityfs
4% 1.9G /dev/shm tmpfs
$
I edited my code to use the standard pipe suggestion by #Barmar and it resolves my problem. The code below works fine.
df -h | \
while read -r fileSystem size used avail percentUsed mountedOn
do printf "%s\n" "$fileSystem | $size | $used | $avail | $percentUsed | $mountedOn"
done

Bash script says: "}" unexpected (expected "done") in function call in .xinitrc

I'm writing the .xinitrc for my system, but the code (below) crashes on line 17 (the ending brace of the `freespace' function) with the error
"}" unexpected (expected "done")
Is there actually a problem with the function declaration, or did I mess up somewhere else in the script?
while true; do
# WiFi connectivity status
if [ -f /sbin/iwgetid ]; then
result="$(/sbin/iwgetid -r )";
if [ -z "$result" ]; then
result="Not connected";
fi
else
result="Not installed";
fi
wifi_status="WiFi: $result";
# Free space on / and /home
function freespace {
local space="$(df -lh --output=avail $1 | tail -n 1 | sed -e 's/^[ \t]*//') free on $1";
echo "$space";
}
root_free=$(freespace /);
home_free=$(freespace /home);
# Miscellaneous
user_str="$(whoami)#$(hostname)";
date_str="$(date +"%a %b %d %R")";
# Battery status and CPU temperature
if [ -f /usr/bin/acpi ]; then
battery_status="Battery: $(acpi -b '{split($0, a, ": "); print a[2]'})";
cpu_temp="CPU temperature: $(acpi -t -f | sed 's/Thermal 0: ok, //g')";
acpi_status="$battery_status | $cpu_temp";
else
acpi_status="ACPI not available";
fi
# System performance
uptime_str=$(uptime -p);
num_cpus="CPUs: $(grep "processor" /proc/cpuinfo | wc -l)";
num_procs="$(ps -e | wc -l) active processes";
load_avgs="Load averages: $(cat /proc/loadavg | awk '{split($0, a, " "); print a[1], a[2], a[3]}')";
# Update top and bottom status bar
top_bar="$wifi_status | $uptime_str | $user_str | $date_str";
bottom_bar="$acpi_status | $num_cpus | $num_procs | $load_avgs | $root_free | $home_free";
xsetroot -name "$top_bar;$bottom_bar";
sleep 30;
done &
xbindkeys
( ( sleep 5 && /usr/bin/xscreensaver -no-splash -display :0.0 ) & )
( ( create-random-gradient && feh --bg-center ~/.wallpaper.png ) & )
( redshifter & )
wmname LG3D
exec /home/ma/build/dwm/dwm
If I slim down the code to just that section, i.e.
while true; do
# Free space on / and /home
function freespace {
local space="$(df -lh --output=avail $1 | tail -n 1 | sed -e 's/^[ \t]*//') free on $1";
echo "$space";
}
root_free=$(freespace /);
home_free=$(freespace /home);
echo "$root_free | $home_free";
sleep 2
done
and run it from the terminal directly (instead of calling startx after logging in), it works just fine.
This error will happen if the shell running this code is not actually bash. It's noteworthy that your script doesn't include a shebang -- without #!/usr/bin/env bash or another shebang, the interpreter used is unspecified. (Explicitly specifying sh yourscript will override the shebang and force sh, just as bash yourscript will ignore the shebang and use bash, but that's neither here nor there).
function is not a keyword in standard POSIX sh -- it's a ksh extension which bash adopted. Consequently, to a POSIX shell, the { in function freespace { is not syntax -- it's an argument to be passed to a command named function.
By contrast, the } is parsed as syntax, but it doesn't match with any syntactic opening brace; hence, your error.
In standards-compliant syntax, the declaration would be:
freespace() {
df -lh --output=avail "$1" | awk -v fs="$1" 'NR == 2 { print $1 " free on " fs }'
}
Note the elimination of the unnecessary command substitution. echo "$(...)" is self-defeating: $() goes to significant overhead to capture stdout of its command into a string, and then echo emits that back to stdout again. Why do any of that when you could just run the command directly?

Bash - output of command seems to be an integer but "[" complains

I am checking to see if a process on a remote server has been killed. The code I'm using is:
if [ `ssh -t -t -i id_dsa headless#remoteserver.com "ps -auxwww |grep pipeline| wc -l" | sed -e 's/^[ \t]*//'` -lt 3 ]
then
echo "PIPELINE STOPPED SUCCESSFULLY"
exit 0
else
echo "PIPELINE WAS NOT STOPPED SUCCESSFULLY"
exit 1
fi
However when I execute this I get:
: integer expression expected
PIPELINE WAS NOT STOPPED SUCCESSFULLY
1
The actual value returned is "1" with no whitespace. I checked that by:
vim <(ssh -t -t -i id_dsa headless#remoteserver.com "ps -auxwww |grep pipeline| wc -l" | sed -e 's/^[ \t]*//')
and then ":set list" which showed only the integer and a line feed as the returned value.
I'm at a loss here as to why this is not working.
If the output of the ssh command is truly just an integer preceded by optional tabs, then you shouldn't need the sed command; the shell will strip the leading and/or trailing whitespace as unnecessary before using it as an operand for the -lt operator.
if [ $(ssh -tti id_dsa headless#remoteserver.com "ps -auxwww | grep -c pipeline") -lt 3 ]; then
It is possible that result of the ssh is not the same when you run it manually as when it runs in the shell. You might try saving it in a variable so you can output it before testing it in your script:
result=$( ssh -tti id_dsa headless#remoteserver.com "ps -auxwww | grep -c pipeline" )
if [ $result -lt 3 ];
The return value you get is not entirely a digit. Maybe some shell-metacharacter/linefeed/whatever gets into your way here:
#!/bin/bash
var=$(ssh -t -t -i id_dsa headless#remoteserver.com "ps auxwww |grep -c pipeline")
echo $var
# just to prove my point here
# Remove all digits, and look wether there is a rest -> then its not integer
test -z "$var" -o -n "`echo $var | tr -d '[0-9]'`" && echo not-integer
# get out all the digits to use them for the arithmetic comparison
var2=$(grep -o "[0-9]" <<<"$var")
echo $var2
if [[ $var2 -lt 3 ]]
then
echo "PIPELINE STOPPED SUCCESSFULLY"
exit 0
else
echo "PIPELINE WAS NOT STOPPED SUCCESSFULLY"
exit 1
fi
As user mbratch noticed I was getting a "\r" in the returned value in addition to the expected "\n". So I changed my sed script so that it stripped out the "\r" instead of the whitespace (which chepner pointed out was unnecessary).
sed -e 's/\r*$//'

bash script that monitor a disk partition's usage

df shows
-bash-4.1# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda3 1918217320 1783986384 36791092 98% /
tmpfs 16417312 0 16417312 0% /dev/shm
/dev/sda1 482214 148531 308784 33% /boot
/dev/sdb1 1922858352 1373513440 451669312 76% /disk2
I need to bash script a function that returns 1 if an paritions become 100% full.
how can this be done? what commands can I use to parse out the output of df?
This should do it:
disks_space() {
! df -P | awk '{print $5}' | grep -Fqx '100%'
}
In other words, check if any of the lines in the fifth column of the POSIX df output contains the exact string "100%".
Probelm with percentage is if its a terrabyte disk 95% of that may still be lots of free gig - refer to the bottom script for actual disk space - the format 100 at the end of the example shows alert when it is below 100MB left on a partition
diskspace.sh
#!/bin/sh
# set -x
# Shell script to monitor or watch the disk space
# It will send an email to $ADMIN, if the (free available) percentage of space is >= 90%.
# -------------------------------------------------------------------------
# Set admin email so that you can get email.
ADMIN="root"
# set alert level 90% is default
ALERT=90
# Exclude list of unwanted monitoring, if several partions then use "|" to separate the partitions.
# An example: EXCLUDE_LIST="/dev/hdd1|/dev/hdc5"
EXCLUDE_LIST="/auto/ripper"
#
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#
function main_prog() {
while read output;
do
echo $output
usep=$(echo $output | awk '{ print $1}' | cut -d'%' -f1)
partition=$(echo $output | awk '{print $2}')
if [ $usep -ge $ALERT ] ; then
if [ "$partition" == "/var" ]; then
# echo "Running out of space \"$partition ($usep%)\" on server $(hostname), $(date)"
echo "Running out of space \"$partition ($usep%)\" on server $(hostname), $(date)" | mail -s "Alert: Almost out of disk space $usep%" $ADMIN
# Extra bits you may wish to do -
#for FILE in `find $partition -size +1G -print`
#do
# echo $FILE
# DATE=`date +%Y-%m-%d_%H%M`
# filename=`echo ${FILE##*/}`
# mkdir /mnt/san/$hostname
# echo cp $FILE /mnt/san/$(hostname)/$filename-$DATE
# #echo > $FILE
#done
fi
fi
done
}
if [ "$EXCLUDE_LIST" != "" ] ; then
df -hP | grep -vE "^[^/]|tmpfs|cdrom|${EXCLUDE_LIST}" | awk '{print $5 " " $6}' | main_prog
else
df -hP | grep -vE "^[^/]|tmpfs|cdrom"| awk '{print $5 " " $6}' | main_prog
fi
Or you could use this style of check I put in place for nagios (using snmp to connect to a remote host)
snmp_remote_disk_auto
#!/bin/bash
# This script takes:
# <host> <community> <megs>
snmpwalk="/usr/bin/snmpwalk"
snmpget="/usr/bin/snmpget"
function usage() {
echo "$0 localhost public 100"
echo "where localhost is server"
echo "public is snmp pass"
echo "100 is when it reaches below a 100Mb"
echo "-----------------------------------"
echo "define threshold below limit specific for partitions i.e. boot can be 50mb where as /var I guess we want to catch it at around 1 gig so"
echo "$0 localhost public 1024"
}
server=$1;
pass=$2
limit=$3;
errors_found="";
partitions_found="";
lower_limit=10;
graphtext="|"
if [ $# -lt 3 ]; then
usage;
exit 1;
fi
# takes <size> <used> <allocation>
calc_free() {
echo "$1 $2 - $3 * 1024 / 1024 / p" | dc
}
for partitions in $($snmpwalk -v2c -c $pass -Oq $server hrStorageDescr|grep /|egrep -v "(/mnt|/home|/proc|/sys)"|awk '{print $NF}'); do
if [[ $partitions =~ /boot ]]; then
limit=$lower_limit;
fi
if result=$($snmpwalk -v2c -c $pass -Oq $server hrStorageDescr | grep "$partitions$"); then
index=$(echo $result | sed 's/.*hrStorageDescr//' | sed 's/ .*//')
args=$($snmpget -v2c -c $pass -Oqv $server hrStorageSize$index hrStorageUsed$index hrStorageAllocationUnits$index | while read oid j ; do printf " $oid" ; done)
free=$(calc_free$args)
back_count=$(echo $partitions|grep -o "/"|wc -l)
if [[ $back_count -ge 2 ]]; then
gpartition=$(echo "/"${partitions##*/})
else
gpartition=$partitions;
fi
if [ "$free" -gt "$limit" ]
then
graphtext=$graphtext$gpartition"="$free"MB;;;0 "
#graphtext=$graphtext$partitions"="$free"MB;;;0 "
partitions_found=$partitions_found" $partitions ($free MB)"
else
graphtext=$graphtext$gpartition"="$free"MB;;;0 "
#graphtext=$graphtext$partitions"="$free"MB;;;0 "
errors_found=$errors_found" $partitions ($free MB)"
fi
else
graphtext=$graphtext$gpartition"="0"MB;;;0 "
#graphtext=$graphtext$partitions"="0"MB;;;0 "
errors_found=$errors_found" $paritions does_not_exist_or_snmp_is_not_responding"
fi
done
if [ "$errors_found" == "" ]; then
echo "OK: $partitions_found$graphtext"
exit 0
else
echo "CRITICAL: $errors_found$graphtext";
exit 2;
fi
./snmp_remote_disk_auto localhost public 100
OK: / (1879 MB) /var (2281 MB) /tmp (947 MB) /boot (175 MB)|/=1879MB;;;0 /var=2281MB;;;0 /tmp=947MB;;;0 /boot=175MB;;;0
Not a huge fan of excessive greps and awks as it can really bring errors over time..
I would just get the information for the folders that matter. Below is a sample of using stat which will give you the available BYTES in a folder, then converts it to MB (10**6). I roughly tested this on my RHEL6.x system.
folder_x_mb=$(($(stat -f --format="%a*%s" /folder_x)/10**6))
folder_y_mb=$(($(stat -f --format="%a*%s" /folder_y)/10**6))
folder_z_mb=$(($(stat -f --format="%a*%s" /folder_z)/10**6))
How about something like:
df | perl -wne 'if(/(\d+)%\s+(.*)/){print "$2 at $1%\n" if $1>90}'
You can change the threshold and instead of printing you can just exit:
df | perl -wne 'if(/(\d+)%\s+(.*)/){exit 1 if $1>99}'
Here is a simple script to check if there are already disk that reached their maximum capacity and -- if there were it would return / output 1.
#!/bin/sh
CHECK=$(df -Ph | grep '100%' | xargs echo | cut -d' ' -f5)
if [ "$CHECK" == "100%"]
then
echo 1
else
echo 0
fi
Try this: df -Ph | grep -v "Use%" | sed 's/%//g' | awk '$5 > LIMIT {print $1,$2,$3,$4,$5"%";}' | column -t'
It will return all df -Ph entries that exceed the LIMIT
For example, on my workstation, df -Ph returns:
Filesystem Size Used Avail Use% Mounted on
/dev/cciss/c0d0p1 92G 32G 56G 37% /
shmfs 98G 304K 98G 1% /dev/shm
192.168.1.1:/apache_cache 2.7T 851G 1.9T 32% /media/backup
/dev/dm-4 50G 49G 1.1G 98% /lun1
/dev/dm-7 247G 30G 218G 12% /lun2
Let's say I want to list the mount points that exceed 20% of capacity.
I use df -Ph | grep -v "Use%" | sed 's/%//g' | awk '$5 > 20 {print $1,$2,$3,$4,$5"%";}' | column -t, and it returns the following:
/dev/cciss/c0d0p1 92G 32G 56G 37% /
192.168.1.1:/apache_cache 2.7T 851G 1.9T 32% /media/backup
/dev/dm-4 50G 49G 1.1G 98% /lun1
The column -t part is here purely for the output to be readable.

Resources