Checking if output of a command contains a certain string in a shell script - bash

I'm writing a shell script, and I'm trying to check if the output of a command contains a certain string. I'm thinking I probably have to use grep, but I'm not sure how. Does anyone know?

Testing $? is an anti-pattern.
if ./somecommand | grep -q 'string'; then
echo "matched"
fi

Test the return value of grep:
./somecommand | grep 'string' &> /dev/null
if [ $? == 0 ]; then
echo "matched"
fi
which is done idiomatically like so:
if ./somecommand | grep -q 'string'; then
echo "matched"
fi
and also:
./somecommand | grep -q 'string' && echo 'matched'

Another option is to check for regular expression match on the command output.
For example:
[[ "$(./somecommand)" =~ "sub string" ]] && echo "Output includes 'sub string'"

A clean if/else conditional shell script:
if (ls | grep '$1')
then
echo "exists"
else
echo "doesn't exist"
fi

SHORT ANSWER
All the above (very excellent) answers all assume that grep can "see" the output of the command, which isn't always true:
SUCCESS can be sent to STDOUT while FAILURE to STDERR.
So depending on which direction you test, your grep can fail. That's to say that if you are testing for the case of FAILURE you must redirect the output of the command to STDOUT using 2>&1 in such a case as this.
LONGER ANSWER w/ PROOFS
I had what I thought was a very simple test in a bash script using grep and it kept failing. Much head scratching followed. Use of set -x in my script revealed that the variable was empty! So I created the following test to understand how things were breaking.
NOTE: iscsiadm is a Linux tool from the "open-iscsi" package used to connect/disconnect a host to SAN storage. The command iscsiadm -m session is used to show if any LUN connections are established):
#!/bin/bash
set -x
TEST1=$(iscsiadm -m session)
TEST2=$(iscsiadm -m session 2>&1)
echo
echo 'Print TEST1'
echo $TEST1
echo
echo 'Print TEST2'
echo $TEST2
echo
If a LUN WAS connected, BOTH variables were successfully populated with values:
Print TEST1
tcp: [25] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:ipdisk.Target-LUN1 (non-flash) tcp: [26] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:storagehost.Target-LUN1 (non-flash)
Print TEST2
tcp: [25] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:ipdisk.Target-LUN1 (non-flash) tcp: [26] 192.168.X.XX:3260,1 iqn.2000-01.com.synology:storagehost.Target-LUN1 (non-flash)
However, if a LUN WASN'T connected, iscsiadm sent the output to STDERR, and only the "TEST2" variable was populated where we had redirected to STDOUT using 2>&1; "TEST1" variable which had no redirection to STDOUT was empty:
iscsiadm: No active sessions.
Print TEST1
Print TEST2
iscsiadm: No active sessions.
CONCLUSION
If you have a funky, half-broken- works in one direction but not the other- situation such as this, try the above test replacing iscsiadm with your own command and you should get the proper visibility to rewrite your test to work correctly.

Related

Grep issue with if else statement in bash

I am trying to check whether a docker container exists using grep.
The following script signals an error exit during running when IMAGE is empty and is fine when IMAGE is set. I can't find out the cause.
#!/usr/bin/env bash
set -e
IMAGE=$(docker ps | grep my-dev-image)
if [[ -z "$IMAGE" ]]; then
echo "It is not there"
else
echo "It is there"
fi
When you use set -e in a script the shell will exit whenever a command fails. It interacts badly with your grep call because grep exits with an error code if it doesn't find a match. If grep fails then the entire IMAGE=$(...) assignment fails and the script exits instead of setting IMAGE to an empty string.
You can fix this by ensuring the assignment always succeeds. A common idiom for this is to append || :. Adding || cmd will run cmd whenever the command on the left fails. And : is a command that always succeeds. (Yes, it's a command name consisting of a single colon. Strange, but it's a legal identifier.)
IMAGE=$(docker ps | grep my-dev-image) || :
Alternatively, you could check grep's exit code directly by using it in the if statement. This is what I would do if I didn't care about grep's output:
if docker ps | grep -q my-dev-image; then
echo "It is there"
else
echo "It is not there"
fi

egrep is returning 0 in bash script but executing manually the command returning correct value | unix bash

I'm executing a bash script that returns me if ftp connection failed or was success using egrep, the issue is that when I'm trying to get a word with egrep is returning 0 but If I execute the command manually is returning 2.
this is my code:
#Create the FTP Connection.
for ip_address in ${IP_ADDRESS[#]}; do
ftp ${ip_address} <<ftp_commands > ${FTP_RESULTS}
user "${USER_ID}" "${USER_PASSWORD}"
pwd
bye
ftp_commands
ftp_result_id=`egrep -c "Login failed|Connection refused|Not connected|Connection timed out" ${FTP_RESULTS}`
if [ ${ftp_result_id} -gt 0 ]; then
echo "$(date +%m/%d/%y_%H:%M:%S) - ${ip_address} - Not connected" >> ${CONNECTION_RESULTS_FILE}
else
echo "$(date +%m/%d/%y_%H:%M:%S) - ${ip_address} - Connected" >> ${CONNECTION_RESULTS_FILE}
fi
done
the ftp_results_id is returning 0 in the egrep -c command, but I'm executing manually after it ran and create the file "FTP_RESULTS" and is working, it suppose that found 2 matches with "Not connected"
any suggestion?
The egrep -c command counts the matches.
Then you use a condition to do something if there are more than 0 matches.
A simpler and better solution is to use the exit code of egrep.
egrep exits with 0 (= success) if it found a match,
and non-zero otherwise.
You can write the if statement like this:
if egrep -q "Login failed|Connection refused|Not connected|Connection timed out" "${FTP_RESULTS}"; then
This is equivalent to the logic in your posted code.
There's no need for the ftp_result_id variable.
And there's no need to save the output of egrep.
I added the -q flag so that egrep doesn't produce any output.
None needed.

string comparison is shell script

I have a scenario to copy file from one server to another, for that i need to check any existing scp is in progress, have wrote a sample shell script but the condition is not being met even though syntax is correct, the main problem here is the output of ps command will gets stored in variable scpstat and the same compared for matching string in if statement, here I'm getting the output of the variable is different from executing outside of the script. can see it is formatted different in script execution when executing sh -x scpsamp.sh, why there is "sh" appended to the output, but while comparing without ps and assigning as scpstat='scp' i can able to get the condition correct, am i doing anything wrong while getting output in to the variable. please help
#!/bin/sh
scpstat=`ps -ef | grep scp | egrep -v 'grep|ssh' | awk '{print $8}')`
if [ "$scpstat" = "scp" ];
then
echo "SCP is in progress"
else
echo "No SCP in progress"
fi
sh -x output
It's notoriously difficult to extract information from the output of ps. If your system has pgrep, it's much easier:
if pgrep scp >/dev/null
then
echo "SCP is in progress"
else
echo "No SCP in progress"
fi

Catching errors in Bash with glassfish commands [return code in pipes]

I am writing a bash script to manage deployments to a GF server for several environments. What I would like to know is how can I get the result of a GF command and then determine whether to continue or exit.
For example
Say I want to redeploy, I have this script
$GF_ASADMIN --port $GF_PORT redeploy --name $EAR_FILE_NAME --keepstate=true $EAR_FILE | tee -a $LOG
The variables are already defined. So GF will start to redeploy and either suceed or fail. I want to check if it does and act accordingly. I have this right after it.
RC=$?
if [[ $RC -eq 0 ]];
then echoInfo "Application Successfully redeployed!" | tee -a $LOG;
else
echoError "Failed to redeploy application!"
exit 1
fi;
However, it doesnt really seem to work .
The problem is the pipe
$GF_ASADMIN ... | tee -a $LOG
$? reflects the return code of tee.
Your are looking for PIPESTATUS. See man bash:
PIPESTATUS
An array variable (see Arrays below) containing a list of exit
status values from the processes in the most-recently-executed
foreground pipeline (which may contain only a single command).
See also this example to clarify the PIPESTATUS
false | true
echo ${PIPESTATUS[#]}
Output is: 1 0
The corrected code is:
RC=${PIPESTATUS[0]}
Or try using a code block redirect, for example:
{
if "$GF_ASADMIN" --port $GF_PORT redeploy --name "$EAR_FILE_NAME" --keepstate=true "$EAR_FILE"
then
echo Info "Application Successfully redeployed!"
else
echo Error "Failed to redeploy application!" >&2
exit 1
fi
} | tee -a "$LOG"

conditional redirection in bash

I have a bash script that I want to be quiet when run without attached tty (like from cron).
I now was looking for a way to conditionally redirect output to /dev/null in a single line.
This is an example of what I had in mind, but I will have many more commands that do output in the script
#!/bin/bash
# conditional-redirect.sh
if tty -s; then
REDIRECT=
else
REDIRECT=">& /dev/null"
fi
echo "is this visible?" $REDIRECT
Unfortunately, this does not work:
$ ./conditional-redirect.sh
is this visible?
$ echo "" | ./conditional-redirect.sh
is this visible? >& /dev/null
what I don't want to do is duplicate all commands in a with-redirection or with-no-redirection variant:
if tty -s; then
echo "is this visible?"
else
echo "is this visible?" >& /dev/null
fi
EDIT:
It would be great if the solution would provide me a way to output something in "quiet" mode, e.g. when something is really wrong, I might want to get a notice from cron.
For bash, you can use the line:
exec &>/dev/null
This will direct all stdout and stderr to /dev/null from that point on. It uses the non-argument version of exec.
Normally, something like exec xyzzy would replace the program in the current process with a new program but you can use this non-argument version to simply modify redirections while keeping the current program.
So, in your specific case, you could use something like:
tty -s
if [[ $? -eq 1 ]] ; then
exec &>/dev/null
fi
If you want the majority of output to be discarded but still want to output some stuff, you can create a new file handle to do that. Something like:
tty -s
if [[ $? -eq 1 ]] ; then
exec 3>&1 &>/dev/null
else
exec 3>&1
fi
echo Normal # won't see this.
echo Failure >&3 # will see this.
I found another solution, but I feel it is clumsy, compared to paxdiablo's answer:
if tty -s; then
REDIRECT=/dev/tty
else
REDIRECT=/dev/null
fi
echo "Normal output" &> $REDIRECT
You can use a function:
function the_code {
echo "is this visible?"
# as many code lines as you want
}
if tty -s; then # or other condition
the_code
else
the_code >& /dev/null
fi
This works well for me. If DUMP_FILE is empty things go to stdout otherwise to the file. It does the job without using explicit redirection, but just uses pipes and existing applications.
function stdout_or_file
{
local DUMP_FILE=${1:-}
if [ -z "${DUMP_FILE}" ]; then
cat
else
sed -n "w ${DUMP_FILE}"
fi
}
function foo()
{
local MSG=$1
echo "info: ${MSG}"
}
foo "bar" | stdout_or_file ${DUMP_FILE}
Of course, you can squeeze this also in one line
foo "bar" | if [ -z "${DUMP_FILE}" ]; then cat; else sed -n "w ${DUMP_FILE}"; fi
Besides sed -n "w ${DUMP_FILE}" another command that does the same is dd status=none of=${DUMP_FILE}
The simplest solution is to use eval (a shell builtin), as it will act on the redirection in the expanded variable... and also act on anything else in the command line, so add extra quoting as required (note the extra single quotes added around the echo string below due to the '?' which would otherwise cause shell filename expansion to be attempted).
#!/bin/bash
# conditional-redirect.sh
if tty -s; then
REDIRECT=
else
REDIRECT=">& /dev/null"
fi
eval echo '"is this visible?"' $REDIRECT

Resources