mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
bash: drop automatic shell integration with --posix
'--posix' starts bash in POSIX mode (like /bin/sh). This is rarely used for interactive shells, and removing automatic shell integration support for this option allows us to simply/remove some exceptional code paths. Users are still able to manually source the shell integration script. Also fix an issue where we would still inject GHOSTTY_BASH_RCFILE if we aborted the automatic shell integration path _after_ seeing an --rcfile or --init-file argument.
This commit is contained in:
@ -29,52 +29,41 @@ if [ -n "$GHOSTTY_BASH_INJECT" ]; then
|
|||||||
# At this point, we're in POSIX mode and rely on the injected
|
# At this point, we're in POSIX mode and rely on the injected
|
||||||
# flags to guide is through the rest of the startup sequence.
|
# flags to guide is through the rest of the startup sequence.
|
||||||
|
|
||||||
# POSIX mode was requested by the user so there's nothing
|
# Restore bash's default 'posix' behavior. Also reset 'inherit_errexit',
|
||||||
# more to do that optionally source their original $ENV.
|
# which doesn't happen as part of the 'posix' reset.
|
||||||
# No other startup files are read, per the standard.
|
builtin set +o posix
|
||||||
if [[ "$ghostty_bash_inject" == *"--posix"* ]]; then
|
builtin shopt -u inherit_errexit 2>/dev/null
|
||||||
if [ -n "$GHOSTTY_BASH_ENV" ]; then
|
|
||||||
builtin source "$GHOSTTY_BASH_ENV"
|
# Unexport HISTFILE if it was set by the shell integration code.
|
||||||
builtin export ENV="$GHOSTTY_BASH_ENV"
|
if [[ -n "$GHOSTTY_BASH_UNEXPORT_HISTFILE" ]]; then
|
||||||
|
builtin export -n HISTFILE
|
||||||
|
builtin unset GHOSTTY_BASH_UNEXPORT_HISTFILE
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Manually source the startup files, respecting the injected flags like
|
||||||
|
# --norc and --noprofile that we parsed with the shell integration code.
|
||||||
|
#
|
||||||
|
# See also: run_startup_files() in shell.c in the Bash source code
|
||||||
|
if builtin shopt -q login_shell; then
|
||||||
|
if [[ $ghostty_bash_inject != *"--noprofile"* ]]; then
|
||||||
|
[ -r /etc/profile ] && builtin source "/etc/profile"
|
||||||
|
for rcfile in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do
|
||||||
|
[ -r "$rcfile" ] && { builtin source "$rcfile"; break; }
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
builtin unset GHOSTTY_BASH_ENV
|
|
||||||
else
|
else
|
||||||
# Restore bash's default 'posix' behavior. Also reset 'inherit_errexit',
|
if [[ $ghostty_bash_inject != *"--norc"* ]]; then
|
||||||
# which doesn't happen as part of the 'posix' reset.
|
# The location of the system bashrc is determined at bash build
|
||||||
builtin set +o posix
|
# time via -DSYS_BASHRC and can therefore vary across distros:
|
||||||
builtin shopt -u inherit_errexit 2>/dev/null
|
# Arch, Debian, Ubuntu use /etc/bash.bashrc
|
||||||
|
# Fedora uses /etc/bashrc sourced from ~/.bashrc instead of SYS_BASHRC
|
||||||
# Unexport HISTFILE if it was set by the shell integration code.
|
# Void Linux uses /etc/bash/bashrc
|
||||||
if [[ -n "$GHOSTTY_BASH_UNEXPORT_HISTFILE" ]]; then
|
# Nixos uses /etc/bashrc
|
||||||
builtin export -n HISTFILE
|
for rcfile in /etc/bash.bashrc /etc/bash/bashrc /etc/bashrc; do
|
||||||
builtin unset GHOSTTY_BASH_UNEXPORT_HISTFILE
|
[ -r "$rcfile" ] && { builtin source "$rcfile"; break; }
|
||||||
fi
|
done
|
||||||
|
if [[ -z "$GHOSTTY_BASH_RCFILE" ]]; then GHOSTTY_BASH_RCFILE="$HOME/.bashrc"; fi
|
||||||
# Manually source the startup files, respecting the injected flags like
|
[ -r "$GHOSTTY_BASH_RCFILE" ] && builtin source "$GHOSTTY_BASH_RCFILE"
|
||||||
# --norc and --noprofile that we parsed with the shell integration code.
|
|
||||||
#
|
|
||||||
# See also: run_startup_files() in shell.c in the Bash source code
|
|
||||||
if builtin shopt -q login_shell; then
|
|
||||||
if [[ $ghostty_bash_inject != *"--noprofile"* ]]; then
|
|
||||||
[ -r /etc/profile ] && builtin source "/etc/profile"
|
|
||||||
for rcfile in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do
|
|
||||||
[ -r "$rcfile" ] && { builtin source "$rcfile"; break; }
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [[ $ghostty_bash_inject != *"--norc"* ]]; then
|
|
||||||
# The location of the system bashrc is determined at bash build
|
|
||||||
# time via -DSYS_BASHRC and can therefore vary across distros:
|
|
||||||
# Arch, Debian, Ubuntu use /etc/bash.bashrc
|
|
||||||
# Fedora uses /etc/bashrc sourced from ~/.bashrc instead of SYS_BASHRC
|
|
||||||
# Void Linux uses /etc/bash/bashrc
|
|
||||||
# Nixos uses /etc/bashrc
|
|
||||||
for rcfile in /etc/bash.bashrc /etc/bash/bashrc /etc/bashrc; do
|
|
||||||
[ -r "$rcfile" ] && { builtin source "$rcfile"; break; }
|
|
||||||
done
|
|
||||||
if [[ -z "$GHOSTTY_BASH_RCFILE" ]]; then GHOSTTY_BASH_RCFILE="$HOME/.bashrc"; fi
|
|
||||||
[ -r "$GHOSTTY_BASH_RCFILE" ] && builtin source "$GHOSTTY_BASH_RCFILE"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -174,31 +174,36 @@ fn setupBash(
|
|||||||
try args.append("--posix");
|
try args.append("--posix");
|
||||||
|
|
||||||
// Stores the list of intercepted command line flags that will be passed
|
// Stores the list of intercepted command line flags that will be passed
|
||||||
// to our shell integration script: --posix --norc --noprofile
|
// to our shell integration script: --norc --noprofile
|
||||||
// We always include at least "1" so the script can differentiate between
|
// We always include at least "1" so the script can differentiate between
|
||||||
// being manually sourced or automatically injected (from here).
|
// being manually sourced or automatically injected (from here).
|
||||||
var inject = try std.BoundedArray(u8, 32).init(0);
|
var inject = try std.BoundedArray(u8, 32).init(0);
|
||||||
try inject.appendSlice("1");
|
try inject.appendSlice("1");
|
||||||
|
|
||||||
var posix = false;
|
// Walk through the rest of the given arguments. If we see an option that
|
||||||
|
// would require complex or unsupported integration behavior, we bail out
|
||||||
|
// and skip loading our shell integration. Users can still manually source
|
||||||
|
// the shell integration script.
|
||||||
|
//
|
||||||
|
// Unsupported options:
|
||||||
|
// -c -c is always non-interactive
|
||||||
|
// --posix POSIX mode (a la /bin/sh)
|
||||||
|
//
|
||||||
// Some additional cases we don't yet cover:
|
// Some additional cases we don't yet cover:
|
||||||
//
|
//
|
||||||
// - If additional file arguments are provided (after a `-` or `--` flag),
|
// - If additional file arguments are provided (after a `-` or `--` flag),
|
||||||
// and the `i` shell option isn't being explicitly set, we can assume a
|
// and the `i` shell option isn't being explicitly set, we can assume a
|
||||||
// non-interactive shell session and skip loading our shell integration.
|
// non-interactive shell session and skip loading our shell integration.
|
||||||
|
var rcfile: ?[]const u8 = null;
|
||||||
while (iter.next()) |arg| {
|
while (iter.next()) |arg| {
|
||||||
if (std.mem.eql(u8, arg, "--posix")) {
|
if (std.mem.eql(u8, arg, "--posix")) {
|
||||||
try inject.appendSlice(" --posix");
|
return null;
|
||||||
posix = true;
|
|
||||||
} else if (std.mem.eql(u8, arg, "--norc")) {
|
} else if (std.mem.eql(u8, arg, "--norc")) {
|
||||||
try inject.appendSlice(" --norc");
|
try inject.appendSlice(" --norc");
|
||||||
} else if (std.mem.eql(u8, arg, "--noprofile")) {
|
} else if (std.mem.eql(u8, arg, "--noprofile")) {
|
||||||
try inject.appendSlice(" --noprofile");
|
try inject.appendSlice(" --noprofile");
|
||||||
} else if (std.mem.eql(u8, arg, "--rcfile") or std.mem.eql(u8, arg, "--init-file")) {
|
} else if (std.mem.eql(u8, arg, "--rcfile") or std.mem.eql(u8, arg, "--init-file")) {
|
||||||
if (iter.next()) |rcfile| {
|
rcfile = iter.next();
|
||||||
try env.put("GHOSTTY_BASH_RCFILE", rcfile);
|
|
||||||
}
|
|
||||||
} else if (arg.len > 1 and arg[0] == '-' and arg[1] != '-') {
|
} else if (arg.len > 1 and arg[0] == '-' and arg[1] != '-') {
|
||||||
// '-c command' is always non-interactive
|
// '-c command' is always non-interactive
|
||||||
if (std.mem.indexOfScalar(u8, arg, 'c') != null) {
|
if (std.mem.indexOfScalar(u8, arg, 'c') != null) {
|
||||||
@ -210,10 +215,13 @@ fn setupBash(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try env.put("GHOSTTY_BASH_INJECT", inject.slice());
|
try env.put("GHOSTTY_BASH_INJECT", inject.slice());
|
||||||
|
if (rcfile) |v| {
|
||||||
|
try env.put("GHOSTTY_BASH_RCFILE", v);
|
||||||
|
}
|
||||||
|
|
||||||
// In POSIX mode, HISTFILE defaults to ~/.sh_history, so unless we're
|
// In POSIX mode, HISTFILE defaults to ~/.sh_history, so unless we're
|
||||||
// staying in POSIX mode (--posix), change it back to ~/.bash_history.
|
// staying in POSIX mode (--posix), change it back to ~/.bash_history.
|
||||||
if (!posix and env.get("HISTFILE") == null) {
|
if (env.get("HISTFILE") == null) {
|
||||||
var home_buf: [1024]u8 = undefined;
|
var home_buf: [1024]u8 = undefined;
|
||||||
if (try homedir.home(&home_buf)) |home| {
|
if (try homedir.home(&home_buf)) |home| {
|
||||||
var histfile_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var histfile_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
@ -227,13 +235,6 @@ fn setupBash(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve the existing ENV value when staying in POSIX mode (--posix).
|
|
||||||
if (env.get("ENV")) |old| {
|
|
||||||
if (posix) {
|
|
||||||
try env.put("GHOSTTY_BASH_ENV", old);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set our new ENV to point to our integration script.
|
// Set our new ENV to point to our integration script.
|
||||||
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
const integ_dir = try std.fmt.bufPrint(
|
const integ_dir = try std.fmt.bufPrint(
|
||||||
@ -262,21 +263,32 @@ test "bash" {
|
|||||||
try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?);
|
try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "bash: inject flags" {
|
test "bash: unsupported options" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
// bash --posix
|
const cmdlines = [_][]const u8{
|
||||||
{
|
"bash --posix",
|
||||||
|
"bash --rcfile script.sh --posix",
|
||||||
|
"bash --init-file script.sh --posix",
|
||||||
|
"bash -c script.sh",
|
||||||
|
"bash -ic script.sh",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (cmdlines) |cmdline| {
|
||||||
var env = EnvMap.init(alloc);
|
var env = EnvMap.init(alloc);
|
||||||
defer env.deinit();
|
defer env.deinit();
|
||||||
|
|
||||||
const command = try setupBash(alloc, "bash --posix", ".", &env);
|
try testing.expect(try setupBash(alloc, cmdline, ".", &env) == null);
|
||||||
defer if (command) |c| alloc.free(c);
|
try testing.expect(env.get("GHOSTTY_BASH_INJECT") == null);
|
||||||
|
try testing.expect(env.get("GHOSTTY_BASH_RCFILE") == null);
|
||||||
try testing.expectEqualStrings("bash --posix", command.?);
|
try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null);
|
||||||
try testing.expectEqualStrings("1 --posix", env.get("GHOSTTY_BASH_INJECT").?);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bash: inject flags" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
// bash --norc
|
// bash --norc
|
||||||
{
|
{
|
||||||
@ -329,17 +341,6 @@ test "bash: rcfile" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "bash: -c command" {
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
|
|
||||||
var env = EnvMap.init(alloc);
|
|
||||||
defer env.deinit();
|
|
||||||
|
|
||||||
try testing.expect(try setupBash(alloc, "bash -c script.sh", ".", &env) == null);
|
|
||||||
try testing.expect(try setupBash(alloc, "bash -ic script.sh", ".", &env) == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "bash: HISTFILE" {
|
test "bash: HISTFILE" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -369,68 +370,6 @@ test "bash: HISTFILE" {
|
|||||||
try testing.expectEqualStrings("my_history", env.get("HISTFILE").?);
|
try testing.expectEqualStrings("my_history", env.get("HISTFILE").?);
|
||||||
try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null);
|
try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HISTFILE unset (POSIX mode)
|
|
||||||
{
|
|
||||||
var env = EnvMap.init(alloc);
|
|
||||||
defer env.deinit();
|
|
||||||
|
|
||||||
const command = try setupBash(alloc, "bash --posix", ".", &env);
|
|
||||||
defer if (command) |c| alloc.free(c);
|
|
||||||
|
|
||||||
try testing.expect(env.get("HISTFILE") == null);
|
|
||||||
try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HISTFILE set (POSIX mode)
|
|
||||||
{
|
|
||||||
var env = EnvMap.init(alloc);
|
|
||||||
defer env.deinit();
|
|
||||||
|
|
||||||
try env.put("HISTFILE", "my_history");
|
|
||||||
|
|
||||||
const command = try setupBash(alloc, "bash --posix", ".", &env);
|
|
||||||
defer if (command) |c| alloc.free(c);
|
|
||||||
|
|
||||||
try testing.expectEqualStrings("my_history", env.get("HISTFILE").?);
|
|
||||||
try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "bash: preserve ENV" {
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
|
|
||||||
var env = EnvMap.init(alloc);
|
|
||||||
defer env.deinit();
|
|
||||||
|
|
||||||
const original_env = "original-env.bash";
|
|
||||||
|
|
||||||
// POSIX mode
|
|
||||||
{
|
|
||||||
try env.put("ENV", original_env);
|
|
||||||
const command = try setupBash(alloc, "bash --posix", ".", &env);
|
|
||||||
defer if (command) |c| alloc.free(c);
|
|
||||||
|
|
||||||
try testing.expect(std.mem.indexOf(u8, command.?, "--posix") != null);
|
|
||||||
try testing.expect(std.mem.indexOf(u8, env.get("GHOSTTY_BASH_INJECT").?, "posix") != null);
|
|
||||||
try testing.expectEqualStrings(original_env, env.get("GHOSTTY_BASH_ENV").?);
|
|
||||||
try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?);
|
|
||||||
}
|
|
||||||
|
|
||||||
env.remove("GHOSTTY_BASH_ENV");
|
|
||||||
|
|
||||||
// Not POSIX mode
|
|
||||||
{
|
|
||||||
try env.put("ENV", original_env);
|
|
||||||
const command = try setupBash(alloc, "bash", ".", &env);
|
|
||||||
defer if (command) |c| alloc.free(c);
|
|
||||||
|
|
||||||
try testing.expect(std.mem.indexOf(u8, command.?, "--posix") != null);
|
|
||||||
try testing.expect(std.mem.indexOf(u8, env.get("GHOSTTY_BASH_INJECT").?, "posix") == null);
|
|
||||||
try testing.expect(env.get("GHOSTTY_BASH_ENV") == null);
|
|
||||||
try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setup automatic shell integration for shells that include
|
/// Setup automatic shell integration for shells that include
|
||||||
|
Reference in New Issue
Block a user