Giter Site home page Giter Site logo

hush's Introduction

Logo

Hush

Hush is a Unix shell scripting language inspired by the Lua programming language.

Check the homepage for more details.

hush's People

Contributors

a-moreira avatar aghast avatar caioraposo avatar deaddog avatar gahag avatar gahag-tw avatar meow-sokovykh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hush's Issues

Odd behaviour with overwrite then append in command block

Running the following hush script multiple times results in unexpected behaviour.

test.hsh

#!/usr/bin/env hush

let now = ${ date "+%s" }.stdout
let stripped_now = std.split(now, "\n")[0]
let arr = [ "1", "2" ]
{
	echo $stripped_now > test_file;
	echo $arr >> test_file
}

Run once


$ ./test.hsh; cat test_file
1652491810
1 2

Run twice

$ rm test_file; ./test.hsh; cat test_file; sleep 2; echo "************"; ./test.hsh; cat test_file
1652491824
1 2
************
1652491827
1 2
1 2

Huh? Notice the time has changed, but now there are two copies of the array at the end of the file...?

Run three times

$ rm test_file; ./test.hsh; cat test_file; sleep 2; echo "************"; ./test.hsh; cat test_file; sleep 2; echo "************"; ./test.hsh; cat test_file
1652491843
1 2
************
1652491845
1 2
1 2
************
1652491847
1 2
1 2
1 2

Again the time is changing in subsequent runs, but now there are three copies of the array appended after the third run.

Run three times with an extra append before the third

% rm test_file; ./test.hsh; cat test_file; sleep 2; echo "************"; ./test.hsh; cat test_file; sleep 2; echo "************"; echo "hi there" >> test_file; ./test.hsh; cat test_file 
1652491862
1 2
************
1652491864
1 2
1 2
************
1652491866
1 2
1 2
hi there
1 2

How did the "hi there" end up in the file? The file test_file should be completely overwritten each time test.hsh is run by the first line in the command block of the script.

I'm really confused....

Add examples

Hi all,
I recently saw Hush and wanted to take a quick look at the docs. There is a lot of text about what Hush is trying to accomplish, etc... but no examples!

It would be great to see a quick example on the homepage of what a script in Hush looks like.

Parse error when std.import'ed script contains executable statements

Consider two files:

importer.hsh

let mcve = std.import("mcve.hsh")

mcve.hsh

std.print("Hello")
[
	[ 1, 2],
	[ 3, 4],
]

Configured thus, when I invoke hush importer.hsh I get these errors:

$ hush importer.hsh
Error: /home/aghast/Code/aghast/hush-larn/mcve.hsh (line 3, column 8) - unexpected ',', expected ']'
Error: /home/aghast/Code/aghast/hush-larn/mcve.hsh (line 4, column 8) - unexpected ',', expected expression
Error: /home/aghast/Code/aghast/hush-larn/mcve.hsh (line 5, column 0) - unexpected ']', expected expression
Panic in importer.hsh (line 1, column 21): failed to import module (/home/aghast/Code/aghast/hush-larn/mcve.hsh)

But if I comment out the print statement, there is no problem.

My expectation is that the inner script (mcve.hsh) is to be executed immediately, and the last expression evaluated will provide the return value for the std.import() function. But what appears to be happening is some kind of different parse mode(?) where the syntax is slightly different? I'm not sure if this is a bug, (IMO: yes!) or a documentation failure, or what.

Feature: Add symbols for interpreter, program, and script

As things presently stand, the following files can all be different:

  • the "installed" hush interpreter (installed via cargo install)
  • the "running" hush interpreter (perhaps due to cargo test in a hush git repo)
  • the current "hush program" being run (invoked with hush foo.hsh)
  • the current hush-script file being executed (perhaps via std.import("bar.hsh"))

In many cases (most cases?) the first two will be identical. If they are not identical, the user
probably doesn't actually care about the installed interpreter.

In many cases the last two will be identical. If they are not identical, it matters and the coder had better be able to figure out what he wants to talk about. (Are we talking about my library, or your program that uses my library.)

Some languages (e.g., python) hide this stuff in a sys. namespace. This is not quite standard-library stuff, since the data is subject to change a lot. Bash, and GnuMake, tend to treat the last two as the opposite ends of a stack or list, with the "program" being the root of the stack and the current script being the leaf. C provides __FILE__ for one and nothing for the other, although glibc provides different versions of the program name (and __FILE__).

IMO this is a language-design issue. It makes sense to have a standard way to get this data, but it could be the result of a function call (std.interp_info()) or it could be a separate dict/namespace (sys.interpreter, sys.argv0, sys.this_file) or it could be special variables, a la Perl5: $#, $^P, etc.

Async not working

For the following code:

let handle = &{
  sleep 2;
  echo Hello world!;
}

# Do some other work before calling join.
# This may be printed before or after "Hello world!".
std.print("Doing some work...")

# This will wait until the block runs to completion, and will return it's result.
let result = handle.join()

std.assert(result == nil)

I'd expect Doing some work... to be printed, and then Hello world!. Instead, it sleeps without any output, and then prints:

Hello world!
Doing some work..

Hush 0.1.4 41adf74

Can't use `elseif` and `else if` with one `end`

From this docs it says that i can use

if condition then
    # ...
elseif condition then
    # ...
end

however, if i try

#!/usr/bin/env hush

let args = std.args()

if std.len(args) == 0 then 
    std.print("nil")
elseif args[0] == "a" then 
    std.print("a")
elseif args[0] == "b" then 
    std.print("b")
elseif args[0] == "c" then 
    std.print("c")
end

i got

❯ hush example.hsh
Error: example.hsh (line 7, column 22) - unexpected 'then', expected expression
Error: example.hsh (line 9, column 22) - unexpected 'then', expected expression
Error: example.hsh (line 11, column 22) - unexpected 'then', expected expression
Error: example.hsh (line 7, column 0) - undeclared variable 'elseif'
Error: example.hsh (line 9, column 0) - undeclared variable 'elseif'
Error: example.hsh (line 11, column 0) - undeclared variable 'elseif'

then i tried

if std.len(args) == 0 then 
    std.print("nil")
else if args[0] == "a" then 
    std.print("a")
else if args[0] == "b" then 
    std.print("b")
else if args[0] == "c" then 
    std.print("c")
end

i got

Error: unexpected end of file
Error: unexpected end of file
Error: unexpected end of file

turns out i can do

if std.len(args) == 0 then 
    std.print("nil")
else if args[0] == "a" then 
    std.print("a")
else if args[0] == "b" then 
    std.print("b")
else if args[0] == "c" then 
    std.print("c")
end end end end

which means i have to do this pattern

if std.len(args) == 0 then 
    std.print("nil")
else
    if args[0] == "a" then 
        std.print("a")
    else 
        if args[0] == "b" then 
            std.print("b")
        else
            if args[0] == "c" then 
                std.print("c")
            end
        end
    end
end

after digging the issues in this repo i found that #31 adding support for elseif for else if variant, but #30 is closed and not merged? (or i just don't understand github)

btw im on version 0.1.4

❯ hush --version
Hush 0.1.4

is this intentional or not? thank you

External: Neovim syntax highlighting and static checks

Hiya, did not know where to put this, so just putting it here for now in case anyone else is interested.

I made a tree-sitter grammar for Hush - along with set-up instructions: https://github.com/MikaelElkiaer/tree-sitter-hush

And a simple null-ls set-up as well: https://github.com/MikaelElkiaer/nvim-config/blob/d2ef0b9fcd9d173e54748c7ea974ef48ca0f4745/lua/user/lsp/null-ls.lua#L11

These two will provide syntax highlighting and static checks in Neovim.

`elseif` or `switch`?

There doesn't seem to be any support for multi-branch conditions. Is that right?

If not, then it would be great to have either an elseif or a switch statement.

This feels somewhat clunky, but maybe I'm missing an obvious solution.

let x = 4

if x > 10 then
    std.print("x > 10")
else
    if x > 5 then
         std.print("x > 5")
    else
        std.print("x >= 4")
    end
end

The binary doesn't work on my machine

Hello,

I downloaded the binary archive (hush-0.1.1-x86_64.tar.gz) and extracted it. When I try to run it, I get the following error:

$ hush
hush: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by hush)
hush: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by hush)
hush: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by hush)

My system is as follows (basically a bare bones Ubuntu LTS 20.04):

$ uname -srvmpo
Linux 5.13.0-40-generic #45~20.04.1-Ubuntu SMP Mon Apr 4 09:38:31 UTC 2022 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.4 LTS
Release:        20.04
Codename:       focal
$ ldd --version
ldd (Ubuntu GLIBC 2.31-0ubuntu9.7) 2.31

It looks like my glibc is too old (2.31). Would it be possible to provide a more "universal" binary, that would work on any major LTS that is still supported?

Note: running the binary on an up to date Archlinux is no problem.

Many thanks for looking at it.

Feature: Add 'File' support that can cross the curly-block barrier

Presently, the grammar supports redirection using hard-coded numbers, nothing else.

{ foo 2> error.log }

Notably, there is no mechanism for variable expansion within a redirection:

{ foo $out_fh> error.log }

In addition, there does not appear to be a way to pass file across the {}-barrier from "hush code" to "command code". For example, there isn't a way to open a file, advance to a particular offset, then begin writing to that position in the file from a command block:

fh = fopen('some_file.txt')
fseek(fh, 100)
{ foo > $fh }

Note:

This seems like an obvious "new type". It might be a dictionary wrapped around something (a bunch of methods, plus some internal data like fileno or something), but it kind of sets the stage for "How do you add new types that require compiled code for support?" So that is probably an important question to answer, too!

[Suggestion] Add global prelude

Hush shell draws inspiration from Rust, Python, Lua.

Rust has the concept of prelude that allows using println!, vec!, Box, Option/Result etc. without any imports.

Python has built-in functions. Hush has a standard library, where plenty of its functions are even called the same as in Python

  • print
  • int
  • float
  • bytes
  • exit
  • range
  • type
  • etc.

However, in Hush, they can be accessed only via std.<function_name> which I find unnecessary verbose. So, the suggestion is pretty straightforward: to add many functions to global prelude, make them available out of the box.

Actually, that would make Hush on par with shells it's trying to improve on, such as bash or zsh. echo, cd, cwd, sleep, exit, read are built-ins there. std.cd instead cd or std.print instead of print / echo in Hush scripts looks like a downgrade compared to bash.

Regex doesn't work due to UTF-8

let timestamp_regex = std.regex("\\d{2}:\\d{2}:\\d{2}(\\.\\d)?")
std.print(timestamp_regex)
std.assert(std.type(timestamp_regex) != "error")

Results in:

error: "invalid regex" ("regex parse error:\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n1: \\d{2}:\\d{2}:\\d{2}(\\.\\d)?\n   ^^\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nerror: Unicode-aware Perl class not found (make sure the unicode-perl feature is enabled)")
Panic in ./crop.hsh (line 8, column 10): assertion failed

Exit from script early?

I don't see a std.exit() function in the docs, and std.panic() seems harsh and exits with a nonzero exit code...

Hush hangs when trying to run on macOS

Installed hush via source build using cargo but running hush from zsh just hangs unless I pass --help or --version.

OS Version: macOS Monterey 12.3.1
Hush Version: Hush 0.1.2
Cargo Version: cargo 1.60.0

Support capturing output for async commands

According to the docs, only work for non-captured command blocks. It would be really handy to support captured command blocks. Something like:

let handle = &${ echo Hello world! }

# Do some other work before calling join.
# This may be printed before or after "Hello world!".
std.print("Doing some work...")

# This will wait until the block runs to completion, and will return it's result.
let result = handle.join()

std.assert(std.type(result) != "error")
std.assert(result.stdout == "Hello world!")

More editor support

Are there plans to add more editor support (syntax higlighting) like for example vim/neovim? I would love to play around with the language but is hard to do so in the current editor without higlighting

Great project btw looks very promising

Running script has no output unless run inside another program

I have a test script based off the doc website

function check_limit(x)
    if x > 10 then
        std.error("x cannot be bigger than 10", x)
    else
        x
    end
end

let result = check_limit(11)

std.print("saying somthing")
if std.type(result) == "error" then
    std.print("Error: ", result)
else
    std.print("Success: ", result)
end


However when I run either using the debug or release builds on an M1 MBA I get no output from scripts. All other commands have no issues. Also if I try to run something like time ./hush test.hsh the output is shown.

std.length is improperly documented

The docs for std say that there is a .length function, but this is not true for version 0.1.1 (which is the version that was downloaded by default using cargo).

The actual function name is .len. Could these docs be updated? Thanks!

Question: std.catch with parameters

I'm trying to handle integer parsing error and the best I came up is this:

function to_int(x)
    return std.catch(
        function()
            return std.int(x)
        end
    )
end

Seems a little awkward to me. Is there a better way to pass arguments to function in std.catch?

potential todo list (shortcomings in bash introspection, portability problems, other project, semantic model)

I have a more or less complete list of essential introspection, which bash/POSIX shells are lacking and which are essential to get some form of weak coupling on executing scripts:
https://github.com/matu3ba/dotfiles_skeleton/blob/main/POSIXunsafe

You can not in a POSIX way reliably and sanely short

  1. check if symbols is defined in a script.
    https://stackoverflow.com/questions/18597697/posix-compliant-way-to-scope-variables-to-a-function-in-a-shell-script
  2. detect if a script is being sourced
    https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced
  3. get the current directory of the script.
    https://stackoverflow.com/questions/29832037/how-to-get-script-directory-in-posix-sh
  4. how to find files in PATH (standard is ambiguous how it should work)
    https://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then

Please also be aware, that symlinks work differently on Windows and Linux, which make scripts utilizing them unportable.
(platform support is not yet documented)

Sidenode: Please use fn or fun instead of function to enable faster script writing.

It would also help visitors to understand the scope of the project, if you compare to other scripting projects intended as bash execution replacement:

Finally, I want to ask about the intended execution semantics for 1. multi-processing (if and how scripts are aware of another, IPC communication between instances, resource handling to prevent deadlocks etc) and related 2. how the runtime is supposed to work.
Do you plan to stay "relative bare metal" and allow users to setup IPC routines or how should this be handled?
For understanding, it would help to provide intended use cases.

Feature: Potential for bash transpilation?

Seeing as this language is so neat, is it in the cards for a bash transpiler? I'd love to use hush more often with projects that require some distribution, but relying on it as a dependency requires more leg work for the consumer.

Not sure if this is the proper place to address this, or even a proper feature to suggest. Just curious if it's on the table, or even feasible for that matter. I have some experience with Clang, so maybe I could work on it myself if not!

Feature request: Add syntax sugar for dictionary access

Hey there, first, thank you very much for the project, it is quite handy for me.
What do you think of being able to use ? on dictionaries access?
So instead of writing this:

let data = @[
    a: 1
]
function get_value_by_key(key)
    data[key]
end

function get_value()
    get_value_by_key("b")
end

std.print(std.catch(get_value))

one could write this:

let data = @[
    a: 1
]

function get_value()
    data["b"]?
end

std.print(get_value())

Shell capture hangs indefinitely

I have successfully been capturing the output of some commands, but when I tried to capture the output of ps, the program is hanging indefinitely. Here is a sample program that reproduces the issue:

#!/usr/bin/env hush

std.print("1")
let result = ${
  ps aux
}
std.print("2")

When running this the "1" is printed but the "2" never comes and the program continues to run.

Notably, if I remove the capture and just run the command, it works.

#!/usr/bin/env hush

std.print("1")
{
  ps aux
}
std.print("2")

Feature: Add print() function with no newline

For various reasons, I would like to be able to print "partial" lines. The current std.print automatically inserts tabs and automatically appends a newline.

While I can suppress the tab-between-fields by using the ++ operator, I can't avoid the newline at end.

I'm suggesting a std.printc function, by analog with the old \c option to the echo command, which has the same behavior as std.print except that no newline is appended.

IOW:

std.print("a", "b")  -->  "a\tb\n"
std.printc("a", "b") -->  "a\tb"

Feature: Add support for multi-way conditional(s)

Just fixed a syntax error by replacing this:

if k < 25 then 
    # ...
else if k < 40 and arg == 0 then 
    # ...
else
    # ...
end

With this:

if k < 25 then 
    # ...
else
    if k < 40 and arg == 0 then 
        # ...
    else
        # ...
    end
end

Yow! Bash definitely gets that right with elif. And also case.

I don't know if case or match or cond is better, but I fervently believe that elif needs to be added. Wow, is it painful to statically nest if/else blocks!

Feature Request: Include and import

Hello, nice work with Hush.

I would like to suggest a new feature, I think it would interesting to add to Hush the possibility to include and to import external Hush files to a given script.
To include files one folder above, perhaps, it could be done like this include "../folder2/my_secondscript.hsh", and to include one file at the same directory like: include "./my_secondscript.hsh". The include functionality would only copy and paste all the contents of a given file.
In the case of import, it would verify the file and include functions and other needed functionality like this, ie : import {my_function1 as fn1 , my_function1 as fn2 } from "./folder2/my_secondscript.hsh", similarly to Typescript.

Another way around, could be done by creating to the std functions like std.include(filename), or std.import(functionName).

What do you think about this idea ? Is something like this possible, or even, can you consider an interesting proposition.

Thank you.

How to use `std.split` pattern argument?

Hello, I was playing around with hush tonight and wanted to split a line based on whitespace and was struggling to get a pattern to work. I dug through the docs and the repo and couldn't find any information on how it splits on pattern and what type of patterns it supports.

I noticed that the std.split implementation calls a split_str method. I'm no rust developer, so I'm not sure exactly what's happening in that code, but I was unable to follow any further to figure out what split_str does with the provided pattern.

Here's the code I'm playing with:

#!/usr/bin/env hush

if std.len(std.args()) == 0 then
  std.panic("please pass the port as the first argument")
end


let port = std.args()[0] # 8080
let result = ${
  lsof -i :$port
}

if std.is_empty(result.stdout) then
  std.print("nothing running on port " ++ port)
else
  let lines = std.split(std.trim(result.stdout), "\n")
  # std.print("\noutput: \n" ++ std.to_string(lines))

  for process_line in std.iter(lines) do
    let fields = std.split(std.trim(process_line), "[ \t\n]+")
    std.print("fields: \n" ++ std.to_string(fields))
  end
end

With the following output:

$ ./hello.hsh 8080
fields:
[ "COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME" ]
fields:
[ "java    34304 jack  160u  IPv6 0xf0ea0eac809f015d      0t0  TCP *:http-alt (LISTEN)" ]

tl;dr how to split a string based on a regex pattern?

How to pass arguments to script?

I've skimmed the docs but can see how hush gets args the way bash puts them in $1, $2 etc?

script.hsh arg1 arg2 arg3

Ideally it would support a more powerful argument parser, like argparse in python or getopt.

Test using `capture.hsh` fails

Seems like it's trying to use .stdout an old way, where the website now shows examples with result.context.stdout?

std.assert(result.stdout == "123\n456\n")
std.assert(result.stderr == "")

This failed when I was trying to build master as a nix package and it automatically ran cargo test. I have disabled the checks for now.

The command block commands require extra escape

Thanks for the good project, which can help us make better/big-scale bash script.
When port the exist bash script to this new script, found require extra escape \, for example:
The command from bash script like:

grep -E 'NOT|PRODUCT-.*-build02.*\.out\.tgz '

If copy to hush, should be:

grep -E 'NOT|VMWARE-.*-build02.*\\.out\\.tgz '

Could we don't need this extra escape, which can help us copy the command between hush with bash easier?

Syntax sugar for function containing shell commands

Can I propose a sugared syntax for defining a function containing only shell commands. Currently we have this:

function foo()
  return {
    # shell commands only
  }
end

I think this would be nicer like so:

function foo() {
    # shell commands only
}

The two would be equivalent; the latter would be syntax sugar for the former. I think this would make the scripting language even more integrated with the shell language, i.e. it would feel even more natural to mix the two while still keeping the two separate. What do you think?

Filesystem queries

A lot of bash scripts rely on checking for the existence of files/dirs as pre-flight checks. E.g. something like:

# exit if file doesn't exist
[[ ! -f "/myfile" ]] && exit 1
# exit if dir doesn't exist
[[ ! -d "/mydir" ]] && exit 1

If would be beneficial to be able to make the same kind of queries in hush - maybe something like std.io.file_exists(...)
If it has interest I'll create a PR (and one for the docs as well)

  • Test file exists
  • Test dir exists
  • Read file
  • Write file
  • Create dir/path
  • Delete file
  • Delete path recursively

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.