r_bash – Telegram
I built sbsh to make bash environments reproducible and persistent

I wanted to share a small open-source tool I have been building and using every day called **sbsh**. It lets you define your terminal environments declaratively, something I have started calling **Terminal as Code**, so they are reproducible and persistent.

🔗 Repo: [github.com/eminwux/sbsh](https://github.com/eminwux/sbsh)

🎥 **Demo: u**sing a [bash-demo profile](https://github.com/eminwux/sbsh/blob/main/docs/profiles/bash-demo.yaml)

https://i.redd.it/eqyjmpbkz30g1.gif

Instead of starting a shell and manually setting up variables or aliases, you can describe your setup once and start it with a single command.

Each profile defines:

* Environment variables
* Working directory
* Lifecycle hooks
* Custom prompts
* Which shell or command to run

Run `sbsh -p bash-demo` to launch a fully configured session.
Sessions can be detached, reattached, listed, and logged, similar to `tmux`, but focused on reproducibility and environment setup.

You can also define profiles that run **Docker** or **Kubernetes** commands directly.

📁 Example profiles: [docs/profiles](https://github.com/eminwux/sbsh/tree/main/docs/profiles)

**I would love feedback from anyone who enjoys customizing their terminal or automating CLI workflows. Would this be useful in your daily setup?**

https://redd.it/1os2e7k
@r_bash
My first shell project

I always wanted to try Bash and write small noscripts to automate something. It feels cool for me. One of the most repetitive things I do is type:

git add . && git commit -m "" && git push

So I decided to make a little noscript that does it all for me. It is a really small noscript, but it's my first time actually building something in Bash, and it felt surprisingly satisfying to see it work. I know it’s simple, but I’d love to hear feedback or ideas for improving it


Code: https://github.com/OgShadoww/GitRun

https://redd.it/1oskosd
@r_bash
My PIPESTATUS got messed up

My `PIPESTATUS` is not working. My `bashrc` right now:

```bash
#!/usr/bin/bash
# ~/.bashrc
#

# If not running interactively, don't do anything
[[ $- != *i* ]] && return
# ------------------------------------------------------------------ Bash stuff
HISTCONTROL=ignoreboth:erasedups
# --------------------------------------------------------------------- Aliases
alias ls='ls --color=auto'
alias grep='grep --color=auto'
alias ..='cd ..'
alias dotfiles='/usr/bin/git --git-dir="$HOME/.dotfiles/" --work-tree="$HOME"'
# Completion for dotfiles
[[ $PS1 && -f /usr/share/bash-completion/completions/git ]] &&
source /usr/share/bash-completion/completions/git &&
__git_complete dotfiles __git_main
alias klip='qdbus org.kde.klipper /klipper setClipboardContents "$(cat)"'
# alias arti='cargo run --profile quicktest --all-features -p arti -- '
# -------------------------------------------------------------------- env vars
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_DATA_HOME="$HOME/.local/share"
export XDG_STATE_HOME="$HOME/.local/state"
export EDITOR=nvim
# Colored manpages, with less(1)/LESS_TERMCAP_xx vars
export GROFF_NO_SGR=1
export LESS_TERMCAP_mb=$'\e[1;5;38;2;255;0;255m' # Start blinking
export LESS_TERMCAP_md=$'\e[1;38;2;55;172;231m' # Start bold mode
export LESS_TERMCAP_me=$'\e[0m' # End all mode like so, us, mb, md, mr
export LESS_TERMCAP_us=$'\e[4;38;2;255;170;80m' # Start underlining
export LESS_TERMCAP_ue=$'\e[0m' # End underlining
# ----------------------------------------------------------------------- $PATH
if [[ "$PATH" != *"$HOME/.local/bin"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi

if [[ "$PATH" != *"$HOME/.cargo/bin"* ]]; then
export PATH="$HOME/.cargo/bin:$PATH"
fi
# ------------------------------------------------------------------------- bat
# alias bathelp='bat --plain --paging=always --language=help'
# helpb() {
# builtin help "$@" 2>&1 | bathelp
# }
# help() {
# "$@" --help 2>&1 | bathelp
# }
# ------------------------------------------------------------------------- fzf
# eval "$(fzf --bash)"
#
# IGNORE_DIRS=(".git" "node_modules" "target")
# WALKER_SKIP="$(
# IFS=','
# echo "${IGNORE_DIRS[*]}"
# )"
# TREE_IGNORE="$(
# IFS='|'
# echo "${IGNORE_DIRS[*]}"
# )"
#
# export FZF_DEFAULT_OPTS="--multi
# --highlight-line
# --height 50%
# --tmux 80%
# --layout reverse
# --border sharp
# --info inline-right
# --walker-skip $WALKER_SKIP
# --preview '~/.config/fzf/preview.sh {}'
# --preview-border line
# --tabstop 4"
# export FZF_CTRL_T_OPTS="
# --walker-skip $WALKER_SKIP
# --bind 'ctrl-/:change-preview-window(down|hidden|)'"
# # --preview 'bat -n --color=always {}'
# export FZF_CTRL_R_OPTS="
# --no-preview"
# export FZF_ALT_C_OPTS="
# --walker-skip $WALKER_SKIP
# --preview \"tree -C -I '$TREE_IGNORE' --gitignore {}\""
# # Options for path completion (e.g. vim **<TAB>)
# export FZF_COMPLETION_PATH_OPTS="
# --walker file,dir,follow,hidden"
# # Options for directory completion (e.g. cd **<TAB>)
# export FZF_COMPLETION_DIR_OPTS="
# --walker dir,follow,hidden"
#
# unset IGNORE_DIRS
# unset WALKER_SKIP
# unset TREE_IGNORE
#
# # Advanced customization of fzf options via _fzf_comprun function
# # - The first argument to the function is the name of the command.
# # - You should make sure to pass the rest of the arguments ($@) to fzf.
# _fzf_comprun() {
# local command=$1
# shift
#
# case "$command" in
# cd)
# fzf --preview 'tree -C {} | head -200' "$@"
# ;;
# export | unset)
# fzf --preview "eval 'echo \$'{}" "$@"
# ;;
# ssh)
# fzf --preview 'dig {}' "$@"
# ;;
# *)
# fzf --preview 'bat -n --color=always {}' "$@"
# ;;
# esac
# }
#
---------------------------------------------------------------------- Prompt
# starship.toml#custom.input_color sets input style, PS0 resets it
# PS0='\[\e[0m\]'
if [[ $TERM_PROGRAM != @(vscode|zed) ]]; then
export STARSHIP_CONFIG=~/.config/starship/circles.toml
# export STARSHIP_CONFIG=~/.config/starship/dividers.toml
else
export STARSHIP_CONFIG=~/.config/starship/vscode-zed.toml
fi
# eval "$(starship init bash)"
# ---------------------------------------------------------------------- zoxide
# fucks up starship's status.pipestatus module
# eval "$(zoxide init bash)"
# ------------------------------------------------------------------------ tmux
if [[ $TERM_PROGRAM != @(tmux|vscode|zed) && "$DISPLAY" && -x "$(command -v tmux)" ]]; then
if [[ "$(tmux list-sessions -F '69' -f '#{==:#{session_attached},0}' 2> /dev/null)" ]]; then
tmux attach-session
else
tmux new-session
fi
fi
```

AS you may notice, all `eval`'s are commented out, so there's no shell integrations and stuff. I was initislly thinking its happening cause of `starship.rs` (prompt) but now it does not seem like so. Although `starship.rs` does show the different exit codes in the prompt. I'm not using `ble.sh` or https://github.com/rcaloras/bash-preexec

https://redd.it/1ostpgh
@r_bash
Automating Mint Updates?

Context: I'm trying to write a hardening bash/shell noscript for Mint 21. In it, I'd like to automate these tasks:

* Set the “Refresh the list of updates automatically:” value to “Daily”
* Enable the "Apply updates automatically" option
* Enable the "Remove obsolete kernels and dependencies" option

I know all this could be done pretty quickly in Update Manager, but it's just one of many things I'm trying automate.

I thought it would be simple, since I believe Linux Mint stores these update settings in dconf(?)

This is what I tried:

`#!/bin/bash`



`# Linux Mint Update Manager Settings Script`



`# Set the refresh interval to daily (1 day = 1440 minutes)`

`dconf write /com/linuxmint/updates/refresh-minutes 1440`



`# Enable automatic updates`

`dconf write /com/linuxmint/updates/auto-update true`



`# Enable automatic removal of obsolete kernels`

`dconf write /com/linuxmint/updates/remove-obsolete-kernels true`


Using `dconf read` does verify the changes were applied, but I'd have thought that the changes would've reflected in the Update Manager GUI (like other changes I've made via the noscript have) but everything looks the same. Can anyone tell me if I'm doing something wrong?

https://redd.it/1ot57dw
@r_bash
How do you centrally manage your Bash noscripts especially repeatable noscripts used in multiple server

So, I'm curious about how my fellow engineers handle multiple useful Bash noscripts. Especially when you have flints of servers.


Do you keep them in Git and pull from each host?
Or do you store them somewhere and just copy and paste whenever you want to use the noscript?


I'm exploring better ways to centrally organize, version, and run my repetitive Bash noscripts. Mostly when I have to run the same noscripts on multiple servers. Ideally something that does not need configuration management like Ansible.

Any suggestions? Advice? or better approach or tool used?

https://redd.it/1oslt0p
@r_bash
Template to Markdown File

Not sure if this already exists, but I wrote a small Bash tool that creates Markdown files from templates. It basically just copies a .md file from your templates directory to wherever you're working -- but with some added niceties like custom output names and persistent template directories.

I know this could just be done with a simple cp, but I like the simplicity this adds to my workflow.

workflow.github.com/AtonPomans/md\_template

https://redd.it/1oujchv
@r_bash
I have made a very useful python3 noscript to pause the current process. Switches include: -p,--prompt "message"; -r, --response "message; -t, --timer <seconds>; -q,--quiet; -e,--echo; -h, --help; --version

Below is the link to a python3 noscript that allows for customization, including replacing the prompt, a response option, quiet mode, timer, and echoing to variable the key pressed. The program will echo "Press any key to continue..." indefinitely until either the user presses any key (but [Shift\] [CTRL\], [ALT\]), or the optional timer [--t | --timer\] reaches zero. Timer is shown in [00:00:00\] format where hours and minutes hide as it reaches zero until the final count is [00\]. The program allows for quiet running with no prompt, just a pause with cursor blink [-q | --quiet\] that must have a timer set as well in order to run. There is an option to place an alternative prompt which replaces the default with your own [-p | --prompt\] (Must be within double quotes) and the ability to also add response text to the output [-r | --response\] (Must be within double quotes). I have added the option to send the key press to stdout using [-e | --echo\] in order to be used with command substitution to populate variables and work with case statements, etc.

When I migrated to Linux from Windows/DOS, I was rather surprised that there wasn't some type of "pause" function of any sort within the basic functioning of Linux. I have played around with the noscript for a while and have tried to make this as close to pure bash as possible in every directive I used keeping commands to those built-in. I have also spent many hours trying very hard to get the time function o the countdown to be as accurate as possible using different sources but unfortunately there is no way to get the precision I was hoping for through simple bash. As it is now there is a loss of about a second during the course of an hour.

I wanted a function in bash where I could just give the command "pause" and it would pause and I have done that with a little extra.

The default prompt is "Press any key to continue..."

Usage: $ pause [-p|--prompt\] [-t|--timer\] [-r|--response\] [-h|--help\] [-q|--quiet \] [-e, --echo\]

https://github.com/Grawmpy/pause.py

#!/usr/bin/python3

# Denoscription: This noscript allows for the interruption of the current process until either
# the timer reaches 00, or the user presses any key. If no timer is used, the process
# will be stopped indefinitely until the press of any key except CTRL, ALT, Shift. Using
# the timer (in seconds only) continues the current process without user interaction. The order of the
# variables can be passed to the noscript in any order, doesn't matter, they are processed as they
# are read and the result is stored until applied to the final output.
#
# Example of code:
# $ pause
# $ Press any key to continue...
#
# $ pause -t 10 -p "Hello World" -r "Thank you, come again".
# $ 10 Hello World
# $ Thank you, come again.
#
# $ keypress=$(pause -e -p "Enter your selection: ")
# $ Enter your selection: f (key not echoed to monitor)
# $ echo $keypress
# $ f
#
# Options include: -t, --timer -p, --prompt -r, --response -e, --echo
# -t, --timer: pause -t 10 (Will display as 00h:00m:00s before the prompt)
# -p, --prompt: pause -p "Hello World"
# -r, --response: pause -r "Thank you, come again."
# -e, --echo: pause -e (Can be used in conjunction with command substitution keypress=$(pause -e) )
# -q, --quiet: pause -q -t10 Must be used wit timer
#
# The -e option will echo the single key press to the std out for populating variables to be used in
# case statements, etc.
#
# GPL3 License: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE
I have made a very useful python3 noscript to pause the current process. Switches include: -p,--prompt "message"; -r, --response "message; -t, --timer <seconds>; -q,--quiet; -e,--echo; -h, --help; --version

Below is the link to a python3 noscript that allows for customization, including replacing the prompt, a response option, quiet mode, timer, and echoing to variable the key pressed. The program will echo "Press any key to continue..." indefinitely until either the user presses any key (but \[Shift\] \[CTRL\], \[ALT\]), or the optional timer \[--t | --timer\] reaches zero. Timer is shown in \[00:00:00\] format where hours and minutes hide as it reaches zero until the final count is \[00\]. The program allows for quiet running with no prompt, just a pause with cursor blink \[-q | --quiet\] that must have a timer set as well in order to run. There is an option to place an alternative prompt which replaces the default with your own \[-p | --prompt\] (Must be within double quotes) and the ability to also add response text to the output \[-r | --response\] (Must be within double quotes). I have added the option to send the key press to stdout using \[-e | --echo\] in order to be used with command substitution to populate variables and work with case statements, etc.

When I migrated to Linux from Windows/DOS, I was rather surprised that there wasn't some type of "pause" function of any sort within the basic functioning of Linux. I have played around with the noscript for a while and have tried to make this as close to pure bash as possible in every directive I used keeping commands to those built-in. I have also spent many hours trying very hard to get the time function o the countdown to be as accurate as possible using different sources but unfortunately there is no way to get the precision I was hoping for through simple bash. As it is now there is a loss of about a second during the course of an hour.

I wanted a function in bash where I could just give the command "pause" and it would pause and I have done that with a little extra.

The default prompt is "Press any key to continue..."

Usage: $ pause \[-p|--prompt\] \[-t|--timer\] \[-r|--response\] \[-h|--help\] \[-q|--quiet \] \[-e, --echo\]

[https://github.com/Grawmpy/pause.py](https://github.com/Grawmpy/pause.py)

#!/usr/bin/python3

# Denoscription: This noscript allows for the interruption of the current process until either
# the timer reaches [00], or the user presses any key. If no timer is used, the process
# will be stopped indefinitely until the press of any key except [CTRL], [ALT], [Shift]. Using
# the timer (in seconds only) continues the current process without user interaction. The order of the
# variables can be passed to the noscript in any order, doesn't matter, they are processed as they
# are read and the result is stored until applied to the final output.
#
# Example of code:
# $ pause
# $ Press any key to continue...
#
# $ pause -t 10 -p "Hello World" -r "Thank you, come again".
# $ [10] Hello World
# $ Thank you, come again.
#
# $ keypress=$(pause -e -p "Enter your selection: ")
# $ Enter your selection: f (key not echoed to monitor)
# $ echo $keypress
# $ f
#
# Options include: [-t, --timer] [-p, --prompt] [-r, --response] [-e, --echo]
# -t, --timer: pause -t 10 (Will display as [00h:00m:00s] before the prompt)
# -p, --prompt: pause -p "Hello World"
# -r, --response: pause -r "Thank you, come again."
# -e, --echo: pause -e (Can be used in conjunction with command substitution keypress=$(pause -e) )
# -q, --quiet: pause -q -t10 [Must be used wit timer]
#
# The -e option will echo the single key press to the std out for populating variables to be used in
# case statements, etc.
#
# GPL3 License: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE
AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import time
import sys
import argparse
import select
import tty
import termios
import os

SCRIPT = os.path.basename(sys.argv[0])
VERSION = '5.0.3' # Updated version number
AUTHOR = 'Grawmpy <Grawmpy@gmail.com> (CSPhelps)'
COPYRIGHT = """
GPL3.0 License. Software is intended as free use and is offered 'is is'
with no implied guarantees or copyrights."""
DESCRIPTION = """
A simple noscript that interrupts the current process until optional
timer reaches zero or the user presses any alphanumeric or [Enter]
key.

Optional custom prompt message and response text.

Allows for quiet mode with -q, --quiet [must be used in conjunction with -t, --timer <seconds>]

Command will interrupt process indefinitely until user presses any
alphanumeric key or optional timer reaches [00].

[ seconds are converted to [00h:00m:00s] style format ]
"""
DEFAULT_PROMPT = "Press any key to continue..."

def format_seconds(seconds):
"""Converts seconds into a [DD:HH:MM:SS] style format string."""
if seconds is None:
return ""

parts = []
days, seconds = divmod(seconds, 86400)
hours, seconds = divmod(seconds, 3600)
minutes, seconds = divmod(seconds, 60)

if days > 0:
parts.append(f"{days:02d}")
if hours > 0 or days > 0:
parts.append(f"{hours:02d}")
if minutes > 0 or hours > 0 or days > 0:
parts.append(f"{minutes:02d}")

# Seconds are always present
parts.append(f"{seconds:02d}")

return ":".join(parts)

def wait_for_key_or_timer(timeout=None, prompt=DEFAULT_PROMPT, response_text=None, quiet=False, echo_key=False):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)

HIDE_CURSOR = '\033[?25l'
SHOW_CURSOR = '\033[?25h'
ERASE_LINE = '\r\033[K'

pressed_key = None

try:
tty.setcbreak(fd)
start_time = time.time()

sys.stderr.write(HIDE_CURSOR)
sys.stderr.flush()

while True:
# Check for input immediately using select with a small timeout
# A 0.1 second timeout ensures we check for keys very frequently
if select.select([sys.stdin], [], [], 0.1)[0]:
pressed_key = sys.stdin.read(1)
break # Exit loop immediately on keypress

# Check time conditions
if timeout is not None:
elapsed_time = time.time() - start_time
remaining = int(timeout - elapsed_time)

if remaining <= 0:
break # Timer ran out

timer_display = format_seconds(remaining)
output = f"{ERASE_LINE}[{timer_display}] {prompt}\r"
else:
# If no timeout, show prompt only
output = f"{ERASE_LINE}{prompt}\r"

# Display output if not quiet
if not quiet:
sys.stderr.write(output)
sys.stderr.flush()

# Continue looping. The select.select(..., 0.1) handles the sleep/waiting.

# Clear final prompt line
sys.stderr.write(ERASE_LINE)
sys.stderr.flush()

finally:
# Restore original terminal settings
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
# Show the cursor again
sys.stderr.write(SHOW_CURSOR)
sys.stderr.flush()

# --- Handle Post-Exit Logic (Outside the try...finally block) ---
if response_text:
sys.stderr.write(f"{response_text}\n")
sys.stderr.flush()

if echo_key and pressed_key:
sys.stdout.write(pressed_key)
sys.stdout.flush()

def main():
full_epilog = f"""
Default prompt: {DEFAULT_PROMPT}

Usage:
{SCRIPT} [-p|--prompt ] [-t|--timer ] [-r|--response ] [-h|--help] [-q|--quiet] [-e|--echo]

-p, --prompt [ input required (string must be in quotes) ]
-t, --timer [ number of seconds ]
-r, --response [ requires text (string must be in quotes) ]
-e, --echo [ echoes the key pressed to stdout if present. ]
-h, --help [ this information ]
-q, --quiet [ quiets text, requires timer be set. ]

Examples:
Input: $ {SCRIPT}
Output: $ {DEFAULT_PROMPT}

Input: $ {SCRIPT} -t <seconds>
Output: $ [timer] {DEFAULT_PROMPT}

Input: $ {SCRIPT} --prompt "Optional Prompt" --response "Your response"
Output: $ Optional Prompt
$ Your Response

Input: $ {SCRIPT} -p "Optional Prompt" -r "Your response"

Author: {AUTHOR}
{COPYRIGHT}
"""
parser = argparse.ArgumentParser(
denoscription=DESCRIPTION,
formatter_class=argparse.RawDenoscriptionHelpFormatter,
epilog=full_epilog # <-- Insert the detailed help message here
)

parser.add_argument('-t', '--timer', type=int, help='Number of seconds to wait.')
parser.add_argument('-p', '--prompt', type=str, default=DEFAULT_PROMPT, help='Custom prompt message.')
parser.add_argument('-r', '--response', type=str, help='Requires text to be displayed after interruption.')
parser.add_argument('-q', '--quiet', action='store_true', help='Quiets text, implicitly requires timer be set.')
parser.add_argument('-e', '--echo', action='store_true', help='Echoes the key pressed to stdout if present.')
parser.add_argument('--version', action='version', version=f'%(prog)s v{VERSION}')

args = parser.parse_args()

# Logic checks as per original bash noscript
if args.quiet and not args.timer:
print("Error: Timer must be set when using --quiet mode.")
sys.exit(1)

wait_for_key_or_timer(
timeout=args.timer,
prompt=args.prompt,
response_text=args.response,
quiet=args.quiet,
echo_key=args.echo # Pass the new argument
)
sys.exit(0)

if __name__ == "__main__":
# Check if we are running interactively, which is required for termios
if not sys.stdin.isatty():
print("This noscript requires an interactive terminal to function properly.")
sys.exit(1)

main()


https://redd.it/1ouqrrq
@r_bash