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.