Bash – difference between /bin/bash and “.”

bashshell

Env: Ubuntu 14.04

I have a file called bc on my home folder. The whole content of the file is

func() {
    local a b=()
    echo $0
}
func

Note there's no #!/bin/bash at the beginning.

Then, if I do /bin/bash ~/bc I get

/home/dev/bc

as expected.

However, if I source it instead . ~/bc I get the following error:

bash: /home/dev/bc: line 2: syntax error near unexpected token `('
bash: /home/dev/bc: line 2: `    local a b=()'

I am assuming a different shell is executed for sourcing. If that is the case, then how can I change it?

If I do a chsh I get

dev@c1:~$ sudo chsh
[sudo] password for dev: 
Changing the login shell for root
Enter the new value, or press ENTER for the default
    Login Shell [/bin/bash]: 

—— from comments ——–

echo $SHELL returns /bin/bash

Best Answer

The problem is almost definitely an alias. When you do:

/bin/bash somefile

You load a new shell. You get a fresh start. That executable isn't saddled with all of the stuff you put in your initialization files - rcs - because those are only sourced by default for interactive shells, which is not to mention any additional logic accrued since.

Aliases are weird. Aliases are parser expansions - they're the very first kind of expandable object most shells support, and they are expanded in ways many don't expect. bash, in fact, discourages their use - for whatever reason - by disabling alias expansions by default in any non-interactive shell. And so if an alias causes that error - and I'm pretty sure that's the problem - then even sourcing your environment files would likely still not elicit the same error output when you /bin/bash, anyway.

When I say aliases are weird it is because they are expanded on a different level than other types of shell expansions. Probably this is most keenly obvious in a function definition context; wherein no shell expansions occur - as those are reserved for the function's later execution - except aliases without twice-evaluating the expression. And aliases are never expanded during function execution (again, short of eval), because they were already expanded during its definition.

Here's an example:

alias a='echo not a;b'
b(){ echo a\?; }; a(){ echo le sigh...; }
a; b

...prints...

not a
not a
le sigh...
le sigh...

The first time the alias named a is expanded is when I do:

a(){...

This is possible because the () parens are shell tokens - not reserved words like {} - and so they can delimit a word without whitespace - which makes them particularly useful for things like function and array declarations as the parser performs macro expansion to arrive at an executable statement. The result of the above expansion is...

}; echo not a;b(){ 

The last time it is expanded transforms:

a; b

...into...

echo not a;b; b

You see, typical shell expansions occur in a pre-delimited context - they are bounded on all sides by control operators like:

\n[]{}|&();<>\'"`

In this way most shell expansions serve as a quoting mechanism in and of themselves - quotes expanded out of a variable do not recursively delimit further quoted contexts because the expansion quoted them. Alias expansion occurs before this, though - aliases are expanded if recognized by the parser as it scans the first shell word in a simple command - before it scans in the rest of it. So if the expansion results in more than a single simple command, well... then so be it.

My assumption is you have an alias declared for func - that would be the most simple explanation - but because aliases can chain if they're defined with a trailing space - and will also be expanded within a function definition - there's no way of knowing for certain given only the information you provide. I think I can reproduce your error, though. This is close:

alias func='func('
func(){
    local a b=()
    echo $0
}
func

...which prints...

bash: syntax error near unexpected token `('
bash: local: can only be used in a function
bash
bash: syntax error near unexpected token `}'
bash: syntax error near unexpected token `newline'

So just do:

alias func local a b echo

And let us know.

Related Question