How to select a command among multiples with priority - linux

I am trying to create a basic utility for which i need supervisord installed on machine. But the problem is that its depend on user, how he/she is installing it, so here i am trying to cover almost all scenario to get supervisord command.
Following is my code which i am using right now.
if [[ -f "/opt/anaconda/bin/supervisord" ]]; then
RUNNER="/opt/anaconda/bin/supervisord"
elif [[ -f "/usr/local/bin/supervisord" ]]; then
RUNNER="/usr/local/bin/supervisord"
elif [[ -f "/usr/bin/supervisord" ]]; then
RUNNER="/usr/bin/supervisord"
elif [[ "$(command -v supervisord)" ]]; then
RUNNER="supervisord"
else
echo "supervisord is not install on this machine"
exit 1
fi
I am looking any better approach to achieve this.

The posted code checks some absolute paths,
and when it finds one that exists,
it will use that.
This is not a good idea.
You can expect users to set their PATH in such a way that the correct version of supervisord will get used, wherever it is.
In other words, the command available on PATH must be the first choice.
if type supervisord &>/dev/null; then
RUNNER=supervisord
elif ...
...
fi

Related

check if service is running via bash script

I have a script sh startAWS.sh.
On top of it, i checked this :
if [[ `ps -acx|grep postgres|wc -l` < 1 ]]; then
echo "You need to start your Postgres service to run this script."
ps -acx|grep postgres|wc -l
exit 0
fi
When I run , I got
⚡️ laravel sh startAWS.sh
You need to start your Postgres service to run this script.
6
Hmm... what... 🤦🏻‍♂️
My Postgres is running, that echo should not be executed.
It shows 6, and 6 is not less than 1, that code should not be executed.
Does anyone know why I echo is printing out?
You need to perform a numeric comparison so use -lt or -gt in your check.
Comparing numbers in Bash
if [[ `ps -acx|grep postgres|wc -l` -lt 1 ]];
There are a couple of problems here.
First, as #RameshNaidu pointed out, inside [[ < ]] is doing string comparison (i.e. alphabetical order) rather than numeric, and that's causing trouble. The usual problem this mistake causes is that string sorting order is different than numeric order -- for instance, [[ 10 < 2 ]] evaluates to true, because "1" comes before "2" in character sorting order. But that doesn't apply here, because "6" comes after "1" in sorting order as well. What's happening is subtler than that: wc -l prints a number of spaces before the number of lines, and space does come before "1", so [[ " 6" < 1 ]] evaluates to true.
Second, ps | grep something is a bad way to check whether a process is running, because (depending on the exact timing) the ps command may include "grep something" in its output, and the grep command will match that. If you have pgrep available, use that instead, since it automatically avoids this problem. Another common workaround is to use grep "[s]omething" instead -- the brackets prevent it from matching itself, so you don't get this extra match.
But there's also a much simpler way to see if there were any matches: rather than using wc to count matches, just use grep -q (or pgrep -q) to suppress output, and just check its exit status (success = found at least one match, so negate it with ! to check for no match). It'll look like one of these:
if ! pgrep -q postgres; then
echo "You need to start your Postgres service to run this script."
or
if ! ps -acx |grep -q "[p]ostgres"; then
echo "You need to start your Postgres service to run this script."

Bash script lost shebang path after instantiate function

I am writing a script with a iterative menu to run command lines. However, after create the iterative menu I got a error when I want run the commands.
The error is [COMMAND]No such file or directory linux.
#!/bin/bash
ATESTS=("TEST NAME 1" "TESTE NAME 2")
PATH=("test1.xml" "text2.xml")
menu () {
for i in ${!ATESTS[#]}; do
printf "%3d%s) %s\n" $((i+1)) "${OPT[i]:- }" "${ATESTS[i]}"
done
[[ "$msg" ]] && echo "$msg"; :
}
prompt="Check an ATEST (again to uncheck, ENTER when done): "
while menu && read -rp "$prompt" num && [[ "$num" ]]; do
/usr/bin/clear;
[[ "$num" != *[![:digit:]]* ]] &&
(( num > 0 && num <= ${#ATESTS[#]} )) ||
{ msg="Invalid ATEST: $num"; continue; }
((num--)); msg="${ATESTS[num]} was ${OPT[num]:+un}checked"
[[ "${OPT[num]}" ]] && OPT[num]="" || OPT[num]="+"
done
for i in ${!ATESTS[#]}; do
[[ "${OPT[i]}" ]] && { printf "%s " "${ATESTS[i]}"; msg=""; }
done
echo "$msg"
for i in ${!ATESTS[#]}; do
if [[ "${OPT[i]}" ]] && [[ $PWD = /repo/$USER/program ]]; then
find . -iname ${PATH[i]} -exec cat {} \;
fi
done
I want find a *.xml file then execute with a script that already exist and belong to /usr/bin. However the find command dont execute and also the cat command in this example, getting the following error ([COMMAND]No such file or directory linux.)
if i try run one bash command before the function, the command execute without any problem, but after the function the commands fails.
I create one alias to the script for running inside /repo/$USER/program without include the path to the script.
The problem has nothing to do with the shebang or the function. The problem is that you're using the variable $PATH. This variable tells the system what directories to search for executable commands, so when you set it to an array... it's going to start looking for commands in the locations(s) specified by ${PATH[0]}, which is "test1.xml", which is not even a directory let alone one that contains all of the executables you need.
Solution: don't use the variable name PATH for anything other than the list of directories to search for commands. In fact, since this is one of a large number of all-uppercase variables that have special functions, it's best to use lowercase (or mixed-case) variables in your scripts, to avoid weird conflicts like this.
BTW, I can't tell if the rest of the script makes sense or not; your use of short-circuit booleans for conditional execution (e.g. this && that || do something) makes it really hard to follow the logic. I'd strongly recommend using if blocks for conditional execution (as you did in the for loop at the end); they make it much easier to tell what's going on.

Am I setting this script up correctly to run specific commands based on user input?

I have a small script that I am working on. This is only the second script that I have made using bash script.
Basically what I am wanting this script to do is take the users input and fire a command based on that choice.
As you can see the user first enters the host address of the instance they are going to ssh into and ultimately tail logs on. There are a couple things that I am not understanding.
If / Then / Else / Elif - The concept seems simple enough but perhaps how these should be used eludes me.
When I run my script through a bash parser, the parser comes back with the following message:
Line 2:
if [ "$mainmenuinput" = "1" ]; then
^-- SC2154: mainmenuinput is referenced but not assigned.
mainmenu() {
if [ "$mainmenuinput" = "1" ]; then
ssh "$customerurl" tail -f /data/jirastudio/jira/j2ee_*/log/main/current
elif [ "$mainmenuinput" = "2" ]; then
ssh "$customerurl" tail -f /data/jirastudio/confluence/j2ee_*/log/main/current
elif [ "$mainmenuinput" = "3" ]; then
ssh "$customerurl" tail -f /data/jirastudio/horde/service/log/main/current
elif [ "$mainmenuinput" = "4" ]; then
ssh "$customerurl" tail -f /data/jirastudio/apache/logs/access_log
fi
}
printf "\nEnter the customers host URL:\n"
read -r customerurl
printf "Press 1 for JIRA\n"
printf "Press 2 for Confluence\n"
printf "Press 3 for Horde\n"
printf "Press 4 for Apache Access\n"
printf "Press 5 for Apache Error\n"
read -p -r "Make your choice:" "$mainmenuinput"
Looking up the SC2154 entry I found that it means this:
ShellCheck has noticed that you reference a variable that is not assigned. Double check that the variable is indeed assigned, and that the name is not misspelled.
I am a little confused on what that means. If someone can explain that, I would greatly appreciate it.
As it stands, when I run the script, it pauses to wait for the user to enter the host address. The user hits ENTER and the script then presents them with the menu to have them choose which log they want to tail. The menu looks a little odd:
Press 1 for JIRA
Press 2 for Confluence
Press 3 for Horde
Press 4 for Apache Access
Press 5 for Apache Error
-r
Im not sure why the -r is showing up at the end of the menu. When a selection is made, the script ends and outputs this:
./tail_logs.sh: line 23: read:Make your choice:': not a valid identifier`
Any help with this would be appreciated or if anything a push in the right direction. I love figuring this stuff out but sometimes, its helpful to get shoved at least in the general direction of the error/resolution.
Thanks
EDIT 1
Ok, I updated my script with your suggestions. It seemed to still balk at a few things. For example:
(mainmenu "$customerurl" "$mainmenuinput")
Using ShellCheck I got back this:
Line 1:
(mainmenu "$customerurl" "$mainmenuinput") {
^-- SC2154: customerurl is referenced but not assigned.
^-- SC2154: mainmenuinput is referenced but not assigned.
^-- SC1070: Parsing stopped here. Mismatched keywords or invalid parentheses?
If I write this out like:
mainmenu() { then it does not complain. Also, if I run the script with it typed out as per the suggested way, I get an error about `syntax error near unexpected token '{'
The current code looks like this:
#!/bin/sh
mainmenu() {
echo "$1"
echo "$2"
if [ "$2" = "1" ]; then
ssh "$1" tail -f "/data/jirastudio/jira/j2ee_*/log/main/current"
elif [ "$2" = "2" ]; then
ssh "$1" tail -f "/data/jirastudio/confluence/j2ee_*/log/main/current"
elif [ "$2" = "3" ]; then
ssh "$1" tail -f "/data/jirastudio/horde/service/log/main/current"
elif [ "$2" = "4" ]; then
ssh "$1" tail -f "/data/jirastudio/apache/logs/access_log"
elif [ "$2" > 4 || < 1 ]; then
echo "Uh uh uh, you didnt say the magic word! The number you picked isnt in the list. Pick again."
fi
}
echo
echo "Enter the customers host address:"
read -r customerurl
echo "Press 1 for JIRA"
echo "Press 2 for Confluence"
echo "Press 3 for Horde"
echo "Press 4 for Apache Access"
echo "Press 5 for Apache Error"
read -r -p "Pick a number: " mainmenuinput
I get no errors when running this. But when I make a selection, the script ends and does not output the tail command at all. Also, Im not sure if I am validating user input outside of 1-4 correctly with the last elif statement although if I change this to else I get an error when I run the script.
I think my issue is in the first part of the function?
mainmenu() {
echo "$1"
echo "$2"
Without having $hostAddress and mainMenuInput does the script not know what should be assigned to $1 and $2 or does it automatically assign the first thing typed in to these variables?
The main problems are with the read command at the end. First, whatever immediately follows the -p option is used as a prompt string; in this case, the next argument is "-r", so it prints that as a prompt. You clearly want "Make your choice:" to be the prompt, so that must go immediately after -p (i.e. use either read -r -p "Make your choice:" ... or read -p "Make your choice:" -r ...). Second, when you use $mainmenuinput, it replaces that with the current value of mainmenuinput. In the shell, you use $variable to get the value of a variable, not to set it. With both of these problems corrected, the last command becomes:
read -p "Make your choice:" -r mainmenuinput
There's also another important thing: after reading the users' input, you need to actually call the mainmenu function. So just add mainmenu as the last line.
As for the if ... then ... elif ... structure, yours looks fine; I'm not sure what the question is. Although personally I'd add an else clause that printed an error that the option was not valid.
I do have some stylistic/best practice recommendations, though:
It's best to pass information to functions in the form of arguments, rather than global variables. That is, rather than using customerurl and mainmenuinput directly in the function, pass them as arguments (mainmenu "$customerurl" "$mainmenuinput"), then reference those arguments ("$1" and "$2") inside the functions. This doesn't matter much in a small script like this, but having clear distinctions between the variables used by different parts of a program makes things much easier to keep straight in larger programs.
In shell scripts, printf is the best way to do complex things like printing lines without a linefeed at the end, or translating escape characters... but if you're just doing a standard print-a-line-with-a-linefeed-at-the-end, echo is simpler. Thus, I'd replace the various printf "something\n" commands with echo "something", and printf "\nEnter the customers host URL:\n" with:
echo
echo "Enter the customers host URL:"
In the command
ssh "$customerurl" tail -f /data/jirastudio/jira/j2ee_*/log/main/current
(or ssh "$1" ... if you follow my recommendation about arguments instead of global variables), the wildcard (*) will be expanded on the local computer before being handed to ssh and passed to the remote computer to be executed. It'd be best to quote that argument to prevent that:
ssh "$customerurl" tail -f "/data/jirastudio/jira/j2ee_*/log/main/current"
Note that the quotes will be removed before it's passed to ssh and then to the remote computer, so they will not prevent the wildcard from being expanded on the remote computer.
The thing you're calling a URL isn't actually a URL; URLs are things like "https://stackoverflow.com/questions". They start with a protocol (or "scheme") like "http" or "ftp", then "://", then a server name, then "/", etc. ssh just takes a raw server name (optionally with a username, in the form user#server).
Update, based on EDIT 1: I wasn't clear on how to call the function; your definition (using mainmenu() { ...) is correct, but having defined the function you then need to actually run the function. Do to this, change the end of the script to something like this:
...
echo "Press 5 for Apache Error"
read -r -p "Pick a number: " mainmenuinput
mainmenu "$customerurl" "$mainmenuinput"
This will run the function, with the first argument ($1) set to "$customerurl", and second argument ($2) set to "$mainmenuinput".
There's also a problem with the elif clause you added in the function. The shell's syntax for test expressions is really really weird (mostly for historical reasons). Also, there are three common variants, the original [ ... ] (which is actually a command) which has the weirdest syntax, bash's [[ ... ]] variant (much cleaner syntax, but not available available in generic POSIX shells), and (( ... )) (cleaner syntax, math- rather than string-oriented, not portable). See BashFAQ #31 for details.
For what you're trying to do, any of these would work:
elif [ "$2" -gt 4 -o "$2" -lt 1 ]; then
# [ ... ] doesn't use || or &&, and uses -lt etc for numeric comparisons.
# < and > do string comparisons, which are ... different. And you'd
# need to quote them to keep them from being mistaken for redirects.
# Also, you need to specify the "$2" explicitly for each comparison.
elif [[ "$2" -gt 4 || "$2" -lt 1 ]]; then
# [[ ... ]] uses || and &&, but still uses -lt etc for numeric comparisons.
# < and > still do string comparisons, but don't need to be quoted
elif (( $2 > 4 || $2 < 1 )); then
# All numeric here, so < and > work
But there's still a problem, since the user might have entered something that isn't a number at all (just pressed return, typed "wibble", etc.), and in all of these cases numeric comparison will fail. Solution: skip the test, and use else instead of elif:
...
elif [ "$2" = "4" ]; then
ssh "$1" tail -f "/data/jirastudio/apache/logs/access_log"
else
echo "Uh uh uh, you didnt say the magic word! The number you picked isnt in the list. Pick again."
fi
}
... that way, if any of the previous conditions aren't met for any reason at all, it'll print the error message.

Bash - Locate Script and Execute with Options

I did a good bit of searching and testing on my own, but I can't seem to find the best way to achieve this goal. I would like to have a bash one liner that will find a script on the machine, execute the script and have the ability to add switches or needed information in my case to execute the script successfully.
To get a little more specific, I am in Kali Linux and I run the locate command like so:
locate pattern_create
which returns:
/usr/share/metasploit-framework/tools/pattern_create.rb
So I thought about piping this into xargs to run the script like so:
locate pattern_create | xargs ruby
but of course I could not specify the options I need to that would run the script successfully, which would be:
ruby /usr/share/metasploit-framework/tools/pattern_create.rb 2700
I came up with a work around, but I feel that it's somewhat sloppy and this could be done easier, and that's where I hope I could get any input/feedback.
I found out I can run:
pattern_create=$(locate pattern_create) && ruby $pattern_create 2700
to get exactly what I need, but then I am dealing with environment variables which I would not want a bunch of when doing this often. I was hoping to figure this out with xargs or maybe and even cleaner way if possible. I know this can be done easily with find -exec, but that won't work in my case where I don't where the script is stored.
Any help would be awesome, I appreciate everyone's time. Thank you.
You can do:
ruby $(locate pattern_create)
But be aware that if there are multiple lines returned by locate, then this may not do what you wanted.
This is a dangerous thing to do as you do not know what locate will return and you could end up executing arbitrary scripts. I suggest that you use an intermediate script which will protect against the unexpected, such as finding no scripts or finding more than one.
#! /bin/sh
#
if [ $# -eq 0 ]
then
echo >&2 "Usage: $0 script arguments"
exit -1
fi
script=$(locate $1)
numfound=$(locate $1 | wc -l)
shift
if [ $numfound -eq 1 ]
then
# Only run the script if exactly one match is found
ruby $script $*
elif [ $numfound -eq 0 ]
then
echo "No matching scripts found" >&2
exit -1
else
echo "Too many scripts found - $script" >&2
exit -1
fi

Running a command only once from a script, even script is executed multiple times

I need some help. I'm having a script 'Script 1', which will call 'Script 2' to run in background which checks something periodically. But I want the Script 2 to get started only once, even Script 1 is called multiple times. Is there a way to do it?
It would be even more helpful, if someone suggests some commands to achieve this.
Thanks in advance
Sure, you can put something like this at the top of Script2:
if [[ -f /tmp/Script2HasRun ]] ; then
exit
fi
touch /tmp/Script2HasRun
That will stop Script2 from ever running again by using a sentinel file, unless the file is deleted of course, and it probably will be at some point since it's in /tmp.
So you probably want to put it somewhere else where it can be better protected.
If you don't want to stop it from ever running again, you need some mechanism to delete the sentinel file.
For example, if your intent is to only have one copy running at a time:
if [[ -f /tmp/Script2IsRunning ]] ; then
exit
fi
touch /tmp/Script2IsRunning
# Do whatever you have to do.
rm -f /tmp/Script2IsRunning
And keep in mind there's a race condition in there that could result in two copies running. There are ways to mitigate that as well by using the content as well as the existence, something like:
if [[ -f /tmp/Script2IsRunning ]] ; then
exit
fi
echo $$ >/tmp/Script2IsRunning
sleep 1
if [[ "$(cat /tmp/Script2IsRunning 2>/dev/null)" != $$ ]] ; then
exit
fi
# Do whatever you have to do.
rm -f /tmp/Script2IsRunning
There are more levels of protection beyond that but they become complex, and I usually find that suffices for most things.

Resources