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 JIRAn" printf "Press 2 for Confluencen" printf "Press 3 for Horden" printf "Press 4 for Apache Accessn" printf "Press 5 for Apache Errorn" 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?
Advertisement
Answer
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
andmainmenuinput
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 variousprintf "somethingn"
commands withecho "something"
, andprintf "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 tossh
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 formuser@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.