Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .bashrc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
secdoc marked this conversation as resolved.
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
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/bashtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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..."
Expand All @@ -36,3 +39,6 @@ jobs:
else
echo "No linting errors found."
fi

- name: Test shell integration
run: tests/test_shell_integration.sh
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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`

Expand Down Expand Up @@ -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

Expand Down
59 changes: 45 additions & 14 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
107 changes: 107 additions & 0 deletions tests/test_shell_integration.sh
Original file line number Diff line number Diff line change
@@ -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'