Bashrc not able to chain commands anymore using && or ;

You guys are my last resort.

I've got a bashrc file with all kinds of commands in it. A lot of them are variations to this

alias test='
    test1 && test2
'
alias test1='
    echo "i hope" && \
    echo "this works"
'
alias test2='
    echo "because" && \
    echo "it annoys the hell out of me"
'

This used to work up until a few days ago. Since then I've been getting the following errors

$ test
i hope
this works
bash: syntax error near unexpected token `&&'

No matter if I use && or ;. The commands test1 and test2 work as expected seperatly, but I can't chain them anymore. My shell is in bash and I've had several colleagues look at it, but no one has any idea what's wrong.

$ echo $SHELL
/bin/bash

Am I missing something? It's just weird that all of this used to work but all of a sudden it doesn't anymore.


An alias replaces text with text. There is no logic, there's only text replacement. In your case consider using shell functions instead. Please see In Bash, when to alias, when to script, and when to write a function?

After Bash expands your test, it expands test1 and test2. No problem here, such multi-level expansion is allowed. But because the replacement is purely textual, all characters matter. Characters that seem harmless in one definition may change the logic of the final outcome, because it's the final outcome what is interpreted eventually.

Your test expands to:


    test1 && test2

and then it expands to:


    
    echo "i hope" && \
    echo "this works"
 && …

where denotes what test2 expanded to (irrelevant to the issue). Only then the outcome is interpreted.

The newline after "it works" is the final character from the expansion of test1. It is unescaped, it terminates the preceding command. && in the next line is unexpected.

It would be different if the troublesome newline wasn't there:

alias test1='
    echo "i hope" && \
    echo "this works"'

or if it was escaped:

alias test1='
    echo "i hope" && \
    echo "this works"\
'

I believe I explained the problem and "fixed" it. Nevertheless I think the Right Thing is not to use aliases this way. Aliases using aliases to build some logic should be replaced by functions calling functions. Aliases are not for building logic.

If test1 and test2 were properly working shell functions (I mean each working on its own), then the expression test1 && test2 would work as expected, regardless of newlines in the definition of test1. This would work because the logic of the expression would be determined before test1 and (optionally) test2 are executed. In case of aliases test1 and test2 are expanded before the shell tries to determine the logic and (as we could see) they can affect the logic.

It's not only about newlines. Execute the following example:

alias foo='echo 1; echo 2'
false && foo

If you don't know foo is an alias (or if you do, but you don't know how aliasing works) then you will expect foo in false && foo to be omitted as a whole. No, echo 2 will be executed. This is because the final command is false && echo 1; echo 2 and its logic is different than what you expected.

An "equivalent" shell function behaves better. The following snippet won't execute foo at all, exactly as you expect from false && foo:

unalias foo
foo() { echo 1; echo 2; }
false && foo

For me, understanding that an alias dumbly replaces text with text was a turning point. All my troubles with aliases stemmed from not realizing this basic fact.


Additionally note test is a standard command. You shouldn't create an alias or a function named test, unless your goal is to deliberately replace the standard test.