Making Pure Bash Useful

This past week, I was troubleshooting a docker image at work that had a number additional problems which blocked me from actually troubleshooting. Initially, I discovered that a shell wasn't installed in the common place. A few minutes later, I discovered that almost no other utilities were available on the system. How did I discover that, you ask?

$ ls
-bash: ls: command not found

What?! No ls?! haha

I did further layer inspection and found almost no useful command line utilities on the image. I had bash available to me, but essentially nothing else. No cat, no grep, no find, and of course, no ls.

But this problem got me thinking, how does one make a system usable with only bash builtins?

Bash Functions

I'm not above rewriting partial implementations of common utilities in pure bash to avoid installing yet another binary in a Docker image. Sure, there's a performance hit, but if it saves me an additional layer, additional api calls for pull, and an additional few megabytes, it's often worth it. Some of the docker images I work with are pulled thousands of times per hour, so storage optimizations are tremendously important for bandwidth, time, and cost concerns.

Not that I endorse this, but for some quick troubleshooting, this works great. More though, it's a problem that made my brain itch, so I needed to try this.

So let's get started with the first two easy ones I wrote.

ls() { for i in *; do printf '%s\n' "${i}"; done; }
cat() { while read line; do printf '%s\n' "${line}"; done < "${1}"; }

Ultimatley, those were the only two I needed for the problem I had, but we'll try to write a few more just for giggles.

find

My first implementation attempt of the find command in my notes looked like this:

find() { printf 'not implemented\n'; }

...Yeah. It was at this point I realized I had strayed from the actual problem at work, and was squarely in "fun to see if I can" territory. When you need bash function recursion in a one-liner, you might have gone too far. So I did what I needed to do at work, made a few notes, and came back to this later (now).

It's after business hours now, so let's implement this thing!

find() { [ -e "${1}" ] && printf '%s\n' "${1}"; [ -d "${1}" ] && for i in "${1}"/*; do find "${i}"; done; }

Ouch. Let's expand that so it's a bit less cryptic.

find() { \
  [ -e "${1}" ] && printf '%s\n' "${1}"; \
  [ -d "${1}" ] && for i in "${1}"/*; do find "${i}"; done;\
}

That's of course still a oneliner essentially, so don't judge my syntax. I'm not trying to solve this with good code, I'm playing code golf over here for copy-pastability.

But in short, what the above does in human friendly text:

I did a quick diff <(find ./testdir | sort) <(./find.sh ./testdir | sort) to compare the output of my function and the real find command, and mine outputs almost exactly the same as the real find. The difference is that find seems to iterrate descending, while mine iterrates ascending. ¯\_(ツ)_/¯.

grep

Oh no. Here we go, a pure bash "implementation" of grep. What fun.

grep() { while read line; do [[ "${line}" =~ ${1} ]] && printf '%s\n' "${line}"; done <"${2}"; }

Up to this point, we've been POSIX compliant. At this point, we need special Bash extensions for regular expression matching.

grep() {
  while read line; do
    [[ "${line}" =~ ${1} ]] && printf '%s\n' "${line}"
  done <"${2}"
}

A few caveats of that one. First, that is not recursive like grep -r. Second, it only works on one file. Third, it is not case insensitive (like grep -i). But! It does work!

Summary

For future use, here are all of the above function definitions in one place for easy copy-pasta. I don't plan to need or use these in the future, but it was a fun half-hour spent trying to figure out how to make pure bash functional as an interractive shell without outside utilities.

ls() { for i in *; do printf '%s\n' "${i}"; done; }
cat() { while read line; do printf '%s\n' "${line}"; done < "${1}"; }
find() { [ -e "${1}" ] && printf '%s\n' "${1}"; [ -d "${1}" ] && for i in "${1}"/*; do find "${i}"; done; }
grep() { while read line; do [[ "${line}" =~ ${1} ]] && printf '%s\n' "${line}"; done <"${2}"; }

exit 0

Written on: 2025-03-07 17:03:18 -0700

Last edited: 2025-03-08 00:06:24 UTC