Drop #473 (2024-05-23): What The Shell?!

amber; bish; bash_tls

We cover quite a bit of shell scripting, here at the Drop, but today we go the extra mile and look at two ecosystems that let you compile special-purpose languages to Bash, and one absolutely bonkers insane project that you can use in a pinch if curl isn’t around and you cannot install it.

7 min read 👀


TL;DR

(This is an AI-generated summary of today’s Drop)

  • Amber: Amber is a modern programming language that compiles into Bash scripts, offering features like type safety, proper floating point arithmetic, and array handling. It aims to provide a more robust and developer-friendly experience for writing shell scripts. Amber
  • Bish: Bish is another language that compiles to Bash, designed to modernize shell scripting with a more intuitive syntax. It addresses the cumbersome nature of traditional Bash scripting, making it easier to write and debug scripts. Bish
  • bash_tls: bash_tls is a minimal TLS 1.2 client implementation in pure Bash, allowing encrypted communication with some web servers. It is useful in environments where installing additional tools like curl is not possible. bash_tls

amber

Photo by Moussa Idrissi on Pexels.com

Shells, even “modern” POSIX (or, even non-POSIX) shells have alot of footguns. Even with tools like shellcheck, you may still end up losing a few toes. One great thing about shells like Bash and Zsh is that you can nigh guarantee a target system has one of them around. But, you have to bring along a ton of your own batteries to have all the functionality you may need for any given task.

Sure, one could use Go or Rust to compile and build CLI tools, but those generate opaque binaries that are often gargantuan in size.

What if we could get the benefits of these modern compiled languages, but have the target of compilation be a shell script vs. a system-specific binary?

Turns out, we can, thanks to amber (GH)!

Amber is a modern programming language that compiles into Bash Script, designed with a syntax inspired by ECMAScript, Rust, and Python. It offers safety features by halting execution if edge cases aren’t handled, type safety to catch bugs at compile time, and practical functionalities lacking in Bash such as proper floating point arithmetic, array handling, passing variables by reference, and a standard library with utilities like text manipulation and math functions. Amber aims to provide a more robust and developer-friendly experience for writing shell scripts.

It has two hard dependencies: Bash and bc (a CLI calculator that’s likely either already installed, or is one package manager incantation away).

Amber feels alot like JavaScript. Take this super basic example:

let publication = "hrbrmstr's Daily Drop"
let issue = 473

echo "This is Issue #{issue} of {publication}!"

if issue < 500 {
  echo "We're just shy of 500 issues!"
} else {
  echo "I cannot believe we've had this many issues!"
}

let favs = ["subscribers", "readers", "commenters"]
echo "My favorite humans are {publication}:"
loop fav in favs {
  echo fav
}

which compiles to this Bash:

__0_publication="hrbrmstr's Daily Drop";
__1_issue=473;
echo "This is Issue #${__1_issue} of ${__0_publication}!";
if [ $(echo ${__1_issue} '<' 500 | bc -l | sed '/\./ s/\.\{0,1\}0\{1,\}$//') != 0 ]; then
  echo "We're just shy of 500 issues!"
else
  echo "I cannot believe we've had this many issues!"
fi;
__AMBER_ARRAY_0=("subscribers" "readers" "commenters");
__2_favs=("${__AMBER_ARRAY_0[@]}");
echo "My favorite humans are ${__0_publication}:";
for fav in "${__2_favs[@]}"
do
  echo "${fav}"
done

It has five core data types:

  • Text: The textual data type. In other programming languages it can also be called “string”.
  • Num: The numeric data type. It’s basically any number.
  • Bool: The boolean data type. It’s value can be either true or false.
  • Null: The nothing data type.
  • []: The array data type.

plus the cadre of expressions and operators you’d expect to have at hand.

Rather than deal with hieroglyphics to deal with command execution status codes, amber lets you “catch” error conditions just like you might in real programming languages:

let file_path = "/path/to/file"
$cat {file_path}$ failed {
    echo "Could not open '{file_path}'"
}
__0_file_path="/path/to/file";
cat ${__0_file_path}
__AMBER_STATUS=$?;
if [ $__AMBER_STATUS != 0 ]; then
  echo "Could not open '${__0_file_path}'"
fi

The last release was in 2023, but the language and tooling are under active development.

bish

Bish — like amber — is a language that compiles to Bash, and was designed to help modernize shell scripting. We didn’t explicitly call this out in the section on amber, but traditional Bash scripting can be cumbersome, with its syntactical quirks often leading to frustrating debugging sessions. Bish addresses these issues by offering a more intuitive and modern syntax while maintaining the portability of Bash.

The underlying tooling is C++-based (amber’s is Rust), and this is a fairly old project, so you’ll need to make one change to get it to compile.

Edit line 19 in src/Util.h to be this:

return std::move(std::ostringstream() << i).str();

Once you have a working bish executable, you can start compiling! NOTE: bish feels alot more like icky Python or Ruby than amber does, which may be your thing, but it 100% not mine.

Here’s a slightly more complex example than what we made for amber:

#!/usr/bin/env bish -r
# fibonnaci!
def fib(n) {
  if (n < 2) {
    return 1
  }
  return fib(n-1) + fib(n-2)
}

def test() {
  assert(fib(5) == 8)
  println("Fib test passed.")
}

test()

That code compiles down to this monster:

#!/usr/bin/env bash
# Autogenerated script, compiled from the Bish language.
# Bish version 0.1
# Please see https://github.com/tdenniston/bish for more information about Bish.

function bish_fib () {
    local n="$1";
    if [[ $n -lt 2 ]]; then
        _global_retval_0=1;
        return;
    fi;
    local _0=$(($n - 1));
    local _1=$(($n - 2));
    bish_fib "$_0";
    local _rv_1="$_global_retval_0";
    bish_fib "$_1";
    local _rv_2="$_global_retval_0";
    _global_retval_0=$(($_rv_1 + $_rv_2));
    return;
}

function bish_test () {
    local _2=5;
    bish_fib "$_2";
    local _rv_5="$_global_retval_0";
    local _3=$([[ $_rv_5 -eq 8 ]] && echo 1 || echo 0);
    stdlib_assert "$_3";
    local _4="Fib test passed.";
    stdlib_println "$_4";
}

function bish_main () {
    bish_test;
}

function stdlib_assert () {
    local v="$1";
    if [[ $v -eq 0 ]]; then
        local _0="Assertion failed.";
        stdlib_println "$_0";
        exit 1;
    fi;
}

function stdlib_println () {
    local s="$1";
    echo -e "$s";
}
args=( $0 "$@" );
{
    : # Empty function
}
bish_main;

bash_tls

Fitting squarely in the “But…WHY?!” category, bash_tls is a project that provides minimal TLS 1.2 client implementation in a pure Bash script.

Yes. You read that correctly.

This means you can grab some encrypted sites (many no longer work with TLS 1.2 handshakes).

It requires a modern Bash interpeter, so macOS folks will need to use Homebrew and also change the shebang to #!/usr/bin/env bash to get it to work. But, it does, indeed work.

./bash_tls.sh https://example.com/         
running handshake ...
sending request ...
receiving response ...
HTTP/1.1 200 OK
Accept-Ranges: bytes
Age: 92585
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Fri, 24 May 2024 20:25:52 GMT
Etag: "3147526947+gzip"
Expires: Fri, 31 May 2024 20:25:52 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECAcc (bsb/2792)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256
Connection: close

<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
        
    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 2em;
        background-color: #fdfdff;
        border-radius: 0.5em;
        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        div {
            margin: 0 auto;
            width: auto;
        }
    }
    </style>    
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

Crazytown.

FIN

Remember, you can follow and interact with the full text of The Daily Drop’s free posts on Mastodon via @dailydrop.hrbrmstr.dev@dailydrop.hrbrmstr.dev ☮️

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.