getting first column of a matching line in bash - linux

I am getting an output from a shell command, and want to incorporate the first column of a matching line into a second command. Here is what I have, and it works:
kubectl get pods -n system | while read string; do
if [[ "$string" == my-pod-* && "$string" == *Running* ]]; then
# echo $string
read -ra ADDR <<< "$string"
echo
echo "--- Reading logs for ${ADDR[0]} ---"
# desired output: kubectl -n system logs my-pod-123 --tail=5 -f
kubectl -n system logs ${ADDR[0]} --tail=5 -f
fi
done
the output from first command would look something like this:
name status namespace running
my-pod-123 Running system 4h31m #<<I want this one
another-pod-5 Running system 5h15m
my-pod-023 Terminating system 8h05m
given that the output will contain only one match, is there a shorter way to do this without looping like this? Thanks in advance for helping me improve my Bash skills as this seems very clumsy.

You may use awk like this:
name=$(kubectl get pods -n system | awk '/^my-pod.*Running/{print $1}')
[[ -n $name ]] && kubectl -n system logs "$name" --tail=5 -f
awk command will match pattern my-pod.*Running at the start of a line and if it is found then it will print first column. We store that in variable name.
If $name is not empty then we call kubectl -n system logs using that value.

How about grep?
wanted=$(kubectl get pods -n system | grep 'my-pod-.*Running')
Can do error checking at the same time:
if ! wanted=$(kubectl get pods -n system | grep 'my-pod-.*Running'); then
echo "Error: no running my-pods" >&2
fi

Related

Shellscript to monitor a log file if keyword triggers then run the snmptrap command

Is there a way to monitor a log file using shell script like
tail -f /var/log/errorlog.txt then if something like down keyword appears, then generate SNMPTRAP to snmp manager and continues the monitoring
I have a SNMP script available to generate SNMPTrap and it looks like
snmptrap -v v2c -c community host "Error message"
Lets the say the script name is snmp.sh
My question is how to perform the below operation
tail the logs
if keyword[down] matches then use snmp.sh script to send alert
else leave
As per the suggestion i tried this
tail -F /data/log/test.log |
egrep -io 'got signal 15 | now exiting' |
while read -r line ;
do
case "$line" in
"got signal 15")
echo "hi"
;;
"now exiting")
echo "hi2"
;;
*)
esac
done
but the problem is tail is not working here with case statement, whenever the new log details added its not going to the case statement and echos the output
I could get the output if i use cat/less/more
Could you someone please tell what mistake i have done here ?
Thanks in advance
It sounds like the pattern you want is this:
tail -f /var/log/errorlog.txt | grep -e down -e unmounted | while read -r line
do
case "$line" in
down)
./snmp.sh …
;;
unmounted)
./snmp.sh …
;;
*)
echo "Unhandled keyword ${line}" >&2
exit 1
esac
done
try
tail -f /var/log/errorlog.txt | grep "down" | while read line; do snmp.sh; done

Unable to array values outside of function in shell script [duplicate]

Please explain to me why the very last echo statement is blank? I expect that XCODE is incremented in the while loop to a value of 1:
#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output
if [ -z "$OUTPUT" ]
then
echo "Status WARN: No messages from SMcli"
exit $STATE_WARNING
else
echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
do
if [ "$STATUS" != "Optimal" ]
then
echo "CRIT: $NAME - $STATUS"
echo $((++XCODE))
else
echo "OK: $NAME - $STATUS"
fi
done
fi
echo $XCODE
I've tried using the following statement instead of the ++XCODE method
XCODE=`expr $XCODE + 1`
and it too won't print outside of the while statement. I think I'm missing something about variable scope here, but the ol' man page isn't showing it to me.
Because you're piping into the while loop, a sub-shell is created to run the while loop.
Now this child process has its own copy of the environment and can't pass any
variables back to its parent (as in any unix process).
Therefore you'll need to restructure so that you're not piping into the loop.
Alternatively you could run in a function, for example, and echo the value you
want returned from the sub-process.
http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL
The problem is that processes put together with a pipe are executed in subshells (and therefore have their own environment). Whatever happens within the while does not affect anything outside of the pipe.
Your specific example can be solved by rewriting the pipe to
while ... do ... done <<< "$OUTPUT"
or perhaps
while ... do ... done < <(echo "$OUTPUT")
This should work as well (because echo and while are in same subshell):
#!/bin/bash
cat /tmp/randomFile | (while read line
do
LINE="$LINE $line"
done && echo $LINE )
One more option:
#!/bin/bash
cat /some/file | while read line
do
var="abc"
echo $var | xsel -i -p # redirect stdin to the X primary selection
done
var=$(xsel -o -p) # redirect back to stdout
echo $var
EDIT:
Here, xsel is a requirement (install it).
Alternatively, you can use xclip:
xclip -i -selection clipboard
instead of
xsel -i -p
I got around this when I was making my own little du:
ls -l | sed '/total/d ; s/ */\t/g' | cut -f 5 |
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )
The point is that I make a subshell with ( ) containing my SUM variable and the while, but I pipe into the whole ( ) instead of into the while itself, which avoids the gotcha.
#!/bin/bash
OUTPUT="name1 ip ip status"
+export XCODE=0;
if [ -z "$OUTPUT" ]
----
echo "CRIT: $NAME - $STATUS"
- echo $((++XCODE))
+ export XCODE=$(( $XCODE + 1 ))
else
echo $XCODE
see if those changes help
Another option is to output the results into a file from the subshell and then read it in the parent shell. something like
#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
LINE="$LINE $line"
echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)

Calling a shell script that is stored in another shell script variabl

I searched SO but could not find any relevant post with this specific problem. I would like to know how to call a shell script which is stored in a variable of another shell script.
In the below script I am trying to read service name & corresponding shellscript, check if the service is running, if not, start the service using the shell script associated with that service name. tried multiple options shared in various forums(like 'eval' etc) with no luck. please help to provide your suggestions on this.
checker.sh
#!/bin/sh
while read service
do
servicename=`echo $service | cut -d: -f1`
servicestartcommand=`echo $service | rev | cut -d: -f1 | rev`
if (( $(ps -ef | grep -v grep | grep $servicename | wc -l) > 0 ))
then
echo "$servicename Running"
else
echo "!!$servicename!! Not Running, calling $servicestartcommand"
eval "$servicestartcommand"
fi
done < names.txt
Names.txt
WebSphere:\opt\software\WebSphere\startServer.sh
WebLogic:\opt\software\WebLogic\startWeblogic.sh
Your script can be refactored into this:
#!/bin/bash
while IFS=: read -r servicename servicestartcommand; do
if ps cax | grep -q "$servicename"; then
echo "$servicename Running"
else
echo "!!$servicename!! Not Running, calling $servicestartcommand"
$servicestartcommand
fi
done < names.txt
No need to use wc -l after grep's output as you can use grep -q
No need to use read full line and then use cut, rev etc later. You can use IFS=: and read the line into 2 separate variables
No need to use eval in the end
It is much simpler than you expect. Instead of:
eval "$servicestartcommand"
eval should only be used in extreme circumstances. All you need is
$servicestartcommand
Note: no quotes.
As an example, try this on the command-line:
cmd='ls -l'
$cmd
That should work. But:
"$cmd"
will fail. It will look for a program with a space in its name called 'ls -l'.
May be I don't get the idea, but why not use system variables?
export FOO=bar
echo $FOO
bar

Bash Script output is always 'ps' when piping to grep from ps regardless of PID results

given an array of pids and the code:
for i in ${listedPids[#]}
do
runningCheck="ps -u $USER | grep $i"
grepRes=(${runningCheck})
if [[ -n $grepRes ]]
then
echo $grepRes
echo $runningCheck
... code not related to the issue
fi
done
Regardless if those pids are active or not; I keep getting 'ps' from echo $grepRes while the output of echo $runningCheck shows up with the correct user name and pid. What am I missing?
Replace
"ps -u $USER | grep $i"
by
$(ps -u $USER | grep $i)
Command Substitution: Bash performs the expansion by executing your command and replacing the command substitution with the standard output of the
command, with any trailing newlines deleted.
I simplified your script and here's what it should look like.
for i in "${listedPids[#]}"
do
grepRes=$(ps --no-heading -p $i)
if [[ -n "$grepRes" ]]
then
echo "$grepRes"
... code not related to the issue
fi
done
An even shorter code could be written using while loop.
ps --noheading -p "${listedPids[#]}" | while read grepRes
do
echo "$grepRes"
... code not related to the issue
done
As alvits and l0b0 pointed out, I made a few syntax errors: grepRes=(${runningCheck}) when I just wanted to execute that line and not turn it to a list, and the fact pipes and redirects don't work in variables. In the end pgrep did the job as I just needed to continue looping till all the background processes ended.
Maybe you could try eval.
runningCheck1="ps -u $USER"
runningCheck2=" | grep $i"
echo $runningCheck1$runningCheck
eval $runningCheck1$runningCheck2

Check for zero lines output from command over SSH

If I do the following, and the network is down, then the zero case will be executed, which it shouldn't.
case "$(ssh -n $host zfs list -t snapshot -o name -H | grep "tank/fs" | wc -l | awk '{print $1}')" in
0) # do something
;;
1) # do something else
;;
*) # fail
esac
Earlier in the script I check that I can SSH to $host, but today I found this problem, where the network failed right after my check.
If I check the return value from the SSH command, then I will always get the return value from awk as it is executed last.
Question
How do I insure that I actually count zero lines that zfs outputted, and not zero lines from a failed SSH connection?
Say:
set -o pipefail
at the beginning of your script (or before the case statement).
Moreover, check for the return code of the command before executing the case statement:
set -o pipefail
$value=$(ssh -n $host zfs list -t snapshot -o name -H | grep "tank/fs" | wc -l | awk '{print $1}')
if [ $? == 0 ]; then
case $value in
...
esac
fi
From the manual:
pipefail
If set, the return value of a pipeline is the value of the last
(rightmost) command to exit with a non-zero status, or zero if all
commands in the pipeline exit successfully. This option is disabled by
default.
How about this (commands after ssh shouldn't get executed locally):
"$(ssh -n $host 'zfs list -t snapshot -o name -H | grep "tank/fs" | wc -l | awk \'{print $1}\')'"
Note how I single quoted the command for ssh to run on the remote machine. Also escaped the ' for awk like \'. Check the return value of the call and only act when it returns success.

Resources