Using Erlang as a scripting language - escripts

I like using Erlang as a scripting language instead of bash. Actually I had been using python quite a bit but erlang is more elegant and powerful I find. I like just creating a text file, new_command.escript, and kicking off my main function and building out smart scripting capabilities on the fly - generally processing files and navigating directories. Erlang is quite expressive on its own because I had not realized until yesterday that I’m not using any external libraries in my escript. That is until yesterday when I wanted to process some JSON by using the jsx ( or any other JSON ) library. Suddenly it seems this cannot be done without invoking a lot of rebar3 commands and build up a directory structure and all kinds of dependencies like a full blown Erlang app - exactly what I’m trying to avoid. I just want my escript text file and execute the thing.

Hopefully I’m missing something obvious but I’m on my second day trying this and no joy. Anyone know how I can do this? If it isn’t supported then by golly this is a major feature improvement I really need if I’m going to keep using Erlang as a scripting language.

2 Likes

Depending on your Erlang version. But if you use otp 27 you maybe can use native json?

1 Like

Thanks - I will try that but really I am looking for a general solution to be able to access external libraries from within a simple escript file and not have to go through all this heavy weight infrastructure.

Could you please give a bit more context on how you generate your script?

I’m not 100% sure that I understood the problem, but I’ve just pushed an example repo, and when I generate the Erlang script, there are no extra files or folders:

It contains a dependency:

{deps, [euneus]}.

You can try downloading and running the program without cloning the project: jsondecode/jsondecode at main · williamthome/jsondecode · GitHub

$ chmod +x ./jsondecode
$ ./jsondecode "[0,null,{\"foo\": \"bar\"}]"
Result: [0,null,#{<<"foo">> => <<"bar">>}]
1 Like

An escript is supposed to be stand-alone, which means it’s either just a single file, or (internally) an archive of .beam files in a standardized layout – in that case it’s not a plain text file any more but a shell script header on a binary archive (zip I believe). Either way, rebar3 makes it really easy to build your application and package it as an escript.

If you’re hell-bent on having a single plain text file and dependencies taken from “elsewhere”, then you need to:
(1) install those dependencies “elsewhere”,
(2) have a script (escript or Unix shell) that adds those dependencies to Erlang’s code path – you can do that as Erlang starts or dynamically after it’s started.

I’ve use both approaches:
a) I have a set of Unix-style command-line tools built from a collection of applications and libraries, each packaged by rebar3 as an escript containing an archive.
b) I have simple Scheme interpreter which is just a Bourne shell script that roughly does

#!/bin/sh
EBIN_DIR=/path/to/the/installed/dependencies
exec erl -pa ${EBIN_DIR} -noshell -run mymain start

I don’t mean to detract from the Erlang conversation, but maybe @scherrey is not familiar with Elixir’s Mix.install/2 feature. If a solution that includes Elixir is not exactly welcome in the Erlang Forum (I can understand that), please feel free to flag my post and folks could take action if needed. I just wanted to share what I found, mostly because I thought it was interesting, I hope you do as well.

Here is a potential option using Mix.install/2 and here are the docs: Mix — Mix v1.17.2

More examples here: GitHub - wojtekmach/mix_install_examples: A collection of simple Elixir scripts that are using Mix.install/2.

You can call Erlang directly from the Elixir script or you can compile an Erlang file and call it in the Elixir script.

The example below can maybe get you closer to what you originally wanted:

With this Erlang file:

# ef_erlang.erl
-module(ef_erlang).
-export([greet/0]).

greet() ->
    io:format("Hello, World, from the Erlang file!~n").

And this Elixir file:

# ef_elixir.exs
Mix.install([:jason])

# Showing here to show how the json problem is can be solved in Elixir
# (note fetching the dependency above, `jason`
{:ok, encoded} = Jason.encode(%{a: 1}) |> IO.inspect()
{:ok, _decoded} = Jason.decode(encoded) |> IO.inspect()

:io.format("You can call Erlang directly here, like this.\n\n")

:io.format("And you can compile and call your Erlang code here as well, like this ->\n\n")

file_path = [File.cwd!(), "ef_erlang.erl"] |> Path.join() |> String.to_charlist()
:compile.file(file_path)

# Finally, call your function:
:ef_erlang.greet()

You can run elixir ef_elixir.exs and get the following output:

$ elixir ef_elixir.exs
{:ok, "{\"a\":1}"}
{:ok, %{"a" => 1}}
You can call Erlang directly here, like this.

And you can compile and call your Erlang code here as well, like this ->

Hello, World, from the Erlang file!
$
1 Like

Addendum: please read code — kernel v10.0.1 especially the section about the code path, and the functions available to modify it at runtime.

I just use

# zypper install jsx