Bash multi-line with `{` and `}` syntax - bash

I realized in bash, and perhaps in other shells too, multiline commands can be pasted within
{
# some bash command here
}
How does this work?
What is the bash language syntax or property coming into picture here?
I realize this has to be newline separated
For e.g.
$ { date }
> -bash: syntax error: unexpected end of file
$ {
> date
> }
Sat Aug 24 06:02:03 PDT 2019

{...} is one of several compound commands described in the man page. Neither { nor } is a reserved keyword; they are only treated specially when they appear in the command position of a simple command. For that reason, the last command in the group has to be properly terminated (either with a newline or a semicolon), so that } isn't treated simply as another argument of the last command.
$ { date; }
Sat Aug 24 06:02:03 PDT 2019
From the man page:
Compound Commands
A compound command is one of the following. In most cases a list in a
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.
[...]
{ list; }
list is simply executed in the current shell environment. list
must be terminated with a newline or semicolon. This is known
as a group command. The return status is the exit status of
list. Note that unlike the metacharacters ( and ), { and } are
reserved words and must occur where a reserved word is permitted
to be recognized. Since they do not cause a word break, they
must be separated from list by whitespace or another shell
metacharacter.
[...]

Related

Can the '-' character, as is, be an alias in bash?

I am trying to make bash alias the '-' character, but this does not work, e.g.,
% alias "-"=date
bash: alias: -=: invalid option
can this be done? and if we are at it, how about alias '='=date ?
`
The behavior you want depends on shell-specific extensions; even when the POSIX standard does specify alias behavior (which is only the case for shells implementing both XSI and user portability extensions), the set of allowed names is not required to include either - or =:
3.10 Alias Name
In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set and any of the following characters: '!', '%', ',', '#'.
Implementations may allow other characters within alias names as an extension.
That said, when defining an alias in bash, -- can be used to cause subsequent arguments not to be parsed as options (per POSIX syntax guidelines entry #10):
alias -- -=date
Another option available in practice with bash (tested on both 3.2.57(1) and 4.3.46(1), but not required by the POSIX standard to be supported with these names) is to define functions:
$ =() { date "$#"; }
$ -() { date "$#"; }
$ =
Sat Aug 13 18:12:37 CDT 2016
$ -
Sat Aug 13 18:12:08 CDT 2016
Again, this goes beyond the set of names required by POSIX:
2.9.5 Function Definition Command
The format of a function definition command is as follows:
fname() compound-command[io-redirect ...]
The function is named fname; the application shall ensure that it is a name (see the Base Definitions volume of IEEE Std 1003.1-2001, Section 3.230, Name). An implementation may allow other characters in a function name as an extension. The implementation shall maintain separate name spaces for functions and variables.
3.230 Name
In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.
...and, thus, being defined neither by POSIX nor by bash's own documentation, may be subject to change in future releases.
You can have an alias with the name - like this:
alias -- -=date
I'm not aware of any way of defining an alias named =.

Suppress quote character in bash completion

I am adding bash completion for some parameters of a query that users can enter on the command line to a script. Something like:
foo.py 'select abc(), def
Here I am completing the column names abc() and def. The problem that I am facing is that bash is automatically adding the ending single quote which is a hindrance to users. They might want to continue writing the query. Note that I need to use the single quotes while accepting the query as the query can contain parenthesis.
For example,
foo.py 'select ab<TAB>
should result in:
foo.py 'select abc(
instead of:
foo.py 'select abc('
My completion function returns select abc( but it seems that bash is inserting the extra closing quote that I want to get rid of. Any ideas how to do this?
Completion function used:
_egg_autocomplete_()
{
case "$COMP_CWORD" in
2)
readarray -t COMPREPLY < <( ./suggest.py order $2 )
;;
esac
}
suggest.py contains the actual code which prints suggested completions one per line. The suggestions can contain spaces in them.
I found the following at https://tiswww.case.edu/php/chet/bash/NEWS but was unable to make it work:
j. New application variable, rl_completion_suppress_quote, settable
by an application completion function. If set to non-zero,
readline does not attempt to append a closing quote to a completed
word.
I added the following line to my ~/.inputrc file but it didn't cause any difference:
set rl_completion_suppress_quote 1
Bash version used: GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)

Parameter substitution with date

I've got a function that takes an optional date argument, and does calculations based on it. I'm trying to make sure that if that argument isn't provided, the current date/time is used as a default. I've tried this half a dozen ways, but I keep coming back to
${testDate:=$(date)}
Which works. The value of testDate is properly set, and the rest of the function works properly. However, I get an error message thrown with it:
Wed: command not found
(The date string is "Wed Mar 9 20:16:48 EST 2016" as of right now)
What am I doing wrong? Or is there a better way to do this?
Edit: To clarify, I'm emulating this, which works fine elsewhere in my script:
${menuTitle:=$"Choose from the Menu"}
How specifically is the line I posted at the top different from this one?
The typical approach in this situation is to use the null utility (builtin), :, as it both expands and ignores its arguments:
: "${testDate:=$(date)}" # double quotes not strictly required
While it won't make a difference in most scenarios, the double quotes eliminate unnecessary additional expansions, such as pathname expansion and word splitting.
Even though expansion ${testDate:=$(date)} expands to the effective value of $testDate - whether it had a previous nonempty value or was just initialized to the output from date - that value is ignored by :, resulting in the desired conditional initialization only.
Without :, with ${testDate:=$(date)} used as a statement by itself, Bash interprets whatever the expansion results in as a command, which in your case resulted in the attempt to execute the output from date as a command, which obviously failed (the first whitespace-separated token, Wed, was interpreted as the command name).
Note that the above is POSIX-compliant, so it therefore works not only in Bash, but in all major POSIX-compatible shells (bash, dash, ksh, zsh).
The problem is that this line:
${testDate:=$(date)}
not only assigns testDate a value but the variable is evaluated as well, and consequently executed as a command.
You can just do:
if [ -z "${testDate}" ]; then
testDate=$(date)
fi
Or, as a one-liner:
[ -z "${testDate}" ] && testDate=$(date)

Bash command groups: Why do curly braces require a semicolon?

I know the difference in purpose between parentheses () and curly braces {} when grouping commands in bash.
But why does the curly brace construct require a semicolon after the last command, whereas for the parentheses construct, the semicolon is optional?
$ while false; do ( echo "Hello"; echo "Goodbye"; ); done
$ while false; do ( echo "Hello"; echo "Goodbye" ); done
$ while false; do { echo "Hello"; echo "Goodbye"; }; done
$ while false; do { echo "Hello"; echo "Goodbye" }; done
bash: syntax error near unexpected token `done'
$
I'm looking for some insight as to why this is the case. I'm not looking for answers such as "because the documentation says so" or "because it was designed that way". I'd like to know why it was designed this is way. Or maybe if it is just a historical artifact?
This may be observed in at least the following versions of bash:
GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu)
GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin12)
GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu)
Because { and } are only recognized as special syntax if they are the first word in a command.
There are two important points here, both of which are found in the definitions section of the bash manual. First, is the list of metacharacters:
metacharacter
A character that, when unquoted, separates words. A metacharacter is a blank or one of the following characters: ‘|’, ‘&’, ‘;’, ‘(’, ‘)’, ‘<’, or ‘>’.
That list includes parentheses but not braces (neither curly nor square). Note that it is not a complete list of characters with special meaning to the shell, but it is a complete list of characters which separate tokens. So { and } do not separate tokens, and will only be considered tokens themselves if they are adjacent to a metacharacter, such as a space or a semi-colon.
Although braces are not metacharacters, they are treated specially by the shell in parameter expansion (eg. ${foo}) and brace expansion (eg. foo.{c,h}). Other than that, they are just normal characters. There is no problem with naming a file {ab}, for example, or }{, since those words do not conform to the syntax of either parameter expansion (which requires a $ before the {) or brace expansion (which requires at least one comma between { and }). For that matter, you could use { or } as a filename without ever having to quote the symbols. Similarly, you can call a file if, done or time without having to think about quoting the name.
These latter tokens are "reserved words":
reserved word
A word that has a special meaning to the shell. Most reserved words introduce shell flow control constructs, such as for and while.
The bash manual doesn't contain a complete list of reserved words, which is unfortunate, but they certainly include the Posix-designated:
! { }
case do done elif else
esac fi for if in
then until while
as well as the extensions implemented by bash (and some other shells):
[[ ]]
function select time
These words are not the same as built-ins (such as [), because they are actually part of the shell syntax. The built-ins could be implemented as functions or shell scripts, but reserved words cannot because they change the way that the shell parses the command line.
There is one very important feature of reserved words, which is not actually highlighted in the bash manual but is made very explicit in Posix (from which the above lists of reserved words were taken, except for time):
This recognition [as a reserved word] shall only occur when none of the characters is quoted and when the word is used as:
The first word of a command …
(The full list of places where reserved words is recognized is slightly longer, but the above is a pretty good summary.) In other words, reserved words are only reserved when they are the first word of a command. And, since { and } are reserved words, they are only special syntax if they are the first word in a command.
Example:
ls } # } is not a reserved word. It is an argument to `ls`
ls;} # } is a reserved word; `ls` has no arguments
There is lots more I could write about shell parsing, and bash parsing in particular, but it would rapidly get tedious. (For example, the rule about when # starts a comment and when it is just an ordinary character.) The approximate summary is: "don't try this at home"; really, the only thing which can parse shell commands is a shell. And don't try to make sense of it: it's just a random collection of arbitrary choices and historical anomalies, many but not all based on the need to not break ancient shell scripts with new features.

Why are my here-docs (<<-) giving me a syntax error?

methods() {
cat <<-!
start
stop
restart
reload
status
methods
!
}
Is this correct I am getting error
syntax error: unexpected end of file
For here-docs in ancient shells, you have to match the tag exactly. That means:
methods() {
cat <<!
start
stop
restart
reload
status
methods
!
}
Yes, at the start of the line, although you could do tricky things like cat <<'^I!' to set the marker to a single tab followed by !.
Now bash (and possibly earlier shells) fixed that with the <<- variant which strips off all leading tabs from your data lines and the end marker before processing. That way, you could still indent nicely:
methods() {
cat <<-!
start
stop
restart
reload
status
methods
!
}
But, note the proviso: it strips tabs, not whitespace in general. If you have spaces (or any non-tab character, printable or otherwise) anywhere before that ! character, it won't work.
If you're using vi, you can enter :set list to see the non-printable characters a bit better, otherwise xd or od -xcb can give you a hex dump of your file.

Resources