bash: improved 'sudo' command wrapper (#4080)

The previous approach to wrapping `sudo` had a few shortcomings:

1. We were (re)defining our 'sudo' function wrapper in the "precmd"
path. It only needs to be defined once in the shell session.
2. If there was an existing 'sudo' alias, the function definition would
conflict and result in a syntax error.

Fix (1) by hoisting the 'sudo' function into global scope. I also
considered only defining our wrapper if an executable `sudo` binary
could be found (e.g. `-x $(builtin command -v sudo)`, but let's keep the
existing behavior for now. This allows for a `sudo` command to be
installed later in the shell session and still be wrapped.

Address (2) by defining the wrapper function using `function sudo`
(instead of `sudo()`) syntax. An explicit function definition won't
clash with an existing 'sudo' alias, although the alias will continue to
take precedence (i.e. our wrapper won't be called). If the alias is
defined _after_ our 'sudo' function is defined, our function will call
the aliased command.

This ordering is relevant because it can result in different behaviors
depending on when a user defines their aliases relative to sourcing the
shell integration script. Our recommendation remains that users either
use automatic shell injection or manually source the shell integration
script _before_ other things in their `.bashrc`, so that aligns with the
expected behavior of the 'sudo' wrapper with regard to aliases. Given
that, I don't think we need any more explicit user-facing documentation
on this beyond the script-level comments.
This commit is contained in:
Mitchell Hashimoto
2024-12-30 08:44:47 -08:00
committed by GitHub

View File

@ -68,6 +68,34 @@ if [ -n "$GHOSTTY_BASH_INJECT" ]; then
builtin unset ghostty_bash_inject rcfile
fi
# Sudo
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_SUDO" != "1" && -n "$TERMINFO" ]]; then
# Wrap `sudo` command to ensure Ghostty terminfo is preserved.
#
# This approach supports wrapping a `sudo` alias, but the alias definition
# must come _after_ this function is defined. Otherwise, the alias expansion
# will take precedence over this function, and it won't be wrapped.
function sudo {
builtin local sudo_has_sudoedit_flags="no"
for arg in "$@"; do
# Check if argument is '-e' or '--edit' (sudoedit flags)
if [[ "$arg" == "-e" || $arg == "--edit" ]]; then
sudo_has_sudoedit_flags="yes"
builtin break
fi
# Check if argument is neither an option nor a key-value pair
if [[ "$arg" != -* && "$arg" != *=* ]]; then
builtin break
fi
done
if [[ "$sudo_has_sudoedit_flags" == "yes" ]]; then
builtin command sudo "$@";
else
builtin command sudo TERMINFO="$TERMINFO" "$@";
fi
}
fi
# Import bash-preexec, safe to do multiple times
builtin source "$GHOSTTY_RESOURCES_DIR/shell-integration/bash/bash-preexec.sh"
@ -109,31 +137,6 @@ function __ghostty_precmd() {
PS0=$PS0'\[\e[0 q\]'
fi
# Sudo
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_SUDO" != "1" ]] && [[ -n "$TERMINFO" ]]; then
# Wrap `sudo` command to ensure Ghostty terminfo is preserved
# shellcheck disable=SC2317
sudo() {
builtin local sudo_has_sudoedit_flags="no"
for arg in "$@"; do
# Check if argument is '-e' or '--edit' (sudoedit flags)
if [[ "$arg" == "-e" || $arg == "--edit" ]]; then
sudo_has_sudoedit_flags="yes"
builtin break
fi
# Check if argument is neither an option nor a key-value pair
if [[ "$arg" != -* && "$arg" != *=* ]]; then
builtin break
fi
done
if [[ "$sudo_has_sudoedit_flags" == "yes" ]]; then
builtin command sudo "$@";
else
builtin command sudo TERMINFO="$TERMINFO" "$@";
fi
}
fi
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_TITLE" != 1 ]]; then
# Command and working directory
# shellcheck disable=SC2016