Linux Shell Functions: 8 Command Line Essentials

Once you’ve gained a solid understanding of Linux and mastered the various commands available, your next big win comes in the form of shell functions. Code wrapped in a function can be reused by your shell scripts, but you can also make it available on your command line, just like you’d use any program, built-in command, or alias.

Short functions that carry out common tasks can save you lots of time, and they’re satisfying to piece together. Here are some of the shell functions that I’ve found the most useful.


1

mkd: create a directory and enter it

I’m starting with an example of an ideal shell function: it’s useful and easy to understand, but it should help to illustrate some key concepts. At its most basic, a shell function wraps a command or two that you might often want to run together, providing convenience.

When was the last time you ran the mkdir command to create a directory, and you didn’t immediately run cd to enter that directory? I’m guessing it’s probably never, so why not complement mkdir with a version that does just this? Here’s the function:

        mkd() {
    mkdir -p -- "$1" && cd -P -- "$1"
}

You can keep all your custom shell functions in a file and source them on startup. I keep mine in a hidden directory in my home, so I run this command from my ~/.zshrc file to load them into my shell: . ~/.config/shell_funcs.sh.

The && here is a clever terminal trick that runs the second command after the first, but only if the first succeeds. In this case, there’s no point in changing to the directory if it wasn’t created, because of a permission problem, for example.

The mkdir -p option lets you create more than one level of directory: mkd docs/letters/urgentfor example. It has a nice side effect: if you run this function and pass it a directory that already exists, you won’t see any errors, but you’ll still end up changing directory anyway.

The final thing to note is those “–” arguments. This is a special argument that indicates the end of options and the start of other arguments: directory names, in this case. This is an example of defensive programming to avoid problems if you try to create a directory beginning with a “-” character. That may seem unlikely, but it’s always better to be safe than sorry.


2

prompt: set your prompt to various things

One of the first things that everyone learns about their Linux shell is how to customize the prompt. At the beginning of every command line, your shell will print text to let you know that it’s ready for input:

An example Linux prompt showing the name of the current directory.

The above prompt shows the current directory in brackets, followed by a dollar sign, but the prompt can be as minimal or informative as you like. Its value is controlled by an environment variable named PS1. You can set this to a fixed string, like “$ ,” just remember to escape the $, which has special meaning to the shell:

        PS1="$ "
    

The following function sets the prompt to something a bit more dynamic. This version checks whether you’re running bash or zsh, so it can be used by either shell. They differ slightly in their capabilities: each tries to show only the last three parts of your current path, but bash adds its own logic on top.

        
prompt() {
NEWLINE=$'n'

    if [ ! "${BASH_VERSINFO}" == "" ]; then
        PROMPT_DIRTRIM=3
        PS1="${NEWLINE}[w] $ "
    fi

    if [ ! "${ZSH_VERSION}" == "" ]; then
        PS1="${NEWLINE}[%3~] $ "
    fi
}

This prompt also includes a newline character, which is easier to add by defining a variable first. This newline creates an empty line between the output of the previous command and the next prompt, which makes it easier to group commands and their output at a glance.

Although bash and zsh handle the prompt in more or less the same way, other shells are not so consistent. Some more modern shells, like fishtake a functional approach to the prompt, offering a lot more flexibility. If you’re not using bash or zsh, check your shell’s manual to find out how you can configure the prompt.

But why have a function to do this, instead of just setting it once in your ~/.bashrc or ~/.zshrc file? Well, I find it very useful to have different prompts that I can switch to easily:

Three different types of shell prompts, switched via functions.

This is really handy for taking screenshots of my terminal when I want to hide my current path. But you could use it in other situations: to tell the difference between two terminal sessions, for example, or to save screen real estate in smaller windows.


3

trim: remove leading and trailing whitespace

This function removes whitespace from the beginning and end of each line:

        trim() {
    sed 's/^[ t]*//;s/[ t]*$//'
}

The Linux command line is all about passing data from one command to another, via pipes, files, etc. But that requires clean, predictable data, and commands aren’t always great at producing it. Take wc, for example:

A wc command showing output with extra space at the beginning of each line.

This output uses alignment to produce a nice, readable list, but it’s awkward for use in a pipeline since other commands will have to account for that leading whitespace. Instead, piping output to trim will remove extra whitespace, making further processing more straightforward:

A wc command showing output with leading space removed after piping to trim.

This trim function uses sed, the stream editor utility. It specifies two replacements, separated by a semicolon. The first looks for a run of space/tab characters at the beginning of each line, and replaces it with nothing, i.e., deletes it. The second replacement does the same, but at the end of the line.

Note that sed operates on standard input if no filename is passed to it, and calling it via a function like this does the same thing, passing the standard input to the command.


4

rgrep: a shorthand for recursive grep

Sometimes, a function is no simpler than a single command, primed with specific options. While an rgrep command may exist on your system, and do pretty much the same as this function, it’s not present on macOS, so I use this alternative:

        rgrep() {
grep -Id recurse "$@"
}

The -I option prevents grep from searching in binary files, which usually just produce errors. The -d flag specifies how grep should treat directories; in this case, “recurse” causes them to be read recursively.

Note the use of “$@” to pass any additional arguments to grep. This is important because it means you can run “rgrep” exactly as you’d run “grep,” it’ll just have your custom behavior. For example, rgrep -i todo will expand to grep -Id recurse -i all.

You can confirm how this works by adding “set -o xtrace” at the beginning of your function to print commands as they run. This is one of the best ways of polishing your shell scripts because it helps you debug exactly what’s going on under the hood.


5

ucase/lcase: convert between cases

Most programming languages have an uppercase/lowercase function; even apps like LibreOffice have the feature built in because it’s often so useful. You can easily have the equivalent on your Linux command line, where it’s even more powerful, since it can operate on every line of a file that you send:

        ucase() {
    tr '[:lower:]' '[:upper:]'
}

lcase() {
    tr '[:upper:]' '[:lower:]'
}

This pair of complementary functions uses the tr command, which performs simple transformations on a stream of text.

tr takes two arguments: a list of characters to search for, and a list of characters to replace them with. Each replacement character acts on the corresponding one in the search string, so “tr ‘AB’ ‘ab’” will convert “A” into “a” and “B” into “b.” The special character classes “[:upper:]” and “[:lower:]” expand to the full set of uppercase and lowercase characters in the current locale.


6

today: get the current date in short ISO format

        today() {
    date '+%Y-%m-%d'
}

Forget the difference between US dates and the rest of the world for the moment: this date format isn’t really used by anyone, but it’s hands-down my favorite. The reason is simple: dates in this form will sort naturally, and that’s a huge bonus in my book. I use the format in plenty of places: filenames, headings in documents, databases, etc.

This may be another simple command shortcut, but it’s a very convenient one. The exact date format is annoying to remember: the “+” prefix, the “%” symbols, and getting the case of each part correct. I much prefer to forget about all of that and just type today instead: highly satisfying!


7

filesize: show files in the current directory, ordered by size

A common maintenance task I find myself carrying out involves cleaning out big files. While ncdu is a great way to locate large filesit’s sometimes overkill. What I often want is to see just which files in my current directory are the biggest.

        filesize() {
du -sk * | sort -n
}

du is the disk usage tool; it reports how much space each file takes up, including directories, which it reports on recursively. So this function will tell you the total size of any directories present, making it easier to drill down through your file system and weed out any unwanted files.


8

paths: print the PATH variable, nicely

        paths() {
echo $PATH | tr ':' 'n'
}

Another maintenance task I like to carry out from time to time is cleaning up my PATH. macOS has a nasty habit of adding extra directories to the environment variable, including some that don’t actually exist! But because this variable uses colons to separate each path, it can be pretty tricky to read:

The value of a PATH variable showing one long line containing many paths separated by colons.

This function makes the output a lot more palatable:

The paths command showing the contents of a PATH variable with one path per line.

It’s another great use for that brilliant tr program, and you could extend this further: by checking that each directory in your PATH exists, for example:

        check_paths() {
    paths | while read path
    do
        if [ ! -d "$path" ]; then
            echo "bad PATH: dir does not exist: $path" >&2
        fi
    done
}

Related Posts

Leave a Comment