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?
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.
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:
<dir>/*
when the directory is empty)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. ¯\_(ツ)_/¯
.
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!
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