项目作者: madlambda

项目描述 :
Nash stands for Nash shell.
高级语言: Go
项目地址: git://github.com/madlambda/nash.git
创建时间: 2016-04-01T03:58:04Z
项目社区:https://github.com/madlambda/nash

开源协议:Apache License 2.0

下载


Table of Contents

nash

Join the chat at https://gitter.im/madlambda/nash GoDoc
Build Status Go Report Card

Nash is a system shell, inspired by plan9 rc, that makes it easy to create reliable and safe scripts taking advantages of operating systems namespaces (on linux and plan9) in an idiomatic way.

Useful stuff

  • nashfmt: Formats nash code (like gofmt) but no code styling defined yet (see Installation section).
  • nashcomplete: Autocomplete done in nash script.
  • Dotnash: Nash profile customizations (e.g: prompt, aliases, etc)
  • nash-mode: Emacs major mode integrated with nashfmt.

Why nash scripts are reliable?

  1. Nash aborts at first non-success status of commands;
  2. Nash aborts at first unbound variable;
  3. It’s possible to check the result status of every component of a pipe;
  4. no eval;
  5. Strings are pure strings (no evaluation of variables);
  6. No wildcards (globbing) of files; (‘rm *‘ removes a file called ‘*‘);
    • On windows, the terminal does the globbing when in interactive mode.
    • On unix there’s libs/completions to achieve something similar.
  7. No obscure syntax;
  8. Support tooling for indent/format and statically analyze the scripts;

Installation

Nash uses two environment variables: NASHROOT to find the standard nash library and NASHPATH to find libraries in general (like user’s code).

It is important to have two different paths since this will allow you
to upgrade nash (overwrite nash stdlib) without risking lost your code.

If NASHPATH is not set, a default of $HOME/nash will be assumed
($HOMEPATH/nash on windows).
If NASHROOT is not set, a default of $HOME/nashroot will be assumed
($HOMEPATH/nashroot on windows).

The libraries lookup dir will be $NASHPATH/lib.
The standard library lookup dir will be $NASHROOT/stdlib.

After installing the nash binary will be located at $NASHROOT/bin.

Installing a Release

Installing is so stupid that we provide small scripts to do it.
If your platform is not supported take a look at the existent ones
and send a MR with the script for your platform.

Unix

If you run a unix based machine (Linux, Darwin/OSX, *BSD, etc)
you can use the script below:

Run:

  1. ./hack/install/unix.sh

Master

Run:

  1. make install

Getting started

Nash syntax resembles a common shell:

  1. nash
  2. λ> echo "hello world"
  3. hello world

Pipes works like borne shell and derivations:

  1. λ> cat spec.ebnf | wc -l
  2. 108

Output redirection works like Plan9 rc, but not only for filenames. It
supports output redirection to tcp, udp and unix network protocols
(unix sockets are not supported on windows).

  1. # stdout to log.out, stderr to log.err
  2. λ> ./daemon >[1] log.out >[2] log.err
  3. # stderr pointing to stdout
  4. λ> ./daemon-logall >[2=1]
  5. # stdout to /dev/null
  6. λ> ./daemon-quiet >[1=]
  7. # stdout and stderr to tcp address
  8. λ> ./daemon >[1] "udp://syslog:6666" >[2=1]
  9. # stdout to unix file
  10. λ> ./daemon >[1] "unix:///tmp/syslog.sock"

For safety, there’s no eval or string/tilde expansion or command substitution in Nash.

To assign command output to a variable exists the ‘<=’ operator. See the example
below:

  1. var fullpath <= realpath $path | xargs -n echo
  2. echo $fullpath

The symbol ‘<=’ redirects the stdout of the command or function invocation in the
right-hand side to the variable name specified.

If you want the command output splited into an array, then you’ll need
to store it in a temporary variable and then use the builtin split function.

  1. var out <= find .
  2. var files <= split($out, "\n")
  3. for f in $files {
  4. echo "File: " + $f
  5. }

To avoid problems with spaces in variables being passed as
multiple arguments to commands, nash pass the contents of each
variable as a single argument to the command. It works like
enclosing every variable with quotes before executing the command.
Then the following example do the right thing:

  1. var fullname = "John Nash"
  2. ./ci-register --name $fullname --option somevalue

On bash you need to enclose the $fullname variable in quotes to avoid problems.

Nash syntax does not support shell expansion from strings. There’s no way to
do things like the following in nash:

  1. echo "The date is: $(date +%D)" # DOESNT WORKS!

Instead you need to assign each command output to a proper variable and then
concat it with another string when needed (see the reference docs).

In the same way, nash doesn’t support shell expansion at if condition.
For check if a directory exists you must use:

  1. -test -d $rootfsDir # if you forget '-', the script will be aborted here
  2. # if path not exists
  3. if $status != "0" {
  4. echo "RootFS does not exists."
  5. exit $status
  6. }

Nash stops executing the script at first error found and, in the majority of times, it is what
you want (specially for deploys). But Commands have an explicitly
way to bypass such restriction by prepending a dash ‘-‘ to the command statement.
For example:

  1. fn cleanup()
  2. -rm -rf $buildDir
  3. -rm -rf $tmpDir
  4. }

The dash ‘-‘ works only for operating system commands, other kind of errors are impossible to bypass.
For example, trying to evaluate an unbound variable aborts the program with error.

  1. λ> echo $PATH
  2. /bin:/sbin:/usr/bin:/usr/local/bin:/home/user/.local/bin:/home/user/bin:/home/user/.gvm/pkgsets/go1.5.3/global/bin:/home/user/projects/3rdparty/plan9port/bin:/home/user/.gvm/gos/go1.5.3/bin
  3. λ> echo $bleh
  4. ERROR: Variable '$bleh' not set

Long commands can be split in multiple lines:

  1. λ> (aws ec2 attach-internet-gateway --internet-gateway-id $igwid
  2. --vpc-id $vpcid)
  3. λ> var instanceId <= (
  4. aws ec2 run-instances
  5. --image-id ami-xxxxxxxx
  6. --count 1
  7. --instance-type t1.micro
  8. --key-name MyKeyPair
  9. --security-groups my-sg
  10. | jq ".Instances[0].InstanceId"
  11. )
  12. λ> echo $instanceId

Accessing command line args

When you run a nash script like:

  1. λ> nash ./examples/args.sh --arg value

You can get the args using the ARGS variable, that is a list:

  1. #!/usr/bin/env nash
  2. echo "iterating through the arguments list"
  3. echo ""
  4. for arg in $ARGS {
  5. echo $arg
  6. }

Namespace features

Nash is built with namespace support only on Linux (Plan9 soon). If
you use OSX, BSD or Windows, then the rfork keyword will fail.

The examples below assume you are on a Linux box.

Below are some facilities for namespace management inside nash.
Make sure you have USER namespaces enabled in your kernel:

  1. zgrep CONFIG_USER_NS /proc/config.gz
  2. CONFIG_USER_NS=y

If it’s not enabled you will need root privileges to execute every example below…

Creating a new process in a new USER namespace (u):

  1. λ> id
  2. uid=1000(user) gid=1000(user) groups=1000(user),98(qubes)
  3. λ> rfork u {
  4. id
  5. }
  6. uid=0(root) gid=0(root) groups=0(root),65534

Yes, Linux supports creation of containers by unprivileged users. Tell
this to the customer success of your container-infrastructure-vendor. :-)

The default UID mapping is: Current UID (getuid) => 0 (no
range support). I’ll look into more options for this in the future.

Yes, you can create multiple nested user namespaces. But kernel limits
the number of nested user namespace clones to 32.

  1. λ> rfork u {
  2. echo "inside first container"
  3. id
  4. rfork u {
  5. echo "inside second namespace..."
  6. id
  7. }
  8. }

You can verify that other types of namespace still requires root
capabilities, see for PID namespaces (p).

  1. λ> rfork p {
  2. id
  3. }
  4. ERROR: fork/exec ./nash: operation not permitted

The same happens for mount (m), ipc (i) and uts (s) if used without
user namespace (u) flag.

The c flag stands for “container” and is an alias for upmnis (all
types of namespaces). If you want another shell (maybe bash) inside
the namespace:

  1. λ> rfork c {
  2. bash
  3. }
  4. [root@stay-away nash]# id
  5. uid=0(root) gid=0(root) groups=0(root),65534
  6. [root@stay-away nash]# mount -t proc proc /proc
  7. [root@stay-away nash]#
  8. [root@stay-away nash]# ps aux
  9. USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
  10. root 1 0.0 0.0 34648 2748 pts/4 Sl 17:32 0:00 -rcd- -addr /tmp/nash.qNQa.sock
  11. root 5 0.0 0.0 16028 3840 pts/4 S 17:32 0:00 /usr/bin/bash
  12. root 23 0.0 0.0 34436 3056 pts/4 R+ 17:34 0:00 ps aux

Everything except the rfork is like a common shell. Rfork will spawn a
new process with the namespace flags and executes the commands inside
the block on this namespace. It has the form:

  1. rfork <flags> {
  2. <statements to run inside the container>
  3. }

OK, but how scripts should look like?

See the project nash-app-example.

Didn’t work?

I’ve tested in the following environments:

  1. Linux 4.7-rc7
  2. Archlinux
  3. Linux 4.5.5 (amd64)
  4. Archlinux
  5. Linux 4.3.3 (amd64)
  6. Archlinux
  7. Linux 4.1.13 (amd64)
  8. Fedora release 23
  9. Linux 4.1.13 (amd64)
  10. Debian 8

Language specification

The specification isn’t complete yet, but can be found
here.
The file spec_test.go makes sure it is sane.

Some Bash comparisons

Bash Nash Description
GOPATH=/home/user/gopath GOPATH="/home/user/gopath" Nash enforces quoted strings
GOPATH="$HOME/gopath" GOPATH=$HOME+"/gopath" Nash doesn’t do string expansion
export PATH=/usr/bin PATH="/usr/bin"
setenv PATH
setenv operates only on valid variables
export showenv
ls -la ls -la Simple commads are identical
ls -la "$GOPATH" ls -la $GOPATH Nash variables shouldn’t be enclosed in quotes, because it’s default behaviour
./worker 2>log.err 1>log.out ./worker >[2] log.err >[1] log.out Nash redirection works like plan9 rc
./worker 2>&1 ./worker >[2=1] Redirection map only works for standard file descriptors (0,1,2)

Security

The PID 1 of every namespace created by nash is the same nash binary reading
commands from the parent shell via unix socket. It allows the parent namespace
(the script that creates the namespace) to issue commands inside the child
namespace. In the current implementation the unix socket communication is not
secure yet.

Installing libraries

Lets say you have a nash library and you want to install it. For example you have
the following:

  1. awesome/code.sh

And you want to install it so you can write code like this:

  1. import awesome/code
  2. code_do_awesome_stuff()

All you have to do is run:

  1. nash -install ./awesome

Or:

  1. nash -install /absolute/path/awesome

The entire awesome dir (and its subdirs) will be copied where nash
searches for libraries (dependent on environment variables).

This is the recommended way of installing nash libraries (althought
you can do it manually if you want).

Single files can also be installed as packages, for example:

  1. nash -install ./awesome/code.sh

Will enable you to import like this:

  1. import code

If there is already a package with the given name it will be
overwritten.

Releasing

To generate a release basically:

  • Generate the release on github
  • Clone the generated tag
  • Run: make release "version=<version>"

Where must match the version of the git tag.

Want to contribute?

Open issues and PR :)
The project is in an early stage, be patient because things can change in the future.

“What I cannot create, I do not understand.”

— Richard Feynman