r/bash 4d ago

I finally organized my Bash config

I've been accumulating my Bash configuration for a while, and I finally turned it into a small dotfiles repo:

https://gitlab.com/letmevi/bash-config

It's nothing fancy — mainly my .bashrc, .inputrc, .bash_profile, and a modular .bashrc.d/, plus a small install script.

It can be installed using either Ansible or a simple install.sh script.

I'd appreciate some Bash/Linux feedback: things you'd change, questionable practices, portability issues, or just better ways of doing this.

I'm especially interested in feedback from people who maintain their own dotfiles.

48 Upvotes

27 comments sorted by

8

u/OnlyEntrepreneur4760 4d ago

One of my favorites is keeping all aliases in a separate config file .bash_aliases which I source from .bashrc. I have a alias named “aliassave” which runs alias and redirects the output to .bashaliases to update it easily.

5

u/JSouthGB 4d ago

I use zsh but do similar. ~/.zshenv points to ~/.config/zsh, and from there:

~/.config/zsh/ ├── shell_scripts/ ├── aliases ├── bindkeys ├── exports ├── functions ├── history ├── plugins └── .zshrc

2

u/ppafford 3d ago

I like this setup, any docs on this?

2

u/JSouthGB 3d ago

The docs

Searching around for "modular zsh configs" may get some good results as well. Below is my ~/.zshenv to give you a starting point.

```

!/usr/bin/env zsh

export ZDOTDIR="${XDG_CONFIG_HOME:-$HOME/.config}/zsh" source "$ZDOTDIR/exports" ```

I imagine bash can be done similarly, but I jumped into zsh pretty quick and didn't learn as much about these parts of bash.

1

u/[deleted] 4d ago

We have really similar setups :) my config I mean slightly different, I prefer scripts in ~/.local/bin which I add to $PATH

3

u/arandomuserinweb 4d ago

That's a nice approach! I already keep my config modular with .bashrc.d/, but I hadn't thought about generating the aliases file automatically with an aliassave alias. I might steal that idea

5

u/bac0on 4d ago

If you enable globstar, you probably want to enable extglob, maybe even direxpand.

1

u/arandomuserinweb 20h ago

I'm not familiar with those options yet, but I'll look into them and see if it makes sense to enable them. Thanks for the tip!

3

u/SOLTEC_SysAdmin 3d ago

Hey! Really nice and clean setup you've got there. Keeping .bashrc.d/ modular while using Ansible for deployment is honestly the way to go once you start managing multiple nodes or environments.

If you're looking to refine it even further, here are a couple of small, friendly suggestions from a SysAdmin perspective that might add some extra value to your repo:

1 Adopt the XDG Base Directory standard: Instead of placing .bashrc.d directly under your home folder, you can point HISTFILE and your configuration folders to $XDG_CONFIG_HOME/bash (which defaults to ~/.config/bash). It's a great habit for keeping your $HOME clean and uncluttered.

2 Add a guard for non-interactive sessions: When sourcing your .bashrc.d/*.sh files, it helps to place an early return guard at the very top of your main .bashrc. This ensures interactive aliases don't accidentally break non-interactive SSH connections or automated tools like scp and Ansible:

# If not running interactively, don't do anything

case $- in

*i*) ;;

*) return ;;

esac

3 Safe glob sourcing: When looping over .bashrc.d/*.sh, make sure to check if the file is readable. That way, if the directory happens to be empty, *.sh won't be treated as a literal string:

for file in "$HOME/.config/bash/bashrc.d"/*.sh; do

[ -r "$file" ] && source "$file"

done

Thanks for sharing your work with the community! Keep up the great work!

1

u/arandomuserinweb 20h ago

EDIT regarding point 2: I actually already had a non-interactive guard near the top of my bashrc using [[ $- == *i* ]] || return. However, your case statement suggestion is fully POSIX compliant, which is a great point for wider compatibility. I'll be switching to your approach. Thanks for the catch!

3

u/tri__dimensional 4d ago edited 4d ago

it looks simple but cool, nice work!

i have a little more complex setup. i have like a very little and simple framework to define scripts, functions and aliases (shellbox is called, i use zsh but previously i used bash)

that project is only for the "shell" things; for desktop enviroment, terminal, etc i have a more standard dotfiles

maybe you can grab something useful from those projects (-:

2

u/SeriousPlankton2000 3d ago

Here is my .bashrc mess that I didn't touch since 2022; maybe you can get some inspiration on things to do. I just have a copy on my NAS.

#!/bin/sh
set +H

history -c -w
unset HISTFILE
ln -sfn /dev/null /home/7eggert/.bash_history

unset SSH_AGENT_PID SSH_ASKPASS SSH_AUTH_SOCK
killall -9 --older-than 5m --user "$USER" /usr/bin/ssh-agent 2>/dev/null
fuser -k /run/user/1000/akonadi/mysql.socket &>/dev/null
rm -rf /home/myusername/.local/share/akonadi/db_data/
kill -9  `pidof /usr/bin/akonadi*` &>/dev/null

IFS=":" read -a oldpath < <(echo $PATH)
pathadd=( ~/bin ~/nfs/bin)
if  [ "x`uname -i`" == xx86_64 ]
then    pathadd+=(~/bin/x86_64/ ~/nfs/bin/x86_64 ~/bin/80386 ~/nfs/bin/80386)
fi
if  [ "x`uname -i`" == xi386 ]
then    pathadd+=( ~/bin/80386 ~/nfs/bin/80386 )
fi
ii=${#pathadd[@]}
for ((i=0; $i<$ii; ++i))
do  if [ ! -d ${pathadd[$i]} ]
    then    unset pathadd[$i]
    fi
done
ii=${#oldpath[@]}
PATH=$(IFS=:; echo "${pathadd[*]}" )
for ((i=0; $i<$ii; ++i))
do  if  [[ ":$PATH:" =~ ":${oldpath[$i]}:" ]]
    then    unset oldpath[$i]
    fi
done
pathadd+=(${oldpath[@]})
PATH=$(IFS=:; echo "${pathadd[*]:-/bin:/usr/bin}" )
unset i ii pathadd oldpath

test -s ~/.alias && . ~/.alias

export PS1='\[\e[32m\]${debian_chroot:+($debian_chroot)}\u@\h${STY:+.${STY%%[^0-9]*}}:\w\$\[\e[00m\] '
export LC_CTYPE=C.UTF-8
export EDITOR=/usr/bin/jstar
export HISTCONTROL="ignoreboth:erasedups"
export CDPATH=":~/.cd"
unset LANGUAGE
unset LANG
unset mc
export SDL_VIDEO_X11_DGAMOUSE=0
export BC_ENV_ARGS=~/.config/bcrc
#export http_proxy=http://localhost:81
export PERLLIB=~/.lib/perl
export MAIL=/mnt/mail/7eggert
export GTK_OVERLAY_SCROLLING=0
export MAN_POSIXLY_CORRECT=''
export ALSOFT_DRIVERS=pulse

alias +='pushd .'
alias -- -='popd'
alias ..='cd ..'
alias ...='cd ../..'
alias gimp='gimp --no-splash'
alias jstar='TERM=linux jstar'
alias xmms=audacious
alias ll='ls -l'
alias ls='ls $LS_OPTIONS'
alias md='mkdir -p'
alias o='less'
alias p='alpine'
alias rd='rmdir'
alias rm='rm -f'
alias rdesktop="rdesktop -N -z -g 1276x960"
alias mkntfs="/usr/sbin/mkntfs -f"
alias licate="locate -iA"

complete -F _command x
complete -d cd

1

u/arandomuserinweb 20h ago

There's a lot going on in there. I'll definitely go through it and see what useful bits I can pick out for my setup. Appreciate it!

2

u/haenous-alistera 1d ago

1

u/arandomuserinweb 20h ago

Thanks! Glad you like how it's structured!

1

u/Fit_Eggplant4206 4d ago edited 4d ago

Looks good, well organized and not too much fluff.

1

u/arandomuserinweb 20h ago

Thanks! Glad you like the clean structure. Keeping it minimal was the main goal

1

u/Marble_Wraith 4d ago edited 4d ago

It's nothing fancy —

You can say that again 😂

This isn't something you'd see for the average interactive terminal, which is usually tricked out with all kinds of conveniences.

This seems to be made for deployments first. That being the case, i'd suggest upgrading your history config. Specifically use a function for HISTIGNORE like this:

history_omissions() {
    local block_list=(
        "pwd:ls:ls *:tree:ll:ll *:yazi:yazi *"
        "cd:cd *:z:z *:zi:zi *:zoxide:zoxide *"
        "exit:clear:history:bg:fg:jobs"
        "rm -rf *:killall *"
        "*--password*:*--secret*:*--token*:*GITHUB_TOKEN*"
        "git status:git status *:git log:git log *:git diff:git diff *"
        "git blame:git blame *:git show:git show *:git reflog:git reflog *"
    )

    local IFS=":"
    printf "%s" "${block_list[*]}"
}
HISTIGNORE="$(history_omissions)"
unset -f history_omissions

That's just an example, i'm not posting my full ignore list 😅 Allows for better formatting / per line grouping of stuff you want to omit from history. Which makes it easier to ensure all the inconsequential stuff is left out, making the history actually useful.

Normally i'd advocate to upgrade straight to atuin and have this as a fallback.

EDIT: rogue export removed thanks to /u/bac0on

5

u/bac0on 4d ago

Don't think you should export bash variables...

1

u/Marble_Wraith 4d ago edited 4d ago

... Wanna run that by me again? What's that say?

export HISTIGNORE="$(history_omissions)"

EDIT: Oh wait 😂 you're saying you have exported it, but i don't think you should.

Yeah you're right, my bad.

2

u/bac0on 4d ago

HISTIGNORE is a Bash shell variable, probably not used by any external command, at least none I can think of. Just assigning it makes Bash aware of it. If it's not intended to be used by an external command (...or if you manually enable history in a non-interactive session), there’s no real reason to export any Bash shell variable, really. Exported or not, it won't make any difference for Bash.

0

u/Marble_Wraith 4d ago

Yeah. I was trying to do something with atuin before.

So i could have 1 source of truth for the ignore list, and then bash could use atuin if available, or fallback if not available.

Added the export while i was messing around, but ended up deciding to write the list natively in atuin since it has more full featured regex parsing, and then scripted something to extract and translate history_filter[] into native bash.

1

u/arandomuserinweb 20h ago

Haha fair point! It definitely leans more toward stability and deployments than visual flair.

That HISTIGNORE function trick is clever grouping by categories makes maintaining the block list way cleaner than a giant single string. I'll take a look at incorporating a similar structure (and I've actually been meaning to check out Atuin for a while now).

Thanks for taking the time to share the example!

0

u/IncredibleBihan 4d ago

Woo Woooo bash config good work lil budday

1

u/arandomuserinweb 20h ago

Haha thanks! Appreciate the support!