Why does the last example throw an error but the others work? Bash is invoked in any case.
#!/bin/bash
function hello {
echo "Hello! user=$USER, uid=$UID, home=$HOME";
}
# Test that it works.
hello
# ok
bash -c "$(declare -f hello); hello"
# ok
sudo su $USER bash -c "$(declare -f hello); hello"
# error: bash: -c: line 1: syntax error: unexpected end of file
sudo -i -u $USER bash -c "$(declare -f hello); hello"
CodePudding user response:
It fail because of the -i or --login switch:
It seems like when debugging with -x
$ set -x
$ sudo -i -u $USER bash -c "$(declare -f hello); hello"
declare -f hello
sudo -i -u lea bash -c 'hello ()
{
echo "Hello! user=$USER, uid=$UID, home=$HOME"
}; hello'
Now if doing it manually it cause the same error:
sudo -i -u lea bash -c 'hello ()
{
echo "Hello! user=$USER, uid=$UID, home=$HOME"
}; hello'
Now lets just do a simple tiny change that make it work:
sudo -i -u lea bash -c 'hello ()
{
echo "Hello! user=$USER, uid=$UID, home=$HOME";}; hello'
The reason is that sudo -i runs everything like an interactive shell. And when doing so, every newline character from the declare -f hello is internally turned into space. The curly-brace code block need a semi-colon before the closing curly-brace when on the same line, which declare -f funcname does not provide since it expands the function source with closing curly brace at a new line.
Now lets make this behaviour very straightforward:
$ sudo bash -c 'echo hello
echo world'
hello
world
It executes both echo statements because they are separated by a newline.
but:
$ sudo -i bash -c 'echo hello
echo world'
hello echo world
It executes the first echo statement that prints everything as arguments because the newline has been replaced by a space.
CodePudding user response:
It is the same code in all examples, so it should be ok.
Yes, "$(declare -f hello); hello" is always the same string. But it is processed differently by sudo su and sudo -i as found out by Lea Gris . Please upvote her answer. I kept my answer because of the side notes at the end.
sudo -i quotes its arguments before passing them to bash. This quoting process seems to be undocumented and very poor. To see what was actually executed, you can print the argument passed to bash -c inside your ~/.bash_profile/:
Content of ~/.bash_profile
cat <<EOF
# is executed as
$BASH_EXECUTION_STRING
# resulting in output
EOF
Some examples of sudo -i's terrible and inconsistent quoting
Linebreaks are replaced by line continuations
$ sudo -u $USER -i echo '1
2'
# is executed as
echo 1\
2
# resulting in output
12
Quotes are escaped as literals
$ sudo -u $USER -i echo \'single\' \"double\"
# is executed as
echo \'single\' \"double\"
# resulting in output
'single' "double"
But $ is not quoted
$ sudo -u $USER -i echo \$var
# is executed as
echo $var
# resulting in output
There might be a misunderstanding in your usage of su.
sudo su $USER bash -c "some command"
does not execute bash -c "echo 1; echo 2". The -c ... is interpreted by su and passed as -c ... to $USER's default shell. Afterwards, the remaining arguments are passed to that shell as well. The executed command is
defaultShellOfUSER -c "some command" bash
You probably wanted to write
sudo su -s bash -c "some command" "$USER"
Interactive shells behave differently
su just executes the command specified by -c. But sudo -i starts a login shell, in your case that login shell seems to be bash (this is not necessarily the case, see section above).
An interactive bash session behaves different from bash -c "..." or bash script.sh. An interactive bash sources files like .profile, .bash_profile, and enables history expansion, aliases, and so on. For a full list see the section Interactive Shell Behavior in bash's manual.
