1
\$\begingroup\$

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.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

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.

\$\endgroup\$
2
  • 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\$ Jan 17, 2023 at 18:44
  • 1
    \$\begingroup\$ Piping arbitrary web content into a shell is not something to be encouraged. \$\endgroup\$ Jan 18, 2023 at 11:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.