From 545d90a3824a4362c115b564de4ce0654eba3442 Mon Sep 17 00:00:00 2001 From: secdoc <55542561+secdoc@users.noreply.github.com> Date: Sun, 30 Aug 2026 20:31:47 +0000 Subject: [PATCH] [verified] fix: load interactive shell enhancements reliably Add portable batcat and bat aliases, ensure Bash login profiles source .bashrc on Linux and macOS, verify the installed alias, and cover the behavior with CI-backed integration tests. --- .bashrc | 9 +++ .github/workflows/bashtest.yml | 6 ++ README.md | 6 +- setup.sh | 59 +++++++++++++----- tests/test_shell_integration.sh | 107 ++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 16 deletions(-) create mode 100755 tests/test_shell_integration.sh diff --git a/.bashrc b/.bashrc index c2a2883..e191d2a 100644 --- a/.bashrc +++ b/.bashrc @@ -217,6 +217,15 @@ alias ping='ping -c 10' alias less='less -R' alias cls='clear' +# Debian-family bat packages expose batcat; other platforms normally expose bat. +# Keep paging disabled, but retain bat's header, grid, and line numbers so plain +# text files are visibly rendered by bat in an interactive terminal. +if command -v batcat >/dev/null 2>&1; then + alias cat='batcat --paging=never --style=full' +elif command -v bat >/dev/null 2>&1; then + alias cat='bat --paging=never --style=full' +fi + if command -v trash-put >/dev/null 2>&1; then alias rm='trash-put' elif command -v trash >/dev/null 2>&1; then diff --git a/.github/workflows/bashtest.yml b/.github/workflows/bashtest.yml index feb23a9..666a58b 100644 --- a/.github/workflows/bashtest.yml +++ b/.github/workflows/bashtest.yml @@ -26,6 +26,9 @@ jobs: if: steps.cache-shellcheck.outputs.cache-hit != 'true' run: sudo apt-get install -y shellcheck + - name: Install rendering test dependency + run: sudo apt-get install -y bat + - name: Lint Bash scripts run: | echo "Running ShellCheck..." @@ -36,3 +39,6 @@ jobs: else echo "No linting errors found." fi + + - name: Test shell integration + run: tests/test_shell_integration.sh diff --git a/README.md b/README.md index f906cfe..b815d25 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,14 @@ The `setup.sh` script automates the installation process by: - Installing Homebrew on macOS if it is not already installed - Installing Bash 5 with Homebrew on macOS - Adding Homebrew Bash to `/etc/shells` and setting it as the default login shell on macOS -- Installing dependencies (bash-completion, neovim, starship, fzf, zoxide) +- Installing dependencies (bash-completion, bat, neovim, starship, fzf, zoxide) - Installing Starship and JetBrainsMono Nerd Font on Linux - Selecting JetBrainsMono Nerd Font in Ptyxis or GNOME Terminal when available - Installing the MesloLGS Nerd Font required for the prompt on macOS when available - Linking configuration files from `~/.local/share/mybash` to your home directory - Linking the fastfetch config to `~/.config/fastfetch/config.jsonc` - Ensuring `~/.bash_profile` initializes Homebrew on macOS -- Ensuring `~/.bash_profile` sources `~/.bashrc` on macOS +- Ensuring the active Bash login profile sources `~/.bashrc` on Linux and macOS - Setting up additional utilities like `fastfetch` On macOS, `setup.sh` may prompt for your password when it adds Homebrew Bash to `/etc/shells` and changes your default shell. Restart Terminal after installation, then verify with: @@ -103,6 +103,7 @@ The `.bashrc` file defines aliases, functions, and environment variables to enha - **Aliases**: Shortcuts for common commands (e.g., `alias cp='cp -i'`) - **Functions**: Custom functions for tasks like extracting archives and copying files with progress +- **Enhanced `cat` output**: Interactive shells use `batcat --paging=never --style=full` on Debian-family systems, or `bat` with the same options elsewhere. This preserves bat's header, grid, line numbers, and terminal colors. Non-interactive scripts keep the normal `cat`; use `command cat` to bypass the alias interactively. ### `starship.toml` @@ -132,6 +133,7 @@ The `config.jsonc` file configures [fastfetch](https://github.com/AlexRogalskiy/ 3. **Enhancements and Utilities** - Improves command output readability with colors + - Uses `batcat` or `bat` for interactive `cat` output without changing non-interactive script behavior - Introduces safer file operations (e.g., using `trash` instead of `rm`) - Integrates Zoxide for easy directory navigation diff --git a/setup.sh b/setup.sh index bcf1345..7edb065 100755 --- a/setup.sh +++ b/setup.sh @@ -325,21 +325,49 @@ link_file() { print_colored "$GREEN" "Linked $target" } -ensure_bash_profile_sources_bashrc() { - [ "$OS_NAME" = Darwin ] || return 0 +ensure_login_profile_sources_bashrc() { + if [ -f "$HOME/.bash_profile" ]; then + profile=$HOME/.bash_profile + elif [ -f "$HOME/.bash_login" ]; then + profile=$HOME/.bash_login + elif [ "$OS_NAME" = Darwin ]; then + profile=$HOME/.bash_profile + else + profile=$HOME/.profile + fi - profile=$HOME/.bash_profile - if [ -f "$profile" ] && grep -q 'HOME/.bashrc' "$profile"; then + if [ -f "$profile" ] && grep -Eq '^[[:space:]]*(\.|source)[[:space:]]+"?(\$\{?HOME\}?|~)/\.bashrc"?[[:space:]]*($|#)' "$profile"; then return 0 fi { - printf '\n# Source .bashrc for interactive bash shells\n' - printf '%s\n' "if [ -f \"\$HOME/.bashrc\" ]; then" + printf '\n# >>> mybash .bashrc >>>\n' + printf '%s\n' "if [ -n \"\${BASH_VERSION:-}\" ] && [ -f \"\$HOME/.bashrc\" ]; then" printf '%s\n' ". \"\$HOME/.bashrc\"" printf 'fi\n' + printf '# <<< mybash .bashrc <<<\n' } >>"$profile" - print_colored "$GREEN" "Updated $profile to source .bashrc" + print_colored "$GREEN" "Updated $profile to source .bashrc in login shells" +} + +verify_interactive_cat_alias() { + if command_exists batcat; then + expected="batcat --paging=never --style=full" + elif command_exists bat; then + expected="bat --paging=never --style=full" + else + print_colored "$RED" "bat was installed, but neither batcat nor bat is available in PATH." + return 1 + fi + + alias_output=$(bash --login -ic 'alias cat >&3' 3>&1 >/dev/null 2>/dev/null || true) + expected_output="alias cat='$expected'" + if [ "$alias_output" = "$expected_output" ]; then + print_colored "$GREEN" "Verified interactive cat alias: $expected" + else + print_colored "$RED" "The interactive cat alias was not loaded from $HOME/.bashrc." + return 1 + fi } ensure_bash_profile_brew_shellenv() { @@ -378,13 +406,16 @@ install_configs() { link_file "$MYBASHDIR/starship-theme" "$HOME/.local/bin/starship-theme" ensure_homebrew_bash_macos ensure_bash_profile_brew_shellenv - ensure_bash_profile_sources_bashrc + ensure_login_profile_sources_bashrc + verify_interactive_cat_alias } -install_dependencies -install_starship_linux -install_nerd_font_linux -install_configs -configure_terminal_font_linux +if [ "${MYBASH_SETUP_LIB_ONLY:-0}" -eq 0 ]; then + install_dependencies + install_starship_linux + install_nerd_font_linux + install_configs + configure_terminal_font_linux -print_colored "$GREEN" "Installation complete. Restart your shell or run: source ~/.bashrc" + print_colored "$GREEN" "Installation complete. Restart your shell or run: source ~/.bashrc" +fi diff --git a/tests/test_shell_integration.sh b/tests/test_shell_integration.sh new file mode 100755 index 0000000..e5c89e4 --- /dev/null +++ b/tests/test_shell_integration.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2030,SC2031 # HOME changes are intentionally isolated in test subshells. +set -euo pipefail + +ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P) +fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT + +grep -Fq "alias cat='batcat --paging=never --style=full'" "$ROOT/.bashrc" || fail 'missing batcat alias' +grep -Fq "alias cat='bat --paging=never --style=full'" "$ROOT/.bashrc" || fail 'missing bat fallback alias' + +BAT_TEST_DIR="$TMPDIR/bat-test" +mkdir -p "$BAT_TEST_DIR" +cat >"$BAT_TEST_DIR/batcat" <<'EOF' +#!/bin/sh +printf 'args:%s\n' "$*" +cat +EOF +chmod +x "$BAT_TEST_DIR/batcat" +bat_output=$(PATH="$BAT_TEST_DIR:/usr/bin:/bin" HOME="$TMPDIR" bash --noprofile --rcfile "$ROOT/.bashrc" -ic 'printf payload | cat' 2>/dev/null) +[[ $bat_output == $'args:--paging=never --style=full\npayload' ]] || fail "interactive cat did not invoke batcat directly: $bat_output" + +BAT_FALLBACK_DIR="$TMPDIR/bat-fallback-test" +mkdir -p "$BAT_FALLBACK_DIR" +cat >"$BAT_FALLBACK_DIR/bat" <<'EOF' +#!/bin/sh +printf 'args:%s\n' "$*" +/bin/cat +EOF +chmod +x "$BAT_FALLBACK_DIR/bat" +bat_fallback_output=$(PATH="$BAT_FALLBACK_DIR" HOME="$TMPDIR" /bin/bash --noprofile --rcfile "$ROOT/.bashrc" -ic 'printf payload | cat' 2>/dev/null) +[[ $bat_fallback_output == $'args:--paging=never --style=full\npayload' ]] || fail "cat alias did not invoke bat fallback correctly: $bat_fallback_output" + +REAL_BAT=$(command -v batcat || command -v bat || true) +[[ -n $REAL_BAT ]] || fail 'batcat/bat is required for the rendering contract' +command -v script >/dev/null 2>&1 || fail 'script is required for the rendering contract' +RENDER_HOME="$TMPDIR/render-home" +mkdir -p "$RENDER_HOME/bin" +ln -s "$REAL_BAT" "$RENDER_HOME/bin/$(basename "$REAL_BAT")" +printf '%s\n' 'PING 192.168.88.1 (192.168.88.1)' '64 bytes from 192.168.88.1' >"$RENDER_HOME/ping.txt" +render_output=$(TERM=xterm-256color PATH="$RENDER_HOME/bin:/usr/bin:/bin" HOME="$RENDER_HOME" \ + script -qec "bash --noprofile --rcfile '$ROOT/.bashrc' -ic 'cat \"$RENDER_HOME/ping.txt\"'" /dev/null 2>/dev/null) +[[ $render_output == *'ping.txt'* ]] || fail 'interactive cat rendering lacks the bat file header' +[[ $render_output == *'1'*'PING 192.168.88.1'* ]] || fail 'interactive cat rendering lacks bat line numbers/grid' +[[ $render_output == *$'\033['* ]] || fail 'interactive cat rendering lacks terminal color escapes' + +noninteractive_output=$(PATH="$BAT_TEST_DIR:/usr/bin:/bin" HOME="$TMPDIR" bash --noprofile -c '. "$1"; printf payload | cat' bash "$ROOT/.bashrc" 2>/dev/null) +[[ $noninteractive_output == payload ]] || fail "non-interactive cat behavior changed: $noninteractive_output" +bypass_output=$(PATH="$BAT_TEST_DIR:/usr/bin:/bin" HOME="$TMPDIR" bash --noprofile --rcfile "$ROOT/.bashrc" -ic 'printf payload | command cat' 2>/dev/null) +[[ $bypass_output == payload ]] || fail "command cat did not bypass the alias: $bypass_output" + +LOGIN_HOME="$TMPDIR/login-home" +mkdir -p "$LOGIN_HOME" +printf '# existing profile\n' >"$LOGIN_HOME/.profile" +ln -s "$ROOT/.bashrc" "$LOGIN_HOME/.bashrc" +( + export HOME="$LOGIN_HOME" MYBASH_SETUP_LIB_ONLY=1 PATH="$BAT_TEST_DIR:/usr/bin:/bin" + # shellcheck source=/dev/null + . "$ROOT/setup.sh" + # shellcheck disable=SC2034 # Consumed by the sourced setup function. + OS_NAME=Linux + ensure_login_profile_sources_bashrc + ensure_login_profile_sources_bashrc +) +# shellcheck disable=SC2016 # Match the literal profile command. +grep -Fq '. "$HOME/.bashrc"' "$LOGIN_HOME/.profile" || fail 'Linux login profile does not source .bashrc' +# shellcheck disable=SC2016 # Match the literal profile command. +[[ $(grep -Fc '. "$HOME/.bashrc"' "$LOGIN_HOME/.profile") -eq 1 ]] || fail 'login profile sources .bashrc more than once' +/bin/sh -c '. "$1"' sh "$LOGIN_HOME/.profile" >/dev/null 2>&1 || fail 'generated .profile is unsafe for non-Bash shells' + +PROFILE_HOME="$TMPDIR/bash-profile-home" +mkdir -p "$PROFILE_HOME" +# shellcheck disable=SC2016 # Write the literal ${HOME} profile form. +printf '%s\n' '. "${HOME}/.bashrc"' >"$PROFILE_HOME/.bash_profile" +( + export HOME="$PROFILE_HOME" MYBASH_SETUP_LIB_ONLY=1 + # shellcheck source=/dev/null + . "$ROOT/setup.sh" + # shellcheck disable=SC2034 # Consumed by the sourced setup function. + OS_NAME=Linux + ensure_login_profile_sources_bashrc +) +[[ $(grep -c '\.bashrc' "$PROFILE_HOME/.bash_profile") -eq 1 ]] || fail 'existing bash profile received a duplicate .bashrc source block' +[[ ! -e $PROFILE_HOME/.profile ]] || fail 'setup ignored existing .bash_profile precedence' + +VERIFY_HOME="$TMPDIR/verify-home" +mkdir -p "$VERIFY_HOME/bin" +cat >"$VERIFY_HOME/bin/batcat" <<'EOF' +#!/bin/sh +exit 0 +EOF +cat >"$VERIFY_HOME/bin/bash" <<'EOF' +#!/bin/sh +printf '%s\n' "$*" >"$HOME/bash-args" +printf "%s\n" "alias cat='batcat --paging=never --style=full'" >&3 +EOF +chmod +x "$VERIFY_HOME/bin/batcat" "$VERIFY_HOME/bin/bash" +( + export HOME="$VERIFY_HOME" MYBASH_SETUP_LIB_ONLY=1 PATH="$VERIFY_HOME/bin:/usr/bin:/bin" + # shellcheck source=/dev/null + . "$ROOT/setup.sh" + verify_interactive_cat_alias >/dev/null +) +grep -Fq -- '--login -ic alias cat >&3' "$VERIFY_HOME/bash-args" || fail 'alias verification did not use a login shell' + +printf 'Shell integration tests passed.\n' \ No newline at end of file