The accepted answer works well, but the idiom for a script re-invoking itself with sudo
on demand can be simplified and made more portable:
[[ $(id -u) -eq 0 ]] || exec sudo /bin/bash -c "$(printf '%q ' "$BASH_SOURCE" "$@")"
Using [[ ... ]]
instead of [ ... ]
makes prepending x
to the operands (or double-quoting the LHS) unnecessary.
Using bash -c
instead of su -c
to interpret the reconstructed command line makes the command more portable, because not all platforms support su -c
(e.g., macOS doesn't).
In bash
, $BASH_SOURCE
is generally the more reliable way to refer to the running script.
With the above approach, any variable references or command / arithmetic substitutions in the arguments are invariably expanded by the calling shell.
If you instead you wanted delayed expansion - so that variable references aren't expanded until the sudo
shell runs, in the context of the root user - use this:
(( __reinvoked )) || exec sudo -s __reinvoked=1 "$BASH_SOURCE" "$@"
Note that you'd then have to single-quote any arguments containing variable references or command substitutions for them to be delayed-expanded; e.g., '$USER'
.
Note the use of ad-hoc environment variable __reinvoked
to ensure re-invocation exactly once (even when initially already invoked as the root user).
Here's a sample script that demonstrates the first technique:
If not invoked as root, the script reinvokes itself with sudo -s
, passing all arguments through as-is.
Unless previously authenticated and still within the timeout period, sudo
will prompt for an administrator password.
#!/bin/bash
[[ $(id -u) -eq 0 ]] || exec sudo /bin/bash -c "$(printf '%q ' "$BASH_SOURCE" "$@")"
# Print the username and all arguments.
echo "Running as: $(id -un)"
echo "Arguments:"
for arg; do echo " $((++i)): [$arg]"; done
acfreitas's helpful answer demonstrates a "script-inside-a-script" technique where a here-document is used to provide shell code via stdin to sudo su
.
Again, sudo -s
is sufficient and quoting is important:
sudo -s -H <<'EOF'
echo "$HOME"
EOF
Note how the opening here-document delimiter, EOF
in this case, is quoted in order to prevent the contents of the document from up-front interpretation by the current shell.
If you didn't quote (any part of) EOF
, $HOME
would be expand to the current user's home directory.
If you want to mix up-front and delayed expansion, leave the opening here-document delimiter unquoted and selectively \
-quote $
instances:
sudo -s -H <<EOF
echo "Called by: $USER; root's home dir: \$HOME"
EOF