Hush is a Unix shell scripting language inspired by the Lua programming language.
Check the homepage for more details.
Hush is a unix shell based on the Lua programming language
License: MIT License
Running the following hush script multiple times results in unexpected behaviour.
#!/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
}
$ ./test.hsh; cat test_file
1652491810
1 2
$ 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...?
$ 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.
% 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....
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.
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.
As things presently stand, the following files can all be different:
cargo install
)cargo test
in a hush git repo)hush foo.hsh
)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.
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
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
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.
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
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.
Hi there :)
Are tests not implemented yet, or not documented?
Thanks
In case the term is inconclusive: https://fishshell.com/docs/current/cmds/test.html
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!
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
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.
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
I don't see a std.exit() function in the docs, and std.panic() seems harsh and exits with a nonzero exit code...
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
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!")
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
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.
Without having looked too much into the compiler - would it be possible to implement REPL support?
I am thinking in regards to using this in a scratchpad manor - like https://github.com/metakirby5/codi.vim or https://github.com/rafcamlet/nvim-luapad.
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!
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?
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
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.
Would it be possible to expose Rust's FFI functionality for invoking (over the C ABI) functions located in external libraries?
https://doc.rust-lang.org/nomicon/ffi.html
That would allow hush shell users to use a world of existing software.
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!
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())
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")
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"
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!
Because the Visual Studio Marketplace is not supported on non-windows platforms, I am unable to install the extension. Would you consider also publishing it to https://open-vsx.org/ ?
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.
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?
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.
Seems like it's trying to use .stdout
an old way, where the website now shows examples with result.context.stdout
?
hush/src/runtime/tests/data/positive/capture.hsh
Lines 6 to 7 in 7fa2dc4
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.
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?
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?
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)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.