check last character of command output - bash

#!/bin/bash
ip="172.16.0.28"
community="abcd"
currentState=$(snmpget -v 1 -Oe -c $community $ip PowerNet-MIB::sPDUOutletCtl.$1)
currentState="${currentState: -1}"
if [ $currentState == 2 ] ; then
snmpset -v 1 -c abcd 172.16.0.28 PowerNet-MIB::sPDUOutletCtl.$1 i outletOn
else
snmpset -v 1 -c abcd 172.16.0.28 PowerNet-MIB::sPDUOutletCtl.$1 i outletOff
fi
Is it possible to rewrite the if statement as an one liner?
Something like
echo "${$(snmpget -v 1 -Oe -c abcd 172.16.0.28 PowerNet-MIB::sPDUOutletCtl.1): -1}"
bash: ${$(snmpget -v 1 -Oe -c abcd 172.16.0.28 PowerNet-MIB::sPDUOutletCtl.1): -1}: bad substitution
does not work.
EDIT: As requested, a possible return of snmpget:
$ snmpget -v 1 -Oe -c abcd 172.16.0.28 PowerNet-MIB::sPDUOutletCtl.1
PowerNet-MIB::sPDUOutletCtl.1 = INTEGER: 1

In code compatible with all POSIX-compliant shells:
case "$(snmpget -v 1 -Oe -c "$community" "$ip" "PowerNet-MIB::sPDUOutletCtl.$1")" in
*2) sfx=On;; # output from snmpget ends with 2
*) sfx=Off;; # output from snmpget does not end with 2
esac

You can't use the parameter expansion operators like this, they only operate on parameters (i.e. variables).
You can pipe to the tail command to get the last character of output.
currentState="$(snmpget -v 1 -Oe -c abcd 172.16.0.28 PowerNet-MIB::sPDUOutletCtl.1 | tail -c 2)"
You need to use -c 2 because the last character is a newline, and you want the character before that. The command substitution will then discard the newline at the end of the tail output.

Assumptions:
currentState ends in a character that's expected to be a number (or at least if the last character is 2 then the snmpget call should end with ...On)
the objective is to determine if the last snmpset arg should end with ..On or ..Off
One idea:
ip="172.16.0.28"
community="abcd"
currentState=$(snmpget -v 1 -Oe -c $community $ip PowerNet-MIB::sPDUOutletCtl.$1)
currentState="${currentState: -1}"
# figure out the suffix ...
sfx='Off'
[[ "${currentState}" = "2" ]] && sfx='On'
# now make the snmpset call
snmpset -v 1 -c "${community}" "${ip}" PowerNet-MIB::sPDUOutletCtl.$1 i outlet"${sfx}"

Related

Saving grep output to variable changes value

I want to use grep to determine if a string contains a substring. (The plan is to use that result as the test command in a bash if statement.)
Here, I check the length of the output of grep . :
$ echo "abc" | grep "j" | wc -c
0
Since wc -c shows zero, I know that grep returned an empty string.
But if I save output from grep to a variable before calling wc -c, I get a different value:
$ match=$(echo "abc" | grep "j")
$ echo "$match" | wc -c
1
The output from grep now is a string with 1 character in it.
I suspect that it's a newline in there:
$ echo $match
$
Why is there now an extra character in $match, and how can I keep that from happening?
match does not contain a newline, but echo "$match" writes a newline (but see note below). In the first case, you are directly passing the output of grep to wc, but in the second case you are passing the output of grep plus a newline to wc.
But don't do this at all. There is no need to introduce wc into the problem. Just test the value returned by grep. eg:
if ... | grep -q "$pattern"; then echo "$pattern was found in the input"; fi
Note, echo "$match" is bad practice. For example, consider if $match expands to the string -e or -n. It is much more robust to use printf '%s' "$match"
Consider the following:
$ echo "$match" | od -c
0000000 \n
0000001
$ echo "$match" | wc -c
1
$ printf "$match" | od -c
0000000
$ printf "$match" | wc -c
0
The only difference in the 2 commands is that echo appends a \n on the end of the ouput.
Of course, we can make the printf generate the same result by explicitly adding a \n on the end:
$ printf "$match\n" | od -c
0000000 \n
0000001
$ printf "$match\n" | wc -c
1

Set a command to a variable in bash script problem

Trying to run a command as a variable but I am getting strange results
Expected result "1" :
grep -i nosuid /etc/fstab | grep -iq nfs
echo $?
1
Unexpected result as a variable command:
cmd="grep -i nosuid /etc/fstab | grep -iq nfs"
$cmd
echo $?
0
It seems it returns 0 as the command was correct not actual outcome. How to do this better ?
You can only execute exactly one command stored in a variable. The pipe is passed as an argument to the first grep.
Example
$ printArgs() { printf %s\\n "$#"; }
# Two commands. The 1st command has parameters "a" and "b".
# The 2nd command prints stdin from the first command.
$ printArgs a b | cat
a
b
$ cmd='printArgs a b | cat'
# Only one command with parameters "a", "b", "|", and "cat".
$ $cmd
a
b
|
cat
How to do this better?
Don't execute the command using variables.
Use a function.
$ cmd() { grep -i nosuid /etc/fstab | grep -iq nfs; }
$ cmd
$ echo $?
1
Solution to the actual problem
I see three options to your actual problem:
Use a DEBUG trap and the BASH_COMMAND variable inside the trap.
Enable bash's history feature for your script and use the hist command.
Use a function which takes a command string and executes it using eval.
Regarding your comment on the last approach: You only need one function. Something like
execAndLog() {
description="$1"
shift
if eval "$*"; then
info="PASSED: $description: $*"
passed+=("${FUNCNAME[1]}")
else
info="FAILED: $description: $*"
failed+=("${FUNCNAME[1]}")
done
}
You can use this function as follows
execAndLog 'Scanned system' 'grep -i nfs /etc/fstab | grep -iq noexec'
The first argument is the description for the log, the remaining arguments are the command to be executed.
using bash -x or set -x will allow you to see what bash executes:
> cmd="grep -i nosuid /etc/fstab | grep -iq nfs"
> set -x
> $cmd
+ grep -i nosuid /etc/fstab '|' grep -iq nfs
as you can see your pipe | is passed as an argument to the first grep command.

grep -c kills script when no match using set -e

Basic example:
#!/bin/bash
set -e
set -x
NUM_LINES=$(printf "Hello\nHi" | grep -c "How$")
echo "Number of lines: ${NUM_LINES}" # never prints 0
Output:
++ grep -c 'How$'
++ printf 'Hello\nHi'
+ NUM_LINES=0
If there are matches, it prints the correct number of lines. Also grep "How$" | wc -l works instead of using grep -c "How$".
You can suppress grep's exit code by running : when it "fails". : always succeeds.
NUM_LINES=$(printf "Hello\nHi" | grep -c "How$" || :)

How to echoing data from WHOIS records with grep (redirecting standard output into a variable)

I have this bash script:
How I can call it the grep's output in the if else statement? If I suppress the output of the grep command with -q, also will work?
#!/usr/bin/env bash
DOMAINS=( '.com' '.biz' )
while read input; do
for (( i=0;i<${#DOMAINS[#]};i++)); do
jwhois --force-lookup --disable-cache --no-redirect -c jwhois.conf "$input${DOMAINS[$i]}" | MATCH="$(grep -oPa '^.*\b(clientTransferProhibited)\b.*$')"
if [ $? -eq 0 ]; then
echo -e "$input${DOMAINS[$i]}\tregistered\t" $(date +%y/%m/%d_%H:%M:%S) "\t" "$MATCH" |& tee --append output/registered.txt
else
echo -e "$input${DOMAINS[$i]}\tavailable\t" $(date +%y/%m/%d_%H:%M:%S) "\t" "$MATCH" |& tee --append output/available.txt
fi
done
done < "$1"
So MATCH="$(grep -oPa '^.*\b(clientTransferProhibited) used by "$MATCH" in the if else statement not outputting anything, but if I just use grep -oPa '^.*\b(clientTransferProhibited), it's printing the line without problem. The reason why I want to use as a variable, because I want to put in specific places in the if else statements.
Actual output:
$ domain1.com available 15/11/16_14:13:05
$ domain1.biz available 15/11/16_14:13:05
$ domain2.com registered 15/11/16_14:13:05
$ domain2.biz registered 15/11/16_14:13:05
Output that I want:
$ domain1.com available 15/11/16_14:13:05
$ domain1.biz available 15/11/16_14:13:05
$ domain2.com registered 15/11/16_14:13:05 Status: clientTransferProhibited http://www.icann.org/epp#clientTransferProhibited
$ domain2.biz registered 15/11/16_14:13:05 Status: clientTransferProhibited http://www.icann.org/epp#clientTransferProhibited
So the result of this line is what you want stored in a variable...
jwhois --force-lookup --disable-cache --no-redirect -c jwhois.conf "$input${DOMAINS[$i]}" | grep -oPa '^.*\b(clientTransferProhibited)\b.*$'
In that case, you want to evaluate that line and set the entire thing to your desired variable. The type of assignment you attempt in the middle of a pipeline will not be valid.
MATCH=$(jwhois --force-lookup --disable-cache --no-redirect -c jwhois.conf "$input${DOMAINS[$i]}" | grep -oPa '^.*\b(clientTransferProhibited)\b.*$')
Those are two different MATCH variables, because pipes execute subshells:
Bash subshell/pipelines - which parts are executing in subshells?
Try it this way instead:
MATCH="$(jwhois --force-lookup --disable-cache --no-redirect -c jwhois.conf "$input${DOMAINS[$i]}" | grep -oPa '^.*\b(clientTransferProhibited)\b.*$')"

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*$//'

Resources