Semicolons superfluous at the end of a line in shell scripts? [duplicate] - bash

This question already has answers here:
Bash semicolon being equal to newline is not exactly true?
(4 answers)
Closed 2 years ago.
I have a shell script which contains the following:
case $1 in
0 )
echo $1 = 0;
OUTPUT=3;;
1 )
echo $1 = 1;
OUTPUT=4;;
2 )
echo $1 = 2;
OUTPUT=4;;
esac
HID=$2;
BUNCH=16;
LR=.008;
Are semicolons completely superfluous in the snippet above? And is there any reason for some people using double semicolons?
It appears semicolons are only a separator, something you would use instead of a new line.

Single semicolons at the end of a line are superfluous, since the newline is also a command separator. case specifically needs double semicolons at the end of the last command in each pattern block; see help case for details.

According to man bash:
metacharacter
A character that, when unquoted, separates words. One of the following:
| & ; ( ) < > space tab
control operator
A token that performs a control function. It is one of the following symbols:
|| & && ; ;; ( ) | |& <newline>
So, the ; can be metacharacter or control operator, while the ;; is always a control operator (in case command).
In your particular code, all ; at the end of line are not needed. The ;; is needed however.

In the special case of find, ; is used to terminate commands invoked by -exec. See the answer of #kenorb to this question.

#Opensourcebook-Amit
newlines equivalent to single semicolon ; on terminal or in shell script.
See the below examples:
On terminal:
[root#server test]# ls;pwd;
On shell script:
[root#server test]# cat test4.sh
echo "Current UserName:"
whoami
echo -e "\nCurrent Date:";date;
[root#server test]#
But I am not agree with the comment that & is equivalent to newline or single semicolon
& is run commands in background also a command separator but not worked as semicolon or newline.

Related

Could someone explain me what this shell bash command "echo{,}" means?

If I do this:
echo{,}
The result is:
echo
I don't really understand the {,} at the end and the result
Thanks to clarify this.
I would start with something simpler to see how {} works: As #anubhava linked, it generates strings. Essentially, it expands all the elements in it and combines them with whatever is before and after it (space is separator if you don't quote).
Example:
$ bash -xc 'echo before{1,2}after and_sth_else'
+ echo before1after before2after and_sth_else
before1after before2after and_sth_else
Note that there is a space between echo and the arguments. This is not the case on what you have posted. So what happened there? Check the following:
$ bash -xc 'man{1,2}'
+ man1 man2
bash: man1: command not found
The result of the expansion is fed to bash and bash tries to execute it. In the above case, the command that is looking for is man1 (which does not exist).
Finally, combine the above to your question:
echo{,}
{,} expands to two empty elements/strings
These are then prefixed/concatenated with "echo" so we now have echo echo
Expansion finished and this is given to bash to execute
Command is echo and its first argument is "echo"... so it echoes echo!
echo{,}
is printing just echo because it is equivalent of echo echo.
More examples to clarify:
bash -xc 'echo{,}'
+ echo echo
echo
echo foo{,}
foo foo
echo foo{,,}
foo foo foo
More about Brace Expansion
Brace expansion is a mechanism by which arbitrary strings may be generated. This mechanism is similar to pathname expansion, but the filenames generated
need not exist. Patterns to be brace expanded take the form of an optional preamble, followed by either a series of comma-separated strings or a sequence
expression between a pair of braces, followed by an optional postscript. The preamble is prefixed to each string contained within the braces, and the
postscript is then appended to each resulting string, expanding left to right.
The {item1,item2,...} is a brace expansion.
So echo{,} is expanded as echo echo because {,} has two (empty) elements, then echo prints it argument.
Try this :
$ set -x
$ echo{,}
+ echo echo
echo
$ set +x
+ set +x
$
It's also handy to generate "cross products" without nested loops:
$ ary=( {1,2,3}{a,b,c} )
$ declare -p ary
declare -a ary=([0]="1a" [1]="1b" [2]="1c" [3]="2a" [4]="2b" [5]="2c" [6]="3a" [7]="3b" [8]="3c")

Bash semicolon being equal to newline is not exactly true?

I've read in multiple articles that semicolon(;) in UNIX-like shells is equal to a new line.
However, the following is confusing me and I'm having no luck googling it either.
I'm guessing it's an issue with do in shell, but "bash semicolon do" is not exactly the most google-friendly search term combination.
Below is a simple for statement.
for i in {1..10}
do
echo "hi"
echo "bye"
done
As many Stack Overflow gurus have posted, every newline can be substituted with semicolons.
So.. we have this following "same" statement.
for i in {1..10}; do; echo "hi"; echo "bye"; done
and we get:
-bash: syntax error near unexpected token `;'
What exactly is the semicolon? Is this just an unique issue with do?
Looking at the syntax of the for/do loop,
for name [ [in [words …] ] ; ] do commands; done
we can see that do is followed by commands immediately, so using a newline after do doesn't replace a semicolon, but a space.
The description for compound commands also says
In most cases a list of commands in a compound command’s description may be separated from the rest of the command by one or more newlines, and may be followed by a newline in place of a semicolon.
but nowhere does it say that you can insert random semicolons. "Every newline can be substituted with semicolons" is simply too general a statement and not correct.
More manual evidence: in the section about lists of commands, it says (emphasis mine):
A list is a sequence of one or more pipelines separated by one of the operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or a newline.
Of these list operators, && and || have equal precedence, followed by ; and &, which have equal precedence.
A sequence of one or more newlines may appear in a list to delimit commands, equivalent to a semicolon.
So a newline is equivalent to a semicolon within a list of commands.
It's not just with do; there are a number of contexts where the shell allows a newline where it would not allow a semicolon.
Here are most of them (leaving out the uses of newlines or semicolons inside quotes, which are always distinct from each other):
After && or ||
some_command &&
some_other_command
After a pipe (|):
some_producer |
some_consumer
Before the in in a for statement:
for x
in $(produce_values); do
Although empty commands are illegal, the shell grammar does allow empty value lists in a for command, so for x in; do do_something; done is legal, and so would be the same thing written with newlines instead of semicolons.
Before or after the in in a case statement; also after the closing ) of each pattern and the ;; which closes each case:
case $x
in
pattern)
do_something ;;
esac
After the keywords if, then, elif, else, while or until:
if
some_condition
then
do_something
else
do_something_else
fi
After the { or ( which opens a compound command or subshell:
{
a_command
another_command
}
(
a_command
another_command
)
Similarly, after the $( which starts a command substitution:
a=$(
echo hello
)
In bash, this also applies to the ( in process substitution: <(...). See below for the slightly different handling in the bash extensions dealing with conditionals and arithmetic substitution.
After the ) in a function definition:
my_function()
{
do_something
}
After the ; or & which terminates a command:
do_something;
do_something_in_background &
(Note that empty commands are illegal, so do_something; ; would be rejected.)
I took that list from the shell grammar in the Posix standard. Bash allows newlines in other places; I couldn't think of any when I wrote this answer, but #BenjaminW reminded me:
Inside a parenthesized array literal, either in an array assignment or an array declaration, newlines are considered whitespace:
a+=(
first_thing
second_thing
)
local -A b=(
[x]="value of x"
[y]="value of y"
)
And then I remembered these other pseudo-quoted environments:
Bash also accepts newlines as whitespace inside arithmetic expressions:
i=$((
i +
3 * j
))
if ((
i + 3 * j
>
9
))
and after the [[ or before the ]] in [[-style conditional statements:
if
[[
$a -eq "done"
]]
then break
fi
What #Benjamin W. explains in a quite exhaustive while lengthy answer can be summed up as a concise and simple general rule which also in my opinion sheds light on the reason behind the misunderstanding of this phenomena. What needs to be remembered is that:
In Bash
Every semicolon CAN be substituted with a newline.
but
Every newline CANNOT be substituted with a semicolon.
Here, do <command> is the original syntax. A newline can replace the space, but as a semicolon cannot replace a newline, we can't put a semicolon here.
However for the syntax command1 ; command2, a newline can replace a semicolon and give us
command1
command2
With time, we got much more familiar with a newline as the command-line separator instead of the semicolon and tend to see semicolons as a replacement for newlines in order to make one-liners, but it seems that bash was in fact designed the other way around: semicolons are the original separator, newlines are just syntactic sugar (in the context of the command-line separator).
NB: according to the explanations of Benjamin W.
They work in zsh:
for i in {1..10}; do; echo "hi"; echo "bye"; done
; for i in {1..10}; do; echo "hi"; echo "bye"; done
; for i in {1..10}; do; echo "hi"; echo "bye"; done;
(Pay attention to the special case, and if you meet similar case, come back to this topic for details)

Do I terminate line in bash file?: find . -type f -exec chmod 775 {} + [;] [duplicate]

This question already has answers here:
Bash semicolon being equal to newline is not exactly true?
(4 answers)
Closed 2 years ago.
I have a shell script which contains the following:
case $1 in
0 )
echo $1 = 0;
OUTPUT=3;;
1 )
echo $1 = 1;
OUTPUT=4;;
2 )
echo $1 = 2;
OUTPUT=4;;
esac
HID=$2;
BUNCH=16;
LR=.008;
Are semicolons completely superfluous in the snippet above? And is there any reason for some people using double semicolons?
It appears semicolons are only a separator, something you would use instead of a new line.
Single semicolons at the end of a line are superfluous, since the newline is also a command separator. case specifically needs double semicolons at the end of the last command in each pattern block; see help case for details.
According to man bash:
metacharacter
A character that, when unquoted, separates words. One of the following:
| & ; ( ) < > space tab
control operator
A token that performs a control function. It is one of the following symbols:
|| & && ; ;; ( ) | |& <newline>
So, the ; can be metacharacter or control operator, while the ;; is always a control operator (in case command).
In your particular code, all ; at the end of line are not needed. The ;; is needed however.
In the special case of find, ; is used to terminate commands invoked by -exec. See the answer of #kenorb to this question.
#Opensourcebook-Amit
newlines equivalent to single semicolon ; on terminal or in shell script.
See the below examples:
On terminal:
[root#server test]# ls;pwd;
On shell script:
[root#server test]# cat test4.sh
echo "Current UserName:"
whoami
echo -e "\nCurrent Date:";date;
[root#server test]#
But I am not agree with the comment that & is equivalent to newline or single semicolon
& is run commands in background also a command separator but not worked as semicolon or newline.

Perl: Can't pass question mark as command line argument

I can't pass a question mark character into a Perl script. It comes out as the letter "t" instead.
./myscript foo
print $ARGV[0]; # prints foo
./myscript ?
print $ARGV[0]; # prints t
./myscript "?"
print $ARGV[0]; # prints ?
./myscript ??
print $ARGV[0]; # prints ?? (multiple question marks work)
./myscript ^
print $ARGV[0]; # prints ^; other special characters work too
./myscript foo?
print $ARGV[0]; # prints foo?
I can't find an explanation for this anywhere. The reason I want to pass in a question mark is so my script has an easy help option. Other special characters work, as does quoting the question mark, as does a question mark mixed in with another string. It's just a single, naked question mark that doesn't.
I'm calling the script via the Bash shell. I'm not 100% positive this is a Perl issue
Update: now that this question is answered, I'm suddenly realizing: is this the reason that most command line arguments have a dash in front of them?
You have a file named t in the directory in which you are running your script. The shell treats a question mark (?) as a single character and expands it prior to passing the argument to your script. If you have a file named xx, for example, then running your script with ?? would print "xx".
If you quote your script's argument, no shell expansion occurs and passing ? or * or ?? remain uninterpreted.
The question mark ? is a shell metacharacter that is used for glob expansion. It is a placeholder for a single character, similar to . in a regex. E.g. If I have files a1.txt a2.txt b1.txt, then the glob pattern a?.txt will match the a1.txt and a2.txt but not b1.txt.
The following shell metacharacters must always be escaped or quoted when they are part of a command line argument:
| & ; < > ( ) $ ` \ " ' <space> <tab> <newline>
These characters must sometimes be escaped:
* ? [ # ˜ = %
Additionally the symbols ! { } [[ ]] are reserved words.
(taken from the POSIX shell language description. Bash is mostly POSIX-compatible.)

Take care of last newline while capturing commands output [duplicate]

This question already has answers here:
Capturing multiple line output into a Bash variable
(7 answers)
Closed 6 years ago.
I know $(cmd) captures the output of cmd into a string. However, it doesn't take care of the newline characters at the end.
Here's a demonstration:
a=$(echo x)
b=$(echo -n x)
[ "$a" = "$b" ] && echo equal
a and b capture outputs differed by a "\n" but these variables have the same value.
So my goal is still to capture the output of a command, but this time I want to preserve the last newline character(s) if there are any.
Trailing newlines are removed
POSIX requires that the $(…) notation (or the equivalent using back-ticks) strips all trailing newlines from the end of the string that is captured.
§6.2.3 Command substitution
…, removing sequences of one or more <newline> characters at the end of the substitution.
There isn't a simple way around that, or to detect how many newlines were deleted.
Add a single newline to the end of the output
If you have Bash 4.x (4.3 tested) you can play with shell parameter expansion and the substring notation, and add a dummy line of output to the end of the original string (that's the echo n in this example):
$ x=$(echo pandemonium; blanklines 4; echo n)
$ echo "$x"
pandemonium
n
$ y=${x: 0: -1}
$ echo "$y"
pandemonium
$
When using Bash 3.2 (GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)) on macOS Sierra, the expansion for y generates an error:
$ y=${x: 0: -1}
-bash: -1: substring expression < 0
$
See also Capturing multiple line output to a Bash variable.
Reliable and POSIX-compliant
Or you can use a simpler, more portable (POSIX-compatible) substitution suggested by the answer — as applied to this answer:
$ y=${x%n}
$ echo "$y"
pandemonium
$
Given that this works in strict POSIX shells and both Bash 3.x and 4.x, there's no need for the substring variant.

Resources