As some of you know, I have made a programming language, called AEC, and written two compilers for it, one targetting x86 and other targetting WebAssembly. Recently, I have tried to write two shell scripts to download, compile and use those compilers, and run the Analog Clock example. I have put those two shell scripts on my blog, and it is important for me that they run on as many computers as possible. Here is what I've made by now.
Shell script to use the AEC-to-x86 compiler:
mkdir ArithmeticExpressionCompiler
cd ArithmeticExpressionCompiler
if [ $(command -v wget > /dev/null 2>&1 ; echo $?) -eq 0 ] # Check if "wget" exists, see those StackOverflow answers for more details:
# https://stackoverflow.com/a/75103891/8902065
# https://stackoverflow.com/a/75103209/8902065
then
wget https://flatassembler.github.io/Duktape.zip
else
curl -o Duktape.zip https://flatassembler.github.io/Duktape.zip
fi
unzip Duktape.zip
if [ $(command -v clang > /dev/null 2>&1 ; echo $?) -eq 0 ] # We prefer "clang" to "gcc" because... what if somebody tries to run this in CygWin terminal? GCC will not work then, CLANG might.
then
c_compiler="clang"
else
c_compiler="gcc"
fi
$c_compiler -o aec aec.c duktape.c -lm # The linker that comes with recent versions of Debian Linux insists that "-lm" is put AFTER the source files, or else it outputs some confusing error message.
if [ "$OS" = "Windows_NT" ]
then
./aec analogClockForWindows.aec
$c_compiler -o analogClockForWindows analogClockForWindows.s -m32
./analogClockForWindows
else
./aec analogClock.aec
$c_compiler -o analogClock analogClock.s -m32
./analogClock
fi
Shell script to use the AEC-to-WebAssembly compiler:
if [ $(command -v git > /dev/null 2>&1 ; echo $?) -eq 0 ]
then
git clone https://github.com/FlatAssembler/AECforWebAssembly.git
cd AECforWebAssembly
elif [ $(command -v wget > /dev/null 2>&1 ; echo $?) -eq 0 ]
then
mkdir AECforWebAssembly
cd AECforWebAssembly
wget https://github.com/FlatAssembler/AECforWebAssembly/archive/refs/heads/master.zip
unzip master.zip
cd AECforWebAssembly-master
else
mkdir AECforWebAssembly
cd AECforWebAssembly
curl -o AECforWebAssembly.zip -L https://github.com/FlatAssembler/AECforWebAssembly/archive/refs/heads/master.zip # Without the "-L", "curl" will store HTTP Response headers of redirects to the ZIP file instead of the actual ZIP file.
unzip AECforWebAssembly.zip
cd AECforWebAssembly-master
fi
if [ $(command -v g++ > /dev/null 2>&1 ; echo $?) -eq 0 ]
then
g++ -std=c++11 -o aec AECforWebAssembly.cpp # "-std=c++11" should not be necessary for newer versions of "g++". Let me know if it is, as that probably means I disobeyed some new C++ standard (say, C++23).
else
clang++ -o aec AECforWebAssembly.cpp
fi
cd analogClock
../aec analogClock.aec
npx -p wabt wat2wasm analogClock.wat
if [ "$OS" = "Windows_NT" ] # https://stackoverflow.com/a/75125384/8902065
# https://www.reddit.com/r/bash/comments/10cip05/comment/j4h9f0x/?utm_source=share&utm_medium=web2x&context=3
then
node_version=$(node.exe -v)
else # We are presumably running on an UNIX-like system, where storing output of some program into a variable works as expected.
node_version=$(node -v)
fi
# "node -v" outputs version in the format "v18.12.1"
node_version=${node_version:1} # Remove 'v' at the beginning
node_version=${node_version%\.*} # Remove trailing ".*".
node_version=${node_version%\.*} # Remove trailing ".*".
node_version=$(($node_version)) # Convert the NodeJS version number from a string to an integer.
if [ $node_version -lt 11 ]
then
echo "NodeJS version is lower than 11 (it is $node_version), you will probably run into trouble!"
fi
node analogClock
So, what do you think, how can I improve those shell scripts? The most important thing for me is that they run on as many computers as possible, simply by being copied-and-pasted into a terminal emulator.
2 Answers 2
First things first: neither script has a #!
line to select a specific shell. If it's intended to be portable, I recommend #!/bin/sh
. And set some options: I recommend using both -u
and -e
to simplify the error-handling.
This is an anti-pattern:
[ $(command -v wget > /dev/null 2>&1 ; echo $?) -eq 0 ]
There's no need to print out the exit status like that - just use it directly:
command -v wget > /dev/null 2>&1
We have a lot of repetition, particularly of URLs. Fix that using variables, so that it's easier to change the hosting.
All that said, I think it's probably better to use existing tools such as Autoconf, and include that in your repo so that users can simply git clone && ./configure && make
in the usual manner without having to do something special for your sources.
You will probably want to supply a makefile pattern rule that matches *.aec
files.
most important thing ... is that they run on as many computers as possible, simply by being copied-and-pasted into a terminal emulator.
Those scripts seem a little on the long side.
Which invites user error from coordinating a copy-n-paste with a window scroll operation. And the longer the pasting, the greater the chance of buffer overrun. I habitually put long pastes into Aquamacs and then ask Terminal.app to run the resulting file, so final characters won't be lost.
No need to paste in the compilation instructions, as that could be pushed down into a ZIPped script.
Some software distributors go with a one-liner, like
$ bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
I've seen others go with $ curl -s $URL | bash
.
Which brings us to the assumption of bash
, rather than sh
.
It's fine to make that assumption -- just explicitly document it.
The $(command -v foo)
expression is an interesting one.
Personally I tend to run which foo 2>&1 > /dev/null
and examine the exit status $?
,
but then I usually just target unix-like platforms.
You might at some point delegate to a split pair of scripts
for unix and for windows.
If running foo
is harmless, e.g. clang --version
,
then a possibly simpler approach is to just run it,
with redirect, and inspect $?
to see whether it worked.
$(node.exe -v)
Maybe $(node -v)
would suffice, even on windows?
After all, we later run node analogClock
.
Rather than "NodeJS version is lower than 11 ... you will probably run into trouble!", you might want to put a stake in the ground and refuse to run on 10. Either it's a supported and tested configuration, or it's not.
Consider publishing a version matrix of test results, showing combinations you know pass their automated tests.
-
2\$\begingroup\$ Maybe $(node -v) would suffice, even on windows? No, it would not. It would output the "stdout is not a tty" error message instead of the version number. \$\endgroup\$FlatAssembler– FlatAssembler2023年01月17日 18:44:56 +00:00Commented Jan 17, 2023 at 18:44
-
1\$\begingroup\$ Piping arbitrary web content into a shell is not something to be encouraged. \$\endgroup\$Toby Speight– Toby Speight2023年01月18日 11:22:31 +00:00Commented Jan 18, 2023 at 11:22