grep command exit code for unmatched patterns - shell

i have written a shell scripts which runs crontab - l command
To make it more easy to use i have also given the user an ability to pass a command line argument to the script which will act like a pattern input for the grep command, so that the user can filter out all the stuffs which he/she doesn't need to see.
here's the script:-
1 #!/bin/bash
2 if [[ $1 == "" ]]; then
3 echo -e "No Argument passed:- Showing default crontab\n"
4 command=$(crontab -l 2>&1)
5 echo "$command"
6 else
7 rc=$?
8 command=$(crontab -l | grep -- "$1" 2>&1)
9 echo "$command"
10 if [[ $rc != 0 ]] ; then
11 echo -e "grep command on crontab -l was not successful"
12 fi
13 fi
this is how i run it
$ ./DisplayCrontab.sh
Now if i don't pass any command line argument it'll show me the complete crontab
If i pass any garbage pattern which doesn't exists in the crontab it'll show me the following message :-
grep command on crontab -l was not successful
But even if i pass a pattern which does exist in a couple of lines in crontab, i'm getting this kind of output:-
#matching lines
#matching lines
#matching lines
grep command on crontab -l was not successful
Why am i getting grep command not successful at the bottom?, how can i get rid of it?
Is there anything wrong with the script?

You're capturing the exit code before the execution, should be:
command=$(crontab -l | grep -- "$1" 2>&1)
rc=$?
To test this code use numeric operators:
[[ $rc -ne 0 ]]
Grep man:
Normally, the exit status is 0 if selected lines are found and
1 otherwise. But the exit status is 2 if an error occurred

Related

Detect if script is already running in bash script, and only restart if not

I'm trying to write a script that will check if a script is already running, and not run it on cron if its still going from the last run. I found another post on here where they suggested using:
echo `pgrep -f $0` . "!=" . "$$";
if [[ `pgrep -f $0` != "$$" ]];
While this seems to work when I run it manually in SSH, it gives weird results when run via cron:
14767 14770 . != . 14770
Is this because there are 2 processes running with 2 different pids?
I have come up with this as an alternative:
if [ -n "$(ps -ef | grep -v grep | grep 'run.sh' | wc -l)" > 2 ];
then
echo "already running"
else
# do some stuff here
fi
Running the command on its own seems to work as expected:
# ps -ef | grep -v grep | grep 'run.sh' | wc -l)
2
But when in the code, it always shows "already running" , even though my condition is not met:
bash run.sh
2
already running
Maybe I'm doing something wrong with the variable as an int?
UPDATE: As suggested, I am trying flock:
#!/bin/bash
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$#" || :
#... rest of code here
But I get:
flock: failed to execute run.sh: No such file or directory
You could write your code like that but it will be complex and errorprone. Better to use file-locking. The flock command exists for this. Its man-page provides various examples you can cut and paste, including:
#!/bin/bash
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$#" || :
# ... rest of code ...
This is useful boilerplate code for shell scripts. Put it at
the top of the shell script you want to lock and it'll automatically
lock itself on the first run. If the env var $FLOCKER is
not set to the shell script that is being run, then execute
flock and grab an exclusive non-blocking lock (using the script
itself as the lock file) before re-execing itself with the right
arguments. It also sets the FLOCKER env var to the right value
so it doesn't run again.
man flock for details.

shell script - run list of commands

for i in `cat foo.txt`
do
$i
done
And I have a input file "foo.txt", with list of commands.
ls -ltr | tail
ps -ef | tail
mysql -e STATUS | grep "^Uptime"
when I run the shell script, it executes, but splits the commands in each line at spaces i.e for first line it executes only "ls", then "-ltr" for which I get command not found error.
How can I run each list as one command?
why am I doing this?
I execute lot of arbitrary shell commands including DB commands. I need to have a error handling as I execute each command(each line from foo.txt), I can't think of what can go wrong, so the idea is put all commands in order and call them in loop and check for error (#?) at each line and stop on error.
Why not just do this?
set -e
. ./foo.txt
set -e causes the shell script to abort if a command exits with a non-zero exit code, and . ./foo.txt executes commands from foo.txt in the current shell.
but I guess I can't send notification (email).
Sure you can. Just run the script in a subshell, and then respond to the result code:
#!/bin/sh
(
set -e
. ./foo.txt
)
if [ "$?" -ne 0 ]; then
echo "The world is on fire!" | mail -s 'Doom is upon us' you#youremail.com
fi
Code mentioned.
for i in `cat foo.txt`
do
$i
done
Please use https://www.shellcheck.net/
This will result _
$ shellcheck myscript
Line 1:
for i in `cat foo.txt`
^-- SC2148: Tips depend on target shell and yours is unknown. Add a shebang.
^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.
^-- SC2006: Use $(...) notation instead of legacy backticked `...`.
Did you mean: (apply this, apply all SC2006)
for i in $(cat foo.txt)
$
Will try while loop, and for test purpose content of foo.txt mentioned below
cat foo.txt
ls -l /tmp/test
ABC
pwd
while read -r line; do $line; if [ "$?" -ne 0 ]; then echo "Send email Notification stating $line Command reported error "; fi; done < foo.txt
total 0
-rw-r--r--. 1 root root 0 Dec 24 11:41 test.txt
bash: ABC: command not found...
Send email Notification stating ABC Command reported error
/tmp
In case error reported you can break the loop.
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_05.html
while read -r line; do $line; if [ "$?" -ne 0 ]; then echo "Send email Notification stating $line Command reported error "; break; fi; done < foo.txt
total 0
-rw-r--r--. 1 root root 0 Dec 24 11:41 test.txt
bash: ABC: command not found...
Send email Notification stating ABC Command reported error
while read -r line; do eval $line; if [ "$?" -ne 0 ]; then echo "Send email Notification stating $line Command reported error "; break; fi; done < foo.txt
total 0
-rw-r--r--. 1 root root 0 Dec 24 11:41 test.txt
bash: ABC: command not found...
Send email Notification stating ABC Command reported error

SoF2 shell script not running

I've got the following code in my shell script:
SERVER=`ps -ef | grep -v grep | grep -c sof2ded`
if ["$SERVER" != "0"]; then
echo "Already Running, exiting"
exit
else
echo "Starting up the server..."
cd /home/sof2/
/home/sof2/crons/start.sh > /dev/null 2>&1
fi
I did chmod a+x status.sh
Now I try to run the script but it's returning this error:
./status.sh: line 5: [1: command not found
Starting up the server...
Any help would be greatly appreciated.
Could you please try changing a few things in your script as follows and let me know if that helps you?(changed back-tick to $ and changed [ to [[ in code)
SERVER=$(ps -ef | grep -v grep | grep -c sof2ded)
if [[ "$SERVER" -eq 0 ]]; then
echo "Already Running, exiting"
exit
else
echo "Starting up the server..."
cd /home/sof2/
/home/sof2/crons/start.sh > /dev/null 2>&1
fi
The problem is with the test command. "But", I hear you say, "I am not using the test command". Yes you are, it is also known as [.
if statement syntax is if command. The brackets are not part of if syntax.
Commands have arguments separated (tokenized) by whitespace, so:
[ "$SERVER" != "0" ]
The whitespace is needed because the command is [ and then there are 4 arguments passed to it (the last one must be ]).
A more robust way of comparing numerics is to use double parentheses,
(( SERVER == 0 ))
Notice that you don't need the $ or the quotes around SERVER. Also the spacing is less important, but useful for readability.
[[ is used for comparing text patterns.
As a comment, backticks ` ` are considered deprecated because they are difficult to read, they are replaced with $( ... ).

Access $? Variable with a piped statement?

I have some code that I would like to have the $? variable of.
VARIABLE=`grep "searched_string" test.log | sed 's/searched/found/'`
Is there any way to test if this entire line (rather than just the sed command) was completed successfully? If I try the following code right after it:
if [ "$?" -ne 0 ]
then
echo 1
exit
fi
it doesn't run even if the grep part of the statement fails.
Could someone show how to resolve this issue?
Use the
echo ${PIPESTATUS[#]}
will print out the array of exit-statuses of all commands.
$ ls | grep . | wc -l
28
$ echo ${PIPESTATUS[#]}
0 0 0
but
$ ls | grep nonexistentfilename | wc -l
0
$ echo ${PIPESTATUS[#]}
0 1 0 #the grep returns 1 - pattern not found
or
$ ls nonexistentfilename | grep somegibberish | wc -l
ls: nonexistentfilename: No such file or directory
0
$ echo ${PIPESTATUS[#]}
1 1 0 #ls and grep fails
for exact command status
echo ${PIPESTATUS[1]} #for the grep
also here is the
set -o pipefail
from the docs
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.
$ ls nonexistentfile | wc -c
ls: nonexistentfile: No such file or directory
0
$ echo $?
0
$ set -o pipefail
$ ls nonexistentfile | wc -c
ls: nonexistentfile: No such file or directory
0
$ echo $?
1
EDIT based on the comment
Youre probably tried the next:
VARIABLE=$(grep "searched_string" test.log | sed 's/searched/found/')
echo "${PIPESTATUS[#]}"
Of course, this can't work because the whole $(...) part runs in the subshell (another process) and therefore any variable what is created is lost when the subshell exits. (at the ))
You should put the whole PIPESTATUS mechanism into $(...) like next:
variable=$(
grep "searched_string" test.log | sed 's/searched/found/'
# do something with PIPESTATUS
# you should not echo anythig to stdout (because will be captured into $variable)
# you can echo on stderr - e.g.
echo "=${PIPESTATUS[#]}=" >&2
)
Also, the second line of the comment is an solution, eg:
var_with_status=$(command | commmand2 ; echo ":DELIMITER:${PIPESTATUS[#]}")
now, the $var_with_status will contain not only the result of the command | command2 but the PIPESTATUS too, delimited with some unique delimiter, so you can extract it...
Also, the set -o pipefail will indicate the result - if you don't need exact place of the fail.
Also you can write the PIPESTATUS in some temp-file (in the subshell) and the parent can read it and delete the temp-file...
Also is possible print the PIPESTATUS into different file-descriptors in the subshell and read this descriptor in the parent shell, but....
... beware do not fall into the XY problem, where you will make extremelly complicated script, only because you don't want change the logic of the processing.
e.g. you can always break you script into safe parts, like:
var1=$(grep 'str' test.log)
#check the `$var1` and do something with the error indicated with `$?`
var2=(sed '....' <<<"$var1")
#check the `$var2` and do something with the error indicated with `$?`
#and so on
simple enough?
So, ask yourself - do you really need mungling with how to get the PIPESTATUS form an subshell?
Ps: don't use uppercase variable names. could interfere with some environment variables and causes hard-to-debug problems..

Bash script not exiting immediately when `exit` is called

I have the following bash script:
tail -F -n0 /private/var/log/system.log | while read line
do
if [ ! `echo $line | grep -c 'launchd'` -eq 0 ]; then
echo 'launchd message'
exit 0
fi
done
For some reason, it is echoing launchd message, waiting for a full 5 seconds, and then exiting.
Why is this happening and how do I make it exit immediately after it echos launchd message?
Since you're using a pipe, the while loop is being run in a subshell. Run it in the main shell instead.
#!/bin/bash
while ...
do
...
done < <(tail ...)
As indicated by Ignacio, your tail | while creates a subshell. The delay is because it's waiting for the next line to be written to the log file before everything closes.
You can add this line immediately before your exit command if you'd prefer not using process substitution:
kill -SIGPIPE $$
Unfortunately, I don't know of any way to control the exit code using this method. It will be 141 which is 128 + 13 (the signal number of SIGPIPE).
If you're trying to make the startup of a daemon dependent on another one having started, there's probably a better way to do that.
By the way, if you're really writing a Bash script (which you'd have to be to use <() process substitution), you can write your if like this: if [[ $line == *launchd* ]].
You can also exit the subshell with a tell-tale exit code and then test the value of "$?" to get the same effect you're looking for:
tail -F -n0 /private/var/log/system.log | while read line
do
if [ ! `echo $line | grep -c 'launchd'` -eq 0 ]; then
echo 'launchd message'
exit 10
fi
done
if [ $? -eq 10 ]; then exit 0; fi

Resources