Skip to main content
  1. Posts/

CLI Magic #1: Useful Bash Scripts

·1375 words·7 mins
Tutorial bash cli
bitSheriff
Author
bitSheriff
Bughunter in the Wild Wild Web
Table of Contents
CLI Magic - This article is part of a series.

Before I centered my workflow around the terminal, I did not know how much friction in daily tasks I could solve with simple tools and own scripts. In this post, I want to share some of the small bash scripts I use on a regular basis. They are not complex, but they save me a lot of time and make my life easier. I hope you find them useful as well.

How to create a (global) bash script
#

Before we start into some cool little bash script we need to learn how to create and use such scripts. To make a bash script global, we need to add it to the PATH environment variable. This can be done by adding the following line to the ~/.bashrc file:

export PATH="$PATH:/path/to/your/scripts"

A more simple way is to just add the script to the ~/.local/bin directory which is already in the $PATH environment variable.

To create a file use your favorite text editor, for example nano or nvim.

nvim ~/.local/bin/test

Maybe you already recognized, that I did not add an extension to the file name. This is because we want to use it like $ test in the terminal and not $ test.sh for example. The language of the script is not (only) defined in the file extension, but also in the shebang line at the beginning of the file. A shebang is a hint to the shell, how to execute the script. In these examples, we use the shebang #!/bin/bash which tells the shell to execute the script with the bash interpreter and also python for some more advanced ones.

So now the script is created in the correct directory and already filled with the desired code. Now we have to make it executable, which can be done with the chmod command.

chmod +x ~/.local/bin/test

Now the shell knows that it can execute the script. You can test it by running $ test in the terminal. With this (maybe) new knowledge, we can start with some interesting examples.

randomselect
#

This script selects a random argument from the list of arguments passed to it. It is useful when you have a list of options and want to randomly choose one of them. Most of the time, I use it to decide what to eat when I have multiple options available, not only in the context of work but also in daily life. For example deciding what to eat for lunch or watching a movie.

#!/bin/bash
# Check if any arguments are passed
if [ "$#" -eq 0 ]; then
    echo "Usage: $0 string1 string2 ... stringN"
    exit 1
fi

# Choose a random index from the arguments
random_index=$((RANDOM % $#))

# Print the randomly selected argument
echo "${@:$((random_index + 1)):1}"

To decide what to eat for lunch, I can use the script like this:

If you want to use words with white spaces, you can enclose them in quotes.
$ randomselect apple banana cherry "pizza diavola"

memo
#

Since I was a little child, I was always fascinated by the idea of keeping a journal, mostly by pirates or other captains who kept a logbook of their adventures. I tried many times to keep a diary, but I always failed. I found it boring to write about my daily life. But when I started using a digital journal, I found it more interesting. I started to write down my thoughts, ideas, and things I learned. This script helps me to add a memo to my journal quickly. It takes the input from the command line or uses gum to get the input interactively, if no arguments are provided. This means I can quickly jot down my thoughts without opening a text editor, but also pipe the output of other commands into the script. For example ls -l | memo to add the output of ls -l to my journal. This allows me to keep track of my daily activities and thoughts but also a little log of notable outputs of commands I run.

#!/bin/bash


# Preamble of the Memo
MEMO_PRE="- **$(date +%H:%M):** "
LINES_PRE="    - "

# Journal File where the memo gets added
JOURNAL="$JOURNAL_PATH"/$(date +"%F").md

# check if file has new line at the end, if not add one
test "$(tail -c 1 "$JOURNAL" | wc -l)" -eq 0 && echo -e "" >>"$JOURNAL"

# decide if the input was given as a positional argument or parsed into (stdin)
if [ $# -gt 0 ]; then
    # If there are arguments, join them into a single string and process
    input="$*"
    echo "$MEMO_PRE""$input" >>"$JOURNAL"
else

    # Otherwise, use gum write to get the input
    input=$(gum write --header "Memo" --show-line-numbers --char-limit 0)

    # line counter
    count=0

    # Process the input line by line
    while IFS= read -r line; do
        # just print the preamble on the first line
        if [ $count -eq 0 ]; then
            # Process the first line differently
            echo "$MEMO_PRE""$line" >>"$JOURNAL"
        else
            # Process the remaining lines
            echo "$LINES_PRE""$line" >>"$JOURNAL"
        fi

        # Increment the counter
        count=$((count + 1))
    done <<<"$input"
fi

Now the memo can be used in two ways:

$ memo # this will run the interactively input
$ memo "This is a test memo" # this will add the memo to the journal

So in my daily journal file this will result in:

- **08:00:** This is from the gum input
    - linebreaks will be preserved in the journal by using bullet indentations
- **12:00:** This is a test memo

z
#

I am a hugh fan of plain text files, like LaTeX or Markdown. Unfortunatly, reading them in a focused way is not always easy. Therefore I had the idea to use Zathura to display them.

But wait, can it view markdown files?

Ok, so we will need a workaround for that. In this case, we can use pandoc to convert the markdown file to pdf and then open it with zathura.

#!/bin/bash

if [[ $# == 0 ]]
then
    echo "Zathura wrapper for displaying md and pdf files"
    echo -e "Usage:\n\tz FILENAME"
    exit 1
fi

EXT=${1##*.}

if [[ "$EXT" == "pdf" ]]
then
    zathura $1
elif [[ "$EXT" == "md" ]]
then
    tempFile="/tmp/${1%%.*}.pdf"
    pandoc $1 -o $tempFile
    zathura $tempFile
else
    echo "Only PDF or MD files!"
    exit 1
fi

tabs2spaces
#

Another very useful script is one which you can use to improve your code quality and consistency by converting all tabs to spaces. Well, I know that the tabs vs. spaces can be sometimes a little bit controversial, but as always you can start a controversy on preferences.

To add a little advertisement for the awesome tv-show Silicon Valley, which is funny, nerdy and accurate at the same time, I can recommend you to watch the following video of this topic:

So, enough of jokes, let’s get to the script:

#!/bin/bash
find ./ -iname "$1" -type f -exec bash -c 'expand -t 4 "$0" | sponge "$0"' {} \;
This script converts every tab to 4 spaces. If you want to change that, you can simply change the -t 4 parameter to the desired number of spaces.

As you can see, the only parameter is the pattern to search for files which will be converted. To convert all .c files:

tabs2spaces "*.c"

I think you get the idea and can think of other useful patterns for your own use cases.

(better) yazi
#

This snippet is very popular and was published by yazi itself. Therefore you can find it houndred of times in different dotfiles repositories. So no, I am not the genius, I just copied it too.

Maybe I have already talked about my favourite terminal file manager, yazi. It is very simmilar to ranger, but more feature-rich (and written in Rust, ofc). But feature I missed it, using it for navigation. So if I found the directory I wanted to navigate to, I could simply use exit yazi and it would bring me to this directory in my shell prompt.

#!/bin/bash
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd
yazi "$@" --cwd-file="$tmp"
if cwd="$(command cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
	builtin cd -- "$cwd"
fi
rm -f -- "$tmp"

To use it simply call it in your shell prompt:

$ y
CLI Magic - This article is part of a series.