In Bash I run:
alias myalias='echo foo
echo bar
echo baz'
myalias
which returns:
foo
bar
baz
But:
ssh localhost "shopt -s expand_aliases &>/dev/null;
alias myalias='echo foo
echo bar
echo baz'
myalias"
Returns:
foo
Why?
I think you found a bug of Bash. This bug is specific to option -c
.
Remote running has nothing to do with your problem about the multi-line alias. You can try it in your local bash. But not in bash script or interactive bash, try it with -c
option, like this
bash -c "shopt -s expand_aliases &>/dev/null;
alias myalias='echo foo
echo bar
echo baz'
myalias"
Same output as your problem. Only foo
is printed.
To get the right (expected) output, you have to at least add one more line after myalias
, as @cuonglm suggested.
bash -c "shopt -s expand_aliases &>/dev/null;
alias myalias='echo foo
echo bar
echo baz'
myalias
:"
Why would it happen this way? Why does one more line after myalias
help?
I just want to say that this doesn't make sense. No document in Bash explains or mentions this case, not a little bit. It is not supposed to run this way. This is a bug. After reading code, you'll make sure of this point.
Go back to the first problematic command. This time don't change anything, just re-compile bash with "ONESHOT" undefined, then you'll get right (expected) output. Yes, you hear right, the command has two different behaviors just because of different compile-time config.
Whether define ONESHOT
or not will lead to two completely different route in Bash code for -c "command"
. If undefine ONESHOT, -c "command"
will run the normal code route, which is the code route for almost all bash executions, such as interactive command and bash script. But if define ONESHOT, -c "command"
will run another particular route which is specially designed for it only, to improve its performance by avoiding fork.
For this case, the normal and mostly used way can give right output, while the particular way can't. I think the inconsistent behavior is not what the Bash authors want. As to which behavior is right, I tend to think the normal way is right.
The following piece of code is related to the bug. It is from function parse_and_execute() in file builtins/evalstring.c
while (*(bash_input.location.string))
{
...
}
This while
loop will run by lines, handling one line in one loop.
After read myalias
, the last line, in the command (see above), the condition in while
will become false. myalias
is expanded to three lines of echo, but only one echo is handled in this loop; the two other echo will be handled in next loop, but... there is not another loop.
If you add one more line after myalias
, after read myalias
, the condition in while
will remain true, so the two other echo will get chance to run in next loop. The last line after myalias
will be handled after all echos expanded by myalias
are handled.
UPDATE
I forgot to say the version of Bash involved in this issue, which is
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
Workaround (inspired by @cuonglm):
ssh localhost "shopt -s expand_aliases &>/dev/null;
alias myalias='ls
echo foo
echo bar
echo baz'
myalias &&
true"
This will preserve exit code. The true
, however, must be on a new line.
It still does not explain why. But it more and more looks like a bug.
echo
after runningmyalias
, the it works. See the debug3 log in both server and client, you will see that serverread
failed so it signal eof to client. I'm not sure why running alias alone cause it. Maybe the server see there's no command aftermyalias
so it signal eof right after callingmyalias
.