Is a conditional function definition allowed in POSIX compliant shell scripts? - shell

In Bash and KornShell (ksh), I see the following script works fine.
if [ -n "foo" ]
then
foo()
{
echo function foo is defined
}
else
bar()
{
echo function bar is defined
}
fi
foo
bar
It also generates the expected output when executed.
$ bash scr.sh
function foo is defined
scr.sh: line 15: bar: command not found
$ ksh scr.sh
function foo is defined
scr.sh: line 15: bar: not found
I want to know if this script would run and generate this output on any POSIX conformant shell.

I agree with your reading of the grammar. A function definition may occur in the body of an if statement, making its execution conditional.

Related

Exporting a function with a nested function definition and heredoc in bash

I want to pass a segment of a script to a subshell, so I export it as a function. When I was trying this, I happened to notice I cannot export a function containing a nested function that ends with heredoc.
For example:
#!/bin/bash
f() {
g() {
cat <<EOF
EOF
}
g
}
export -f f
bash -c ':'
If I run it, the subshell fails and prints:
bash: f: line 8: syntax error: unexpected end of file
bash: error importing function definition for `f'
What's wrong with my script?
To get exported functions into subshells Bash serializes them as strings and saves them in plain name=value environment variables. The subshell recognizes the environment variables as containing functions and parses them as such. This is supposed to be an invisible mechanism but it's complicated enough that there are sometimes bugs (famously, Shellshock).
In Bash 5.0, bash -c env shows that the function is serialized into an environment variable as:
BASH_FUNC_f%%=() { function g ()
{
cat <<EOF
}
EOF
g
}
Notice the placement of <<EOF. Having the curly brace inside the heredoc is wrong and the source of the syntax error.
It appears to be a regression in Bash 5.0. In Bash 4.4 and 4.2 it's serialized as this, which works without issue:
BASH_FUNC_f()=() { function g ()
{
cat
} <<EOF
EOF
g
}
I don't see any relevant bug reports at https://savannah.gnu.org/support/?group=bash, nor https://bugs.debian.org/cgi-bin/pkgreport.cgi?pkg=bash;dist=unstable. Perhaps you could submit one?
Other observations:
declare -fp has the same syntax error. The bug probably originates there, not in the environment variable export code.
$ declare -pf f
f ()
{
function g ()
{
cat <<EOF
}
EOF
g
}
I don't see any way to simplify your test case. Well done!
Removing the call to g fixes the error:
f ()
{
function g ()
{
cat <<EOF
EOF
}
}
So does changing the nested declaration to a simple curly brace block:
f ()
{
{
cat <<EOF
EOF
}
g
}

Difference between bash (shell) function syntaxes? [duplicate]

For example:
Bash-Prog-Intro-HOWTO
function foo() {}
I make search queries in info bash and look in releted chapters of POSIX for function keyword but nothing found.
What is function keyword used in some bash scripts? Is that some deprecated syntax?
The function keyword is optional when defining a function in Bash, as documented in the manual:
Functions are declared using this syntax:
name () compound-command [ redirections ]
or
function name [()] compound-command [ redirections ]
The first form of the syntax is generally preferred because it's compatible with Bourne/Korn/POSIX scripts and so more portable.
That said, sometimes you might want to use the function keyword to prevent Bash aliases from colliding with your function's name. Consider this example:
$ alias foo="echo hi"
$ foo() { :; }
bash: syntax error near unexpected token `('
Here, 'foo' is replaced by the text of the alias of the same name because it's the first word of the command. With function the alias is not expanded:
$ function foo() { :; }
The function keyword is necessary in rare cases when the function name is also an alias. Without it, Bash expands the alias before parsing the function definition -- probably not what you want:
alias mycd=cd
mycd() { cd; ls; } # Alias expansion turns this into cd() { cd; ls; }
mycd # Fails. bash: mycd: command not found
cd # Uh oh, infinite recursion.
With the function keyword, things work as intended:
alias mycd=cd
function mycd() { cd; ls; } # Defines a function named mycd, as expected.
cd # OK, goes to $HOME.
mycd # OK, goes to $HOME.
\mycd # OK, goes to $HOME, lists directory contents.
The reserved word function is optional. See the section 'Shell Function Definitions' in the bash man page.

Bash. Inherit functions scopes

уI need a global counter and function which returns numbers one by one. For example I want this script to echo 6,7,8 (but it echo 6,6,6):
#!/bin/bash
port_counter=5
function get_free_port {
port_counter=$((port_counter + 1))
echo ${port_counter}
}
function foo {
echo $(get_free_port)
}
foo
foo
(foo;)&
How can I obtain 6,7,8?
UPDATE:
Ok, after chepner's answer I need to specify a little my question.
If I need to use get_free_port as variable in foo, I can't use this approach, isn't it?
So I can't write
function foo {
variable=get_free_port # variable=$(get_free_port) was ok, but returns 6,6,6
echo ${variable}
}
Also foo & - like usages is hardly desirable
You can't modify variables from a subprocess (which is what $(...) runs). You don't need one in this case:
function foo {
get_free_port
}
However, for the same reason, you cannot call foo from a subshell or background job, either. Neither foo &, (foo), nor (foo)& will update the value of port_counter in the current shell.
If you really need to call get_free_port and capture its output, you'll need to use a temporary file. For example:
foo () {
get_free_port > some_temp_file
cat some_temp_file
}
If this is not suitable, you may need to rethink your script's design.
The below code would give you the desired behavior:
#!/bin/bash
port_counter=5
function get_free_port {
port_counter=$(( port_counter + 1 ))
echo ${port_counter}
}
function foo {
get_free_port
# $(get_free_port) spawns a subshell and the parent shell variables are not
# available in the subshell.
}
foo #runs fine
foo #runs fine
foo #(foo;)& again spawns a subshell and the parent shell pariables are not available here.

Why bash functions use round brackets, if those are never filled with arguments?

The function definition syntax for various languages is:
C (the godfather of all scripting languages):
func_type myfunc_c (arg_type arg_name , ...)
{
/* arguments explicitly specified */
}
TCL:
proc myfunc_tcl {arg1 arg2 args} {
# arguments explicitly specified
}
Perl:
sub myfunc_perl {
# no arguments explicitly specified && no round brackets used
}
Python:
def myfunc_python(arg1, arg2):
# arguments explicitly specified
Bash:
function myfunc_bash () {
# arguments NEVER explicitly specified
# WHY using round brackets?
}
Why using round brackets in bash?
Parentheses are optional. From Bash Reference Manual --> 3.3 Shell Functions:
Functions are declared using this syntax:
name () compound-command [ redirections ]
or
function name [()] compound-command [ redirections ]
This defines a shell function named name. The reserved word function
is optional. If the function reserved word is supplied, the
parentheses are optional. The body of the function is the compound
command compound-command (see Compound Commands). That command is
usually a list enclosed between { and }, but may be any compound
command listed above. compound-command is executed whenever name is
specified as the name of a command. When the shell is in POSIX mode
(see Bash POSIX Mode), name may not be the same as one of the special
builtins (see Special Builtins). Any redirections (see Redirections)
associated with the shell function are performed when the function is
executed.
So these are equivalent:
function hello {
echo "hello there"
}
hello () {
echo "hello there"
}
In Bash, functions can access global variables normally, so that the approach is slightly different from other languages. Normally, there is no need to use return because there is no value to catch.
See an example. Here, we have a global variable myvar containing a value. In the functions mytest and mytest_inner we are changing its value. However, in one case the value affects the global environment, whereas in the other does not.
In mytest we change the value and it affects the main block. In mytest_inner we do the same, but the value is just changed locally, in the sub-shell running in the function.
#!/bin/bash
function mytest {
echo "mytest -> myvar: $myvar"
((myvar++))
}
function mytest_inner () {
(
echo "mytest_inner -> myvar: $myvar"
((myvar++))
)
}
myvar=$1
mytest
echo "main -> myvar: $myvar"
mytest_inner
echo "main -> myvar: $myvar"
Let's run it:
$ ./myscript.sh 20
mytest -> myvar: 20
main -> myvar: 21
mytest_inner -> myvar: 21
main -> myvar: 21
Why using round brackets in bash?
Actually, they're not needed, at least not in my version.
$ foo() { echo 'foo!' ; }
$ foo
foo!
$ function bar { echo 'bar!' ; }
$ bar
bar!
$ function baz() { echo 'baz!' ; }
$ baz
baz!
$ bash --version | head -n 1
GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu)
man bash:
Shell Function Definitions
A shell function is an object that is called like a simple command and executes a compound
command with a new set of positional parameters. Shell functions are declared as follows:
name () compound-command [redirection]
function name [()] compound-command [redirection]
This defines a function named name. The reserved word function is optional. If
the function reserved word is supplied, the parentheses are optional. The body of
the function is the compound command compound-command (see Compound Commands
above). That command is usually a list of commands between { and }, but may be any
command listed under Compound Commands above. compound-command is executed when-
ever name is specified as the name of a simple command. Any redirections (see RE-
DIRECTION below) specified when a function is defined are performed when the func-
tion is executed. The exit status of a function definition is zero unless a syntax
error occurs or a readonly function with the same name already exists. When exe-
cuted, the exit status of a function is the exit status of the last command exe-
cuted in the body. (See FUNCTIONS below.)

Get function name in KornShell script

I'd like to get the function name from within the function, for logging purposes.
KornShell (ksh) function:
foo ()
{
echo "get_function_name some useful output"
}
Is there anything similar to $0, which returns the script name within scripts, but which instead provides a function's name?
If you define the function with the function keyword, then $0 is the function name:
$ function foo {
> echo "$0"
> }
$ foo
foo
(Tested in pdksh.)
[...] what are the main pros/cons of using keyword function?
Main pro is that "typeset myvar=abc" inside the function is now a local variable, with no possible side effects outside the function. This makes KSH noticeably safer for large shell scripts. Main con is, perhaps, the non-POSIX syntax.
Use the ksh "function foo ..." form:
$ cat foo1
#!/bin/ksh
foo3() { echo "\$0=$0"; }
function foo2 { echo "\$0=$0"; }
foo2
foo3
$ ./foo1
$0=foo2
$0=./foo1
The function below seems to get its name in both Bash and ksh:
# ksh or bash
function foo {
local myname="${FUNCNAME[0]:-$0}"
echo "$myname"
}
# test
foo
# ...

Resources