Home > OS >  call a bash function as root but gives 'unexpected end of file'
call a bash function as root but gives 'unexpected end of file'

Time:10-03

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.

  • Related