bash-stdops – A collection of Bash helper scripts that facilitate operations

License

The bash-stdops project is a collection of helpful Bash scripts, written by James Cherti, that simplify various operations, including file searching, text replacement, and content modification.

The author uses these scripts in conjunction with text editors like Emacs and Vim to automate tasks, including managing Tmux sessions, replacing text across a Git repository, securely copying and pasting from the clipboard by prompting the user before executing commands in Tmux, fix permissions, among other operations.

Table of Contents

Install bash-stdops scripts

System-wide installation

To install bash-stdops scripts system-wide, use the following command:

git clone https://github.com/jamescherti/bash-stdops
cd bash-stdops

sudo ./install.sh

Alternative installation: Install in your home directory

If you prefer to install the scripts locally in your home directory, you can use the ~/.local/bin directory. This method avoids requiring administrative privileges and keeps the installation isolated to your user environment.

Use the following command to install the scripts into the ~/.local/bin directory:

PREFIX=~/.local ./install.sh

Ensure that ~/.local/bin is included in your $PATH by adding the following line to your ~/.bashrc:

export PATH=$PATH:~/.local/bin

Install dependencies

Instructions for installing dependencies are provided below. Note that not all of these dependencies are mandatory for every script.

Install dependencies on Debian/Ubuntu based systems

# Requirements
sudo apt-get install coreutils parallel ripgrep sed

# Git
sudo apt-get install git

# SSH
sudo apt-get install openssh-client

# Clipboard
sudo apt-get install xclip

Install dependencies on RedHat/CentOS/Fedora based systems

# Requirements
sudo dnf install coreutils parallel ripgrep sed git openssh-clients

# Git
sudo dnf install git

# SSH
sudo dnf install openssh-clients

# Clipboard
sudo dnf install xclip

Install dependencies on Gentoo based systems

# Requirements
sudo emerge sys-apps/coreutils sys-process/parallel sys-apps/ripgrep sys-apps/sed

# Git
sudo emerge dev-vcs/git

# SSH
sudo emerge net-misc/openssh

# Clipboard
sudo emerge x11-misc/xclip

Install dependencies on Arch Linux based systems

# Requirements
sudo pacman -S coreutils parallel ripgrep sed

# Git
sudo pacman -S git

# SSH
sudo pacman -S openssh

# Clipboard
sudo pacman -S xclip

Scripts

Script category: tmux

Script: tmux-cbpaste

The tmux-cbpaste: script enables pasting clipboard content into the current tmux window. It ensures safety by requiring user confirmation before pasting, preventing accidental insertion of data.

Script: tmux-run

This script executes a command in a new tmux window, which functions similarly to a tab in other applications.

  • If run within an existing tmux session, it creates a new window in the same session.
  • If run outside of tmux, it creates a new window in the first available tmux session.
  • If the environment variable TMUX_RUN_SESSION_NAME is set, the script will create the new window in the specified tmux session.

Usage:

  tmux-run <command> [args...]

Example:

tmux-run bash

Example 2:

tmux-run bash -c htop

Script: tmux-session

The tmux-session script attempts to attach to an existing tmux session. If the session does not exist, it creates a new session with that name.

If no session name is provided, it defaults to creating or attaching to a session named “0”.

Script category: files, paths, and strings

Script: walk

The walk bash script recursively search the specified directory and print the list of file or directory paths to standard output.

Script: walk-run

Recursively execute a command on all files listed by the rg --files command. For example, to recursively cat all text files in /etc, use the following command:

walk-run /etc cat {}

({} is replaced with the path to each file.)

Here is an example of how you can combine walk-run and sed to replace “Text1” with “Text2” in a Git repository:

walk-run /path/to/git-repository/ sed -i -e "s/Text1/Text2/g" {}

Script: sre

The sre script replaces occurrences of a specified string or regular expression pattern with support for exact string matching, regular expressions, and case-insensitive matching. Unlike sed, which uses a single argument for replacements, this script allows specifying the text-to-find and text-to-replace as two distinct arguments.

To replace text in the standard input and output the result to the standard output:

echo "text-before" | sre "text-before" "text-after"

To replace text directly in a file (overwriting the file):

sre "text-before" "text-after" file

Here are the sre options:

Usage: sre [-ierdh] <string-before> <string-after>

  -i    Ignore case when comparing files
  -e    Use regular expressions instead of exact strings.
  -r    Use extended regular expressions.
  -d    Show the sed command
  -h    Show this help message and exit

Here is an example of how you can combine walk-run and sre to replace Text1 with Text2 in a Git repository:

walk-run /path/to/git-repository/ sre Text1 Text2 {}

Script: git-sre

Execute sre at the root directory of a Git repository.

(The sre script replaces occurrences of a specified string or regular expression pattern with support for exact string matching, regular expressions, and case-insensitive matching.)

Example usage:

git sre TextBefore TextAfter /path/to/git/repo

(sre also supports regular expressions.)

Scripts: path-tr, path-uppercase, path-lowercase

  • path-tr: This script processes a given file path, extracts the directory and filename, converts the filename using the specified tr options (e.g., to lowercase), and prints the modified full path. Example usage: path-tr /Path/TO/FILE '[:upper:]' '[:lower:]' This will convert the filename to lowercase, producing: /Path/TO/file.

  • path-uppercase: This script processes a given file path, extracts the directory and filename, converts the filename to uppercase. Example usage: path-uppercase /Path/TO/FILE This will convert the filename to uppercase, producing: /Path/to/FILE.

  • path-lowercase: This script processes a given file path, extracts the directory and filename, converts the filename to lowercase. Example usage: path-lowercase /Path/TO/FILE. This will convert the filename to lowercase, producing: /Path/TO/file.

Script: autoperm

This script sets permissions for files or directories:

  • If it’s a directory: 755
  • If it’s a file with a shebang (e.g., “#!/bin/bash”): 755
  • If it’s a file: 644

Usage:

autoperm /path/to/file-or-directory

Script: path-is

Print the Path to stdout and exit with the code 0 if it is a binary or text file.

Example usage:

path-is /Path/TO/FILE binary
path-is /Path/TO/FILE text

Script category: git

git-dcommit

Script to automate common Git commit tasks:

  • Automatically add untracked files (prompted),
  • Display git diff to the user before committing,
  • Commit changes to the Git repository,
  • Optionally reuse the previous Git commit message if available.

Usage:

./script_name.sh

Run this script from within a Git repository to automate adding, reviewing, and committing changes.

git-squash

A script to squash new Git commits between the current branch and a specified branch.

Usage:
  ./script_name.sh <other-git-branch>

Features:

  • Compares the current branch with the specified branch.
  • Displays a summary of new commits to be squashed.
  • Prompts for confirmation if there are more than 4 commits.
  • Automatically squashes all new commits into one, retaining the message of the first commit.

git-finder

This script recursively locates all Git repositories starting from a specified directory or the current directory if none is provided.

It first checks for fd to perform faster searches; if unavailable, it defaults to find.

The script outputs the paths of all discovered Git repositories to standard output.

git-finder-exec

The git-finder-exec recursively finds all Git repositories starting from the current directory using the git-finder script.

It then executes the command provided as an argument in the directory of each Git repository.

Example usage:

git-finder-exec pwd

git-ourstheir

This script extracts the ‘ours’ and ‘theirs’ versions of a file involved in a Git merge conflict. It is intended to facilitate manual conflict resolution by saving both conflicting versions under distinct filenames (“ours-” and “theirs-“). This allows users to inspect and compare the conflicting changes independently of Git’s built-in merge tools.

Usage:

git-ourstheir <file-in-conflict>

git-sync-upstream

This script synchronizes the current Git branch with its upstream counterpart and force-pushes the result to the ‘origin’ remote. It is intended for workflows where a local branch is kept in sync with an upstream source of truth, and the mirror on ‘origin’ must match upstream exactly.

The script performs the following actions:

  1. Verifies that both ‘origin’ and ‘upstream’ remotes are defined.
  2. Performs a rebase of the current branch onto its upstream equivalent.
  3. Displays the diff between the rebased branch and the remote ‘origin’.
  4. Prompts for confirmation unless run in batch mode.
  5. Merges upstream changes with –ff-only and force-pushes to ‘origin’.

Intended for use in CI workflows or manual synchronization where upstream is authoritative.

Usage:
  git-sync-upstream [-h] [-b]
  -h    Show help message and exit
  -b    Run in batch mode (no interactive prompts)

Script category: ssh

Script: esa

Esa (Easy SSH Agent) simplifies starting ssh-agent, adding keys with ssh-add, and executing commands using the agent.

Usage:

Usage: esa <start|stop|ssh-add|exec>

start: Starts the ssh agent
start: Stop the ssh agent
add: Adds private keys requiring a password with ssh-add
exec: Executes a program using this agent
env: Displays the ssh-agent environment variables

Script: sshwait

This script repeatedly attempts to check the availability of the SSH server on the host provided as the first argument. It exits with a 0 status upon successfully establishing a connection at least once. Note that it only verifies if the SSH server is reachable and does not provide a shell prompt or execute any commands on the remote host.

Usage:

./script_name.sh <host>

X11/Wayland scripts

xocrshot

The xocrshot script captures a screenshot using ‘scrot’, performs optical character recognition (OCR) using ‘tesseract’ command, and:

  • Displays the extracted text in the terminal
  • Copies it to the clipboard.

Features:

  • Captures a screenshot using the ‘scrot’ command,
  • Performs OCR on the screenshot using Tesseract,
  • Displays the extracted text in the terminal,
  • Copies the extracted text to the clipboard using ‘xclip’,
  • Provides error handling and cleanup of temporary files,
  • Supports notifications using ‘notify-send’ (if available).

Usage:

xocrshot

Script category: Misc

Script: haide

The haide script utilizes AIDE (Advanced Intrusion Detection Environment) to monitor the file integrity of the user’s home directory, ensuring no files are modified, added, or deleted without the user’s knowledge. Key functions handle database setup, integrity checks, and user-approved updates. The script filters non-critical changes, ensuring meaningful alerts while maintaining a secure and reliable monitoring process.

Scripts: cbcopy, cbpaste

  • cbcopy: This script copies the content of stdin to the clipboard.
  • cbpaste: This script reads the contents of the system clipboard and writes it to stdout.
  • cbwatch: Monitor the clipboard and display its content when it changes.

Script: outonerror

The outonerror script redirects the command’s output to stderr only if the command fails (non-zero exit code). No output is shown when the command succeeds.

Here is an example of how to use this script: How to make cron notify the user about a failed command by redirecting its output to stderr only when it fails (non-zero exit code).

Script: over

This program simply displays a notification. It can be used in the terminal while another command is running. Once the command finishes executing, a notification is displayed, informing the user that the process has completed.

Script: largs

This script reads from standard input and executes a command for each line, replacing {} with the content read from stdin. It expects {} to be passed as one of the arguments and will fail if {} is not provided.

This script is an alternative to xargs.

{ echo "file1"; echo "file2"; } | largs ls {}

License

Copyright (C) 2012-2025 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.

Links

Bash shell: Interactive Menu to Insert any String from the Tmux Scrollback Buffer Into the Shell Prompt

Imagine you’re working in a tmux session, navigating logs, editing config files, or running commands, and you suddenly need to reuse a path, a variable name, or a keyword that appeared earlier on the tmux scrollback buffer. Instead of scrolling back or retyping it manually, you press Ctrl-n, type a few fuzzy letters, press enter, and the desired string from the tmux scrollback is instantly inserted at your cursor.

The tmux scrollback buffer is the internal history of terminal output that tmux maintains for each pane. It consists of the lines that have scrolled off the visible screen but are still accessible for review or processing.

This article presents a Bash function that scans the tmux scrollback buffer, lets you interactively select strings using fzf, and inserts the chosen string directly into the command line prompt. It offers a fast, context-aware mechanism for inline string insertion, allowing you to work efficiently without breaking your flow.

Requirements

Implementation using Bash functions and readline

Add the following code snippet to your ~/.bashrc to bind Ctrl-n for selecting strings from the tmux scrollback buffer using fzf, and inserting the selected string directly into the shell prompt:

#!/usr/bin/env bash
# Author: James Cherti
# License: MIT
# URL: https://www.jamescherti.com/tmux-autocomplete-fzf-fuzzy-insertion-scrollback/

__tmux_fzf_autocomplete__() {
  # Capture the last 100,000 lines from the tmux scrollback buffer, reverse
  # order, and extract strings
  tmux capture-pane -pS -100000 \
    |
    # Split input on spaces and newlines, remove duplicates while preserving
    # order, and keep only strings longer than 4 characters
    awk 'BEGIN { RS = "[ \t\n]" } length($0) > 4 && !seen[$0]++' \
    |
    # Invoke fzf for case-insensitive exact fuzzy matching, with results shown
    # in reverse order
    fzf --no-sort --exact +i --tac
}

__tmux_fzf_autocomplete_inline__() {
  local selected
  selected="$(__tmux_fzf_autocomplete__)"

  local before
  before="${READLINE_LINE:0:$READLINE_POINT}"

  local after
  after="${READLINE_LINE:$READLINE_POINT}"
  
  READLINE_LINE="${before}${selected}${after}"
  READLINE_POINT=$((READLINE_POINT + ${#selected}))
}

# Pressing Ctrl-n autocompletes from the Tmux scrollback buffer
bind -x '"\C-n": "__tmux_fzf_autocomplete_inline__"'Code language: Bash (bash)

Key components:

  • tmux capture-pane: Retrieves the scrollback content from the active tmux pane.
  • awk: Processes tokens, removes duplicates, and filters out strings shorter than 5 characters to produce a concise list.
  • fzf: Offers an interactive fuzzy interface for filtering and selecting the desired token. The --tac flag reverses the line order, prioritizing the most recent content.
  • bind: Inserts the selected token inline at the Bash prompt using a readline binding, activated by Ctrl-n. This binding does not interfere with typical command-line editing, preserves the existing prompt content, and updates the cursor position correctly.

Conclusion

The code snippet in this article enhances the Bash shell by turning the tmux scrollback buffer into a live source of contextual string insertion. Within tmux workflows, it removes the need to scroll through output, improving both speed and accuracy during command-line tasks.

Related Links

  • This function is included in the author’s .bashrc, which is available in the jc-dotfiles repository.

bash-stdops – A collection of useful Bash Shell Scripts

The jamescherti/bash-stdops project is a collection of helpful Bash scripts that simplify various operations, including file searching, text replacement, and content modification.

I use these scripts in conjunction with text editors like Emacs and Vim to automate tasks, including managing Tmux sessions, replacing text across a Git repository, securely copying and pasting from the clipboard by prompting the user before executing commands in Tmux, fix permissions, among other operations.

The bash-stdops Script Collection Overview

Files, Paths, and Strings

  • walk: Recursively lists files from the specified directory.
  • walk-run: Executes a command on all files.
  • sre: Replaces occurrences of a specified string or regular expression pattern, with support for case-insensitive matching and regular expressions.
  • git-sre: Executes sre at the root of a Git repository to replace text within files.
  • path-tr, path-uppercase, path-lowercase: Processes a file path to convert the filename to uppercase or lowercase.
  • autoperm: Sets appropriate permissions for files or directories (e.g., 755 for directories).
  • path-is: Prints the path and exits with status 0 if the file is binary or text.

Git

  • git-dcommit: Automates the process of adding, reviewing, and committing changes in Git.
  • git-squash: Squashes new Git commits between the current branch and a specified branch.

SSH

  • esa: Manages the SSH agent, including starting, stopping, adding keys, and executing commands.
  • sshwait: Waits for an SSH server to become available on a specified host.

Tmux

  • tmux-cbpaste: Pastes clipboard content into the current tmux window with user confirmation.
  • tmux-run: Executes a command in a new tmux window. If inside tmux, it adds a new window to the current session; otherwise, it creates a window in the first available tmux session.
  • tmux-session: Attaches to an existing tmux session or creates a new one if it doesn’t exist.

X11/Wayland

  • xocrshot: Captures a screenshot, performs OCR on it, displays the extracted text, and copies it to the clipboard.

Misc

  • haide: Uses AIDE to monitor the file integrity of the user’s home directory.
  • cbcopy, cbpaste, cbwatch: Manages clipboard content by copying, pasting, or monitoring for changes.
  • outonerror: Redirects the command output to stderr only if the command fails.
  • over: Displays a notification once a command completes execution.
  • largs: Executes a command for each line of input from stdin, replacing {} with the line.

Conclusion

The jamescherti/bash-stdops scripts provide a variety of tasks, including file manipulation, Git management, and SSH automation, improving efficiency and usability.

Making ‘cron’ notify the user about a failed command by redirecting its output to stderr only when it fails (non-zero exit code)

Monitoring the success or failure of cron jobs can be challenging, especially when multiple jobs cause cron to send emails even when they don’t fail. A more effective approach to handling cron job errors is to use a Bash script that directs the output to stderr only if the job fails, causing cron to notify the user about the error only when the script fails.

Script overview

The script provided below ensures that any command passed to it will redirect its output to a temporary file. If the command fails (i.e., returns a non-zero exit code), the script sends the content of the temporary file to stderr, which causes cron to notify the user.

Here is the complete script to be installed in /usr/local/bin/outonerror:

#!/usr/bin/env bash
# Description:
# Redirect the command's output to stderr only if the command fails (non-zero
# exit code). No output is shown when the command succeeds.
#
# Author: James Cherti
# License: MIT
# URL: https://www.jamescherti.com/cron-email-output-failed-commands-only/

set -euf -o pipefail

if [[ "$#" -eq 0 ]]; then
  echo "Usage: $0 <command> [args...]" >&2
  exit 1
fi

cleanup() {
  if [[ "$OUTPUT_TMP" != "" ]] && [[ -f "$OUTPUT_TMP" ]]; then
    rm -f "$OUTPUT_TMP"
  fi

  OUTPUT_TMP=""
}

OUTPUT_TMP=$(mktemp --suffix=.outonerror)
trap 'cleanup' INT TERM EXIT

ERRNO=0
"$@" >"$OUTPUT_TMP" 2>&1 || ERRNO="$?"
if [[ "$ERRNO" -ne 0 ]]; then
  cat "$OUTPUT_TMP" >&2
  echo "$0: '$*' exited with status $ERRNO" >&2
fi

cleanup
exit "$ERRNO"
Code language: Bash (bash)

To use this script with a cron job, save it as /usr/local/bin/outonerror and make it executable by runnning:

chmod +x /usr/local/bin/outonerrorCode language: plaintext (plaintext)

Integration with Cron

Cron sends an email by default whenever a job produces output, whether standard output or error output. This is typically configured through the MAILTO environment variable in the crontab file. If MAILTO is not set, cron sends emails to the user account under which the cron job runs. Cron will only be able to send emails if the mail transfer agent (e.g., Postfix, Exim, Sendmail) is configured properly.

Here is how to schedule the cron job to use the outonerror script:

MAILTO="your-email@example.com"
* * * * * /usr/local/bin/outonerror your_command_hereCode language: plaintext (plaintext)

With this setup, the cron job will execute your_command_here and only send an email if it fails, thanks to cron’s default behavior of emailing stderr output to the user.

Conclusion

This script is a simple yet effective solution for improving cron jobs by ensuring that the user is notified only when something goes wrong. It reduces unnecessary notifications for successful executions and provides clear error messages when failures occur.

Bash shell: Perform tab-completion for aliases (bash-completion)

# Author: James Cherti
# License: MIT
# Requirements: bash-completion
# Description: Perform tab-completion for aliases in Bash (bash-completion).
# URL: https://www.jamescherti.com/bash-shell-perform-tab-completion-for-aliases/
#
# Add the following function to ~/.bashrc :

alias_completion() {
  local func_name='alias_completion'
  if [[ $# -lt 2 ]]; then
    echo "Usage: $func_name <cmd> <alias> <alias2> <...>" >&2
    return 1
  fi

  local cmd; cmd="$1"
  shift

  # Load the completion
  if ! type _completion_loader >/dev/null 2>&1; then
    echo "Error: $func_name: '_completion_loader' was not found." >&2
    return 1
  fi

  _completion_loader "$cmd"

  if ! complete -p "$cmd" >/dev/null; then
    echo "Error: $func_name: 'complete -p $cmd' failed." >&2
    return 1
  fi

  # Add aliases
  local alias
  for alias in "$@"; do
    complete_cmd=$(complete -p "$cmd" 2>/dev/null | sed -e 's/[[:space:]][^[:space:]]\+$//')
    complete_cmd="${complete_cmd} $alias"

    if ! ( echo "$complete_cmd" | grep -P '^\s*complete\s' >/dev/null 2>&1 ); then
      echo "Error: $func_name: alias '$alias': '$complete_cmd' is an invalid command." >&2
      return 1
    fi

    eval "$complete_cmd"
  done

  return 0
}Code language: Bash (bash)

Examples of aliases:

alias s='ssh'
alias_completion ssh s

alias g='git'
alias_completion git gCode language: Bash (bash)

Bash shell: A better version of the default bash built-in command “cd”

#!/usr/bin/env bash
# Author: James Cherti
# License: MIT
# URL: https://www.jamescherti.com/shell-bash-replacement-bash-cd-change-directory/
#
# Description:
# 1. 'cd path/to/file' will change the directory to 'path/to'
#    (the parent directory of 'file').
#
# 2. 'cd path/to/dir with spaces' will change the directory to
#    "path/to/dir with spaces".
#
# 3. 'cd file:///home/user' will change the directory to "/home/user".
# 
# 4. You can switch to the previous directory with 'popd' or 'cd -'.
#
# Add the following function and alias to ~/.bashrc :
#

_better_cd() {
  # Previous directory ('cd -')
  if [[ $# -eq 1 ]] && [[ $1 = '-' ]]; then
    popd >/dev/null || return 1
    return 0
  fi

  # Join paths
  local path
  if [[ $# -eq 0 ]]; then
    path="$HOME"
  else
    path=$(echo "$1" | sed -e 's/^file:\/\///')
    shift

    local item
    for item in "$@"; do
      path="${path} ${item}"
    done
  fi

  # Checks
  local errno=0
  if [[ -f "$path" ]]; then
    path=$(dirname "$path")
  fi

  if ! [[ -d "$path" ]]; then
    echo "$(basename "$0"):" "cd: $path: No such file or directory" >&2
    return 1
  fi

  # Change the directory
  local oldcwd; oldcwd=$(pwd)
  pushd . >/dev/null || return 1
  builtin cd "$path" >/dev/null 2>&1 || errno=1
  if [[ $oldcwd = "$(pwd)" ]]; then
    popd >/dev/null || return 1
  fi

  return "$errno"
}

alias cd=_better_cdCode language: Bash (bash)