Phil Nelson’s UNIX Systems class at WWU guides each student through a series of six assignments, each adding more functionality to a basic shell program. At it’s start, msh
can fork
and eval
, but not much else. By the end of the 10 week class, both the minishell and the students are much more capable.
Features Implemented
- Command-line parameters (inluding quoted strings and escape characters)
- Comments
- Built-in commands
- Environment variable expansion
- Nested commands— for example,
envset file_contents $(cat ${FILENAME})
- Signal handling
- Pipelined commands
- Input and output redirection
- If statements and While loops (this implies that the shell interpreter is Turing Complete!)
- Various other expansions (for example,
$?
is the exit code of the last command and$$
is the PID.)
Because this was a school project and the class is still being taught, I can’t provide source code here. However, if you’re interested (and not a student at WWU!), send me an email about it.
Sieve of Eratosthenes
Most of the grade for each minishell assignment is determined by a test script. The scripts are provided by the professor and they exercise the features required at each step. The test scripts are famously picky.
(For example: I once struggled for hours to figure out why a particular line of my output was getting rejected. It turned out I was outputting the NUL-character in my NUL-terminated string. Since NUL is not printed, my output looked identical but was rejected by diff
).
Once I’d passed the final test script, I could have called it quits. However, I decided to play with my newly finished minishell and write a test of my own.
With the addition of If statements and While loops, the language interpreted by the minishell is now Turing Complete. This means that it is (theoretically) possible for msh
to compute anything that can be computed. I decided to take advantage of this, and implemented the Sieve of Eratosthenes in the language defined by my minishell.
Implementing this algorithm in the restricted environment of the minishell involved some complicated workarounds. For example, the only variables we have are environment variables (strings). Since the algorithm calls for an array of numbers, I use a space-separated string of base 10 numbers. To get the first element of this ‘array’, I used sed
and a regex capture for a space-terminated series of digits. Anyway, here is the code, in all its ugliness.
aecho -n "Enter the last number to check for prime-iness: "
read MAX
while [ -n "$(echo ${MAX} | sed -E "s/[0-9]*//g")" -o ${MAX} -ge 274 ]
if [ ${MAX} -ge 274 ]
aecho "Sorry, the line buffers are too short to hold that many numbers."
aecho -n "Please enter a number: "
else
aecho -n "'${MAX}' is not a number. Please enter a number: "
end
read MAX
end
envset IX 2
while [ ${IX} -le ${MAX} ]
envset NUMS "${NUMS} ${IX}"
envset IX $(expr ${IX} + 1)
end
aecho -n "Would you like to display composite numbers as well? (y/n) "
read SHOW_COMP
while [ ${SHOW_COMP} != "y" -a ${SHOW_COMP} != "n" ]
aecho -n "Please enter a 'y' or 'n' "
read SHOW_COMP
end
#While NUMS is not empty
while echo ${NUMS} | grep "[0-9]" > /dev/null
#Copy NUMS to TNUMS, delete NUMS
envset TNUMS "${NUMS}"
envunset NUMS
#set PRIME to the first element of TNUMS
if [ -n "$(echo ${TNUMS} | sed -E "s/[0-9]*\s+(.*)?/\1/g")" ]
envset PRIME "$(echo ${TNUMS} | sed -E "s/([0-9]*)\s+.*/\1/g")"
else
envunset TNUMS
end
#remove the first element of TNUMS
if [ -n "$(echo ${TNUMS} | sed -E "s/[0-9]*\s*(.*)?/\1/g")" ]
envset TNUMS "$(echo ${TNUMS} | sed -E "s/[0-9]*\s+(.*)?/\1/g")"
else
envunset TNUMS
end
echo ${PRIME} is a prime!
#while TNUMS is not empty
while echo ${TNUMS} | grep "[0-9]" > /dev/null
#set CAND to the first element of TNUMS
envset CAND "$(echo ${TNUMS} | sed -E "s/([0-9]*)(\s+.*)?/\1/g")"
#remove the first element of TNUMS
if [ -n "$(echo ${TNUMS} | sed -E "s/[0-9]*\s+(.*)?/\1/g")" ]
envset TNUMS "$(echo ${TNUMS} | sed -E "s/([0-9]*)(\s+.*)?/\2/g")"
else
envunset TNUMS
end
#if PRIME is a factor of CAND
if [ $(expr ${CAND} % ${PRIME}) -eq 0 ]
if [ ${SHOW_COMP} = y ]
echo " ${CAND} is composite: ${PRIME} is a factor"
end
else
envset NUMS "${NUMS} ${CAND}"
end
end
end