Difference between ;& and ;; in a bash script - linux

I'm copying a bash script from a Linux box to my Mac laptop and in the process, the script has started complaining about the usage of ;&.
With the error:
./build.sh: line 122: syntax error near unexpected token `;'
./build.sh: line 122: ` ;&'
There's a few uses in the script, but a short one is:
case ${OPTION} in
xxx)
export DEVICES=xxx
;;
yyy)
export DEVICES=yyy
;;
*)
echo "${OPTION}: Unknown device type: use xxx|yyy"
exit 1
;&
esac
;;
I've replaced all usages of ;& with ;; and I think that everything is ok, but I'm still curious what I've done. What's the difference between semicolon-ampersand and double semicolon in case statements?

From man bash:
Using ;& in place of ;; causes execution to continue with the list
associated
with the next set of patterns.
Since ;& occurs on the last pattern in the case statement, it should make no difference in that script.

Related

BASH getopts Multiple Scripts with Same Options

I have a series of BASH scripts.
I am using getopts to parse arguments from the cmd line (although open to alternatives).
There are a series of common options to these scripts call this options set A
ie queue, ncores etc.
Each script then has a series of extra options ie set B1,B2,B3.
What I want is for script
"1 to be able to take options A+B1"
"2 to be able to take options A+B2"
"3 to be able to take options A+B2"
But I want to be able to store the code for options A in a central location (library/function) with having to write out in each script.
What I want is a way to insert generic code in getopts. Or alternatively a way to run getopts twice.
In fact I've done this by having getopts as a function which is sourced.
But the problem is I cant get the unrecognised option to work them.
I guess one way would be to remove the arguements from options A from the string before passing to a getopts for B1, B2 , B3 etc ?
Thanks Roger
That's a very nice question, to answer which we need to have a good understanding of how getopts works.
The key point here is that getopts is designed to iterate over the supplied arguments in a single loop. Thus, the solution to the question is to split the loop between different files rather then running the command twice:
#!/usr/bin/env bash
# File_1
getopts_common() {
builtin getopts ":ab:${1}" ${2} ${#:3} || return 1
case ${!2} in
'a')
echo 'a triggered'
continue
;;
'b')
echo "b argument supplied -- ${OPTARG}"
continue
;;
':')
echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
exit 1
;;
esac
}
#!/usr/bin/env bash
# File_2
# source "File_1"
while getopts_common 'xy:' OPTKEY ${#}; do
case ${OPTKEY} in
'x')
echo 'x triggered'
;;
'y')
echo "y argument supplied -- ${OPTARG}"
;;
'?')
echo "INVALID OPTION -- ${OPTARG}" >&2
exit 1
;;
':')
echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
exit 1
;;
*)
echo "UNIMPLEMENTED OPTION -- ${OPTKEY}" >&2
exit 1
;;
esac
done
Implementation notes
We start with File_2 since that's where the execution of the script starts:
Instead of invoking getopts directly, we call it via it's proxy: getopts_common, which is responsible for processing all common option.
getopts_common function is invoked with:
A string that defines which options to expect, and where to expect their arguments. This string only covers options defined in File_2.
The name of the shell-variable to use for option reporting.
A list of the command line arguments. (This simplifies accessing them from inside getopts_common function.)
Moving on to the sourced file (File_1) we need to bear in mind that getopts_common function runs inside the while loop defined in File_2:
getopts returns false if there is nothing left to parse, || return 1 bit insures that getopts_common function does the same.
The execution needs to move on to the next iteration of the loop when a valid option is processed. Hence, each valid option match ends with continue.
Silent error reporting (enabled when OPTSPEC starts with :) allows us to distinguish between INVALID OPTION and MISSING ARGUMENT. The later error is specific to the common options defined in File_1, thus it needs to be trapped there.
For more in-depth information on getopts, see Bash Hackers Wiki: Getopts tutorial

case in bash: "line 4: syntax error near unexpected token `)'"

case in bash:
line 4: syntax error near unexpected token `)'
I'm trying to use the command case in Bash (on my Raspberry Pi again), but when I run my script, Bash spits out errors. I've read over many tutorials and I think I'm doing the same thing as them, but something's just not right.
Here's my code:
#!/bin/bash
case "$1" in
help) echo "You asked for help. Sorry, I'm busy."
*) echo "You didn't say anything. Try 'help' as the first argument."
esac
Here's the output (the filename is newmkdir and I ran it with no arguments):
./newmkdir: line 4: syntax error near unexpected token `)'
./newmkdir: line 4: ` *) echo "You didn't say anything. Try 'help' as the first argument."'
I'm trying to have my script interpret help and then make anything else output the next line.
(Note this is just an example of a glitched script. This script has no meaning and might not even make sense, it's just a test.)
You are missing ;; at the end of each pattern:
#!/bin/bash
case "$1" in
help)
echo "You asked for help. Sorry, I'm busy."
;;
*)
echo "You didn't say anything. Try 'help' as the first argument."
;;
esac
Think of it as a break statement in a programming language. They are compulsory on case.

RHEL6 getopts doesn't seem to be working

I have a new RHEL6 machine and I'm trying to run a script to generate some output. The script uses getopts which I've never used in the past. This should have worked on other machines but is my first time trying it. Below is the beginning of the script. Is there anything wrong with the syntax? When I try to output the variables it displays nothing:
#! /bin/sh
while getopts "h:u:g:o:e:y:bf" c
do
case "$c" in
u) USER=$OPTARG;;
g) GROUP=$OPTARG;;
o) OUT=$OPTARG;;
b) BATCH=1;;
f) FORCE=1;;
h) FQDN=$OPTARG;;
e) ENTITYID=$OPTARG;;
y) YEARS=$OPTARG;;
\?) echo "keygen [-o output directory (default .)] [-u username to own keypair] [-g owning groupname] [-h hostname for cert] [-y years to issue cert] [-e entityID to embed in cert]"
exit 1;;
esac
done
echo $FQDN
The echo displays a blank line.
You can't use question mark with the bash getopts (you also can't use the colon). In the case of question mark, getopts sets the value of the argument ($c in your case) to a question mark when the end of options has been encountered. It also uses question mark and colon as the value for the argument name when there's an error (specifically, ? is used when an invalid option is encountered or when in non-silent mode and a required option is not provided; colon is used in silent mode when a required option is not provided). In those error cases, OPTARG contains the offending argument. This is how POSIX getopts works as well.
The KSH getopts behaves differently, but it also excludes ? : (as well as - [ ] and only allowing # as the first option). It does, however, show a usage message when you provide -?. Basically, don't use -? with shell getopts. :)
Typically, I write a small function called "usage" and call it from both *) and by checking $? immediately after the case statement for non-zero value.

Exit code of variable assignment to command substitution in Bash

I am confused about what error code the command will return when executing a variable assignment plainly and with command substitution:
a=$(false); echo $?
It outputs 1, which let me think that variable assignment doesn't sweep or produce new error code upon the last one. But when I tried this:
false; a=""; echo $?
It outputs 0, obviously this is what a="" returns and it override 1 returned by false.
I want to know why this happens, is there any particularity in variable assignment that differs from other normal commands? Or just be cause a=$(false) is considered to be a single command and only command substitution part make sense?
-- UPDATE --
Thanks everyone, from the answers and comments I got the point "When you assign a variable using command substitution, the exit status is the status of the command." (by #Barmar), this explanation is excellently clear and easy to understand, but speak doesn't precise enough for programmers, I want to see the reference of this point from authorities such as TLDP or GNU man page, please help me find it out, thanks again!
Upon executing a command as $(command) allows the output of the command to replace itself.
When you say:
a=$(false) # false fails; the output of false is stored in the variable a
the output produced by the command false is stored in the variable a. Moreover, the exit code is the same as produced by the command. help false would tell:
false: false
Return an unsuccessful result.
Exit Status:
Always fails.
On the other hand, saying:
$ false # Exit code: 1
$ a="" # Exit code: 0
$ echo $? # Prints 0
causes the exit code for the assignment to a to be returned which is 0.
EDIT:
Quoting from the manual:
If one of the expansions contained a command substitution, the exit
status of the command is the exit status of the last command
substitution performed.
Quoting from BASHFAQ/002:
How can I store the return value and/or output of a command in a
variable?
...
output=$(command)
status=$?
The assignment to output has no effect on command's exit status, which
is still in $?.
Note that this isn't the case when combined with local, as in local variable="$(command)". That form will exit successfully even if command failed.
Take this Bash script for example:
#!/bin/bash
function funWithLocalAndAssignmentTogether() {
local output="$(echo "Doing some stuff.";exit 1)"
local exitCode=$?
echo "output: $output"
echo "exitCode: $exitCode"
}
function funWithLocalAndAssignmentSeparate() {
local output
output="$(echo "Doing some stuff.";exit 1)"
local exitCode=$?
echo "output: $output"
echo "exitCode: $exitCode"
}
funWithLocalAndAssignmentTogether
funWithLocalAndAssignmentSeparate
Here is the output of this:
nick.parry#nparry-laptop1:~$ ./tmp.sh
output: Doing some stuff.
exitCode: 0
output: Doing some stuff.
exitCode: 1
This is because local is actually a builtin command, and a command like local variable="$(command)" calls local after substituting the output of command. So you get the exit status from local.
I came across the same problem yesterday (Aug 29 2018).
In addition to local mentioned in Nick P.'s answer and #sevko's comment in the accepted answer, declare in global scope also has the same behavior.
Here's my Bash code:
#!/bin/bash
func1()
{
ls file_not_existed
local local_ret1=$?
echo "local_ret1=$local_ret1"
local local_var2=$(ls file_not_existed)
local local_ret2=$?
echo "local_ret2=$local_ret2"
local local_var3
local_var3=$(ls file_not_existed)
local local_ret3=$?
echo "local_ret3=$local_ret3"
}
func1
ls file_not_existed
global_ret1=$?
echo "global_ret1=$global_ret1"
declare global_var2=$(ls file_not_existed)
global_ret2=$?
echo "global_ret2=$global_ret2"
declare global_var3
global_var3=$(ls file_not_existed)
global_ret3=$?
echo "global_ret3=$global_ret3"
The output:
$ ./declare_local_command_substitution.sh 2>/dev/null
local_ret1=2
local_ret2=0
local_ret3=2
global_ret1=2
global_ret2=0
global_ret3=2
Note the values of local_ret2 and global_ret2 in the output above. The exit codes are overwritten by local and declare.
My Bash version:
$ echo $BASH_VERSION
4.4.19(1)-release
(not an answer to original question but too long for comment)
Note that export A=$(false); echo $? outputs 0! Apparently the rules quoted in devnull's answer no longer apply. To add a bit of context to that quote (emphasis mine):
3.7.1 Simple Command Expansion
...
If there is a command name left after expansion, execution proceeds as described below. Otherwise, the command exits. If one of the expansions contained a command substitution, the exit status of the command is the exit status of the last command substitution performed. If there were no command substitutions, the command exits with a status of zero.
3.7.2 Command Search and Execution [ — this is the "below" case]
IIUC the manual describes var=foo as special case of var=foo command... syntax (pretty confusing!). The "exit status of the last command substitution" rule only applies to the no-command case.
While it's tempting to think of export var=foo as a "modified assignment syntax", it isn't — export is a builtin command (that just happens to take assignment-like args).
=> If you want to export a var AND capture command substitution status, do it in 2 stages:
A=$(false)
# ... check $?
export A
This way also works in set -e mode — exits immediately if the command substitution return non-0.
As others have said, the exit code of the command substitution is the exit code of the substituted command, so
FOO=$(false)
echo $?
---
1
However, unexpectedly, adding export to the beginning of that produces a different result:
export FOO=$(false)
echo $?
---
0
This is because, while the substituted command false fails, the export command succeeds, and that is the exit code returned by the statement.

How do I fix unexpected end of file in bash script?

I had much luck last time I submitted a question so here goes: I am trying to debug a somewhat large BASH script when I get the following error:
./test.sh: line 418: unexpected EOF while looking for matching `"'
./test.sh: line 427: syntax error: unexpected end of file
The code below starts at line 400:
echo "###########################################################"
echo
;;
4)
culebra_carriers
get_month
get_day
logs_cdrs
logs_wap
get_mdn
echo
echo "###########################################################"
echo
echo "Searching for activity of $mobileNumber on $MON $DAY......."
echo
zgrep $mobileNumber $HOME/culebrapeak/$LOGCDR/$CULEB/$MON/$WAPLOG
echo
echo "###########################################################"
echo
;;
esac
done
}
clear
main_menu
How do I make this error go away? It looks like I have the double quotes in all the right places... but this is only my 4th or 5th bash script... so please go easy on me.
I was, indeed, missing a double quote at the top of my script. Thanks to all for the help!
A good way to solve problems like this is to use a text editor that highlights code between quotes. Short of that, if the "find" feature of your text editor gives a count too, you may be able to use it to quantitatively detect start/end character symmetry problems. The highlighting from the find feature will aide your eye tremedously.

Resources