github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/gen/root/tour.inc.md (about)

     1  {{ if env "DOCGEN_TARGET=vuepress" }}
     2  {{ if env "DOCGEN_TARGET=ignore-prefix" }}
     3  ### {{ end }}icon: life-ring
     4  
     5  ---
     6  
     7  {{ end }}<h1>Language Tour</h1>
     8  
     9  {{ if env "DOCGEN_TARGET=" }}<h2>Table of Contents</h2>
    10  
    11  <div id="toc">
    12  
    13  - [Introduction](#introduction)
    14    - [Read–Eval–Print Loop](#readevalprint-loop)
    15    - [Barewords](#barewords)
    16    - [Expressions and Statements](#expressions-and-statements)
    17    - [Functions and Methods](#functions-and-methods)
    18    - [The Bang Prefix](#the-bang-prefix)
    19  - [Rosetta Stone](#rosetta-stone)
    20  - [Basic Syntax](#basic-syntax)
    21    - [Quoting Strings](#quoting-strings)
    22    - [Code Comments](#code-comments)
    23  - [Variables](#variables)
    24    - [Global variables](#global-variables)
    25    - [Environmental Variables](#environmental-variables)
    26    - [Type Inference](#type-inference)
    27    - [Scalars](#scalars)
    28    - [Arrays](#arrays)
    29  - [Piping and Redirection](#piping-and-redirection)
    30    - [Pipes](#pipes)
    31    - [Redirection](#redirection)
    32    - [Redirecting to files](#redirecting-to-files)
    33    - [Type Conversion](#type-conversion)
    34      - [Cast](#cast)
    35      - [Format](#format)
    36  - [Sub-Shells](#sub-shells)
    37  - [Filesystem Wildcards (Globbing)](#filesystem-wildcards-globbing)
    38  - [Brace expansion](#brace-expansion)
    39  - [Executables](#executables)
    40    - [Aliases](#aliases)
    41    - [Public Functions](#public-functions)
    42    - [Private Functions](#private-functions)
    43    - [External Executables](#external-executables)
    44  - [Control Structures](#control-structures)
    45    - [Using `if` Statements](#using-if-statements)
    46    - [Using `switch` Statements](#using-switch-statements)
    47    - [Using `foreach` Loops](#using-foreach-loops)
    48    - [Using `formap` Loops](#using-formap-loops)
    49  - [Stopping Execution](#stopping-execution)
    50    - [The `continue` Statement](#the-continue-statement)
    51    - [The `break` Statement](#the-break-statement)
    52    - [The `return` Statement](#the-return-statement)
    53    - [The `exit` Statement](#the-exit-statement)
    54    - [Signal: SIGINT](#signal-sigint)
    55    - [Signal: SIGQUIT](#signal-sigquit)
    56    - [Signal: SIGTSTP](#signal-sigtstp)
    57  
    58  </div>
    59  {{ end }}
    60  ## Introduction
    61  
    62  Murex is a typed shell. By this we mean it still passes byte streams along
    63  POSIX pipes (and thus will work with all your existing command line tools) but
    64  in addition will add annotations to describe the type of data that is being
    65  written and read. This allows Murex to expand upon your command line tools
    66  with some really interesting and advanced features not available in traditional
    67  shells.
    68  
    69  > POSIX is a set of underlying standards that Linux, macOS and various other
    70  > operating systems support. Most typed shells do not work well with existing
    71  > commands whereas Murex does.
    72  
    73  ### Read–Eval–Print Loop
    74  
    75  {{ if env "DOCGEN_TARGET=vuepress" }}
    76  <!-- markdownlint-disable -->
    77  <a href="user-guide/interactive-shell.html" alt="interactive shell"><img src="/git-autocomplete.png?v={{ env "COMMITHASHSHORT" }}" class="centre-image"/></a>
    78  <!-- markdownlint-restore -->
    79  {{ end }}
    80  
    81  If you want to learn more about the interactive shell then there is a dedicated
    82  document detailing {{ link "Murex's REPL features" "interactive-shell" }}.
    83  
    84  ### Barewords
    85  
    86  Shells need to {{ link "balance scripting with an efficient interactive terminal" "split_personalities" }}
    87  interface. One of the most common approaches to solving that conflict between
    88  readability and terseness is to make heavy use of barewords. Barewords are
    89  ostensibly just instructions that are not quoted. In our case, command names
    90  and command parameters.
    91  
    92  Murex also makes heavy use of barewords and so that places requirements on
    93  the choice of syntax we can use.
    94  
    95  ### Expressions and Statements
    96  
    97  An **expression** is an evaluation, operation or assignment, for example:
    98  ```
    99  » 6 > 5
   100  » fruit = %[ apples oranges bananas ]
   101  » 5 + 5
   102  ```
   103  
   104  > Expressions are type sensitive
   105  
   106  Whereas a **statement** is a shell command to execute:
   107  ```
   108  » echo "Hello Murex"
   109  » kill 1234
   110  ```
   111  
   112  > All values in a statement are treated as strings
   113  
   114  Due to the expectation of shell commands supporting bareword parameters,
   115  expressions have to be parsed differently to statements. Thus Murex first
   116  parses a command line to see if it is a valid expression, and if it is not, it
   117  then assumes it is an statement and parses it as such.
   118  
   119  This allow expressions and statements to be used interchangeably in a pipeline:
   120  ```
   121  » 5 + 5 | grep 10
   122  ```
   123  
   124  ### Functions and Methods
   125  
   126  A **function** is command that doesn't take data from STDIN whereas a **method**
   127  is any command that does.
   128  ```
   129  echo "Hello Murex" | grep "Murex"
   130  ^ a function         ^ a method
   131  ```
   132  
   133  In practical terms, functions and methods are executed in exactly the same way
   134  however some builtins might behave differently depending on whether values are
   135  passed via STDIN or as parameters. Thus you will often find references to
   136  functions and methods, and sometimes for the same command, within these
   137  documents.
   138  
   139  ### The Bang Prefix
   140  
   141  Some Murex builtins support a bang prefix. This prefix alters the behavior of
   142  those builtins to perform the conceptual opposite of their primary role.
   143  
   144  For example, you could grep a file with `regexp 'm/(dogs|cats)/'` but then you
   145  might want to exclude any matches by using `!regexp 'm/(dogs|cats)/'` instead.
   146  
   147  The details for each supported bang prefix will be in the documents for their
   148  respective builtin.
   149  
   150  ## Rosetta Stone
   151  
   152  If you already know Bash and looking for the equivalent syntax in Murex, then
   153  our {{ link "Rosetta Stone" "rosetta-stone" }} reference will help you to
   154  translate your Bash code into Murex code.
   155  
   156  ## Basic Syntax
   157  
   158  ### Quoting Strings
   159  
   160  > It is important to note that all strings in expressions are quoted whereas
   161  > strings in statements can be barewords.
   162  
   163  There are three ways to quote a string in Murex:
   164  
   165  * `'single quote'`: use this for string literals    ({{ link "read more" "single-quote" }})
   166  * `"double quote"`: use this for infixing variables ({{ link "read more" "double-quote" }})
   167  * `%(brace quote)`: use this for nesting quotes     ({{ link "read more" "brace-quote" }})
   168  
   169  ### Code Comments
   170  
   171  You can comment out a single like, or end of a line with `#`:
   172  ```
   173  # this is a comment
   174  
   175  echo Hello Murex # this is also a comment
   176  ```
   177  
   178  Multiple lines or mid-line comments can be achieved with `/#` and `#/` tokens:
   179  ```
   180  /#
   181  This is
   182  a multi-line
   183  command
   184  #/
   185  ```
   186  ...which can also be inlined...
   187  ```
   188  » echo Hello /# comment #/ Murex
   189  ```
   190  
   191  (`/#` was chosen because it is similar to C-style comments however `/*` is a
   192  valid glob so Murex has substituted the asterisks with a hash symbol instead)
   193  
   194  ## Variables
   195  
   196  All variables can be defined as expressions and their data types are inferred:
   197  
   198  * `name = "bob"`
   199  * `age = 20 * 2`
   200  * `fruit = %[ apples oranges bananas ]`
   201  
   202  If any variables are unset then reading from them will produce an error (under
   203  Murex's default behavior):
   204  ```
   205  » echo $foobar
   206  Error in `echo` (1,1): variable 'foobar' does not exist
   207  ```
   208  
   209  ### Global variables
   210  
   211  Global variables can be defined using the `$GLOBAL` namespace:
   212  ```
   213  » $GLOBAL.foo = "bar"
   214  ```
   215  
   216  You can also force Murex to read the global assignment of `$foo` (ignoring
   217  any local assignments, should they exist) using the same syntax. eg:
   218  ```
   219  » $GLOBAL.name = "Tom"
   220  » out $name
   221  Tom
   222  
   223  » $name = "Sally"
   224  » out $GLOBAL.name
   225  Tom
   226  » out $name
   227  Sally
   228  ```
   229  
   230  ### Environmental Variables
   231  
   232  Environmental Variables are like global variables except they are copied to any
   233  other programs that are launched from your shell session.
   234  
   235  Environmental variables can be assigned using the `$ENV` namespace:
   236  ```
   237  » $ENV.foo = "bar"
   238  ```
   239  as well as using the `export` statement like with traditional shells. ({{ link "read more" "export" }})
   240  
   241  Like with global variables, you can force Murex to read the environmental
   242  variable, bypassing and local or global variables of the same name, by also
   243  using the `$ENV` namespace prefix.
   244  
   245  ### Type Inference
   246  
   247  In general, Murex will try to infer the data type of a variable or pipe. It
   248  can do this by checking the `Content-Type` HTTP header, the file name
   249  extension or just looking at how that data was constructed (when defined via
   250  expressions). However sometimes you may need to annotate your types. ({{ linkbm "read more" "set" "type-annotations" }})
   251  
   252  ### Scalars
   253  
   254  In traditional shells, variables are expanded in a way that results in spaces
   255  be parsed as different command parameters. This results in numerous problems
   256  where developers need to remember to enclose variables inside quotes.
   257  
   258  Murex parses variables as tokens and expands them into the command line
   259  arguments intuitively. So, there are no more accidental bugs due to spaces in
   260  file names, or other such problems due to developers forgetting to quote
   261  variables. For example:
   262  ```
   263  » file = "file name.txt"
   264  » touch $file # this would normally need to be quoted
   265  » ls
   266  'file name.txt'
   267  ```
   268  ### Arrays
   269  
   270  Due to variables not being expanded into arrays by default, Murex supports an
   271  additional variable construct for arrays. These are `@` prefixed:
   272  ```
   273  » files = %[file1.txt, file2.txt, file3.txt]
   274  » touch @files
   275  » ls
   276  file1.txt  file2.txt
   277  ```
   278  
   279  ## Piping and Redirection
   280  
   281  ### Pipes
   282  
   283  Murex supports multiple different pipe tokens. The main two being `|` and
   284  `->`.
   285  
   286  * `|` works exactly the same as in any normal shell. ({{ link "read more" "pipe-posix" }})
   287  
   288  * `->` displays all of the supported methods (commands that support the output
   289    of the previous command). Think of it a little like object orientated
   290    programming where an object will have functions (methods) attached. ({{ link "read more" "pipe-arrow" }})
   291  
   292  In Murex scripts you can use `|` and `->` interchangeably, so there's no need
   293  to remember which commands are methods and which are not. The difference only
   294  applies in the interactive shell where `->` can be used with tab-autocompletion
   295  to display a shortlist of supported functions that can manipulate the data from
   296  the previous command. It's purely a clue to the parser to generate different
   297  autocompletion suggestions to help with your discovery of different command
   298  line tools.
   299  
   300  ### Redirection
   301  
   302  Redirection of stdout and stderr is very different in Murex. There is no
   303  support for the `2>` or `&1` tokens,  instead you name the pipe inside angle
   304  brackets, in the first parameter(s).
   305  
   306  `out` is that processes stdout (fd1), `err` is that processes stderr (fd2), and
   307  `null` is the equivalent of piping to `/dev/null`.
   308  
   309  Any pipes prefixed by a bang means reading from that processes stderr.
   310  
   311  So to redirect stderr to stdout you would use `<!out>`:
   312  ```
   313  err <!out> "error message redirected to stdout"
   314  ```
   315  
   316  And to redirect stdout to stderr you would use `<err>`:
   317  ```
   318  out <err> "output redirected to stderr"
   319  ```
   320  
   321  Likewise you can redirect either stdout, or stderr to `/dev/null` via `<null>`
   322  or `<!null>` respectively.
   323  ```
   324  command <!null> # ignore stderr
   325  command <null>  # ignore stdout
   326  ```
   327  
   328  You can also create your own pipes that are files, network connections, or any
   329  other custom data input or output endpoint. ({{ link "read more" "namedpipes" }})
   330  
   331  ### Redirecting to files
   332  
   333  ```
   334  out "message" |> truncate-file.txt
   335  out "message" >> append-file.txt
   336  ```
   337  
   338  ### Type Conversion
   339  
   340  Aside from annotating variables upon definition, you can also transform data
   341  along the pipeline.
   342  
   343  #### Cast
   344  
   345  Casting doesn't alter the data, it simply changes the meta-information about
   346  how that data should be read.
   347  ```
   348  out [1,2,3] | cast json | foreach { ... }
   349  ```
   350  
   351  There is also a little syntactic sugar to do the same:
   352  ```
   353  out [1,2,3] | :json: foreach { ... }
   354  ```
   355  
   356  #### Format
   357  
   358  `format` takes the source data and reformats it into another data format:
   359  ```
   360  » out [1,2,3] | :json: format yaml
   361  - 1
   362  - 2
   363  - 3
   364  ```
   365  
   366  ## Sub-Shells
   367  
   368  There are two types of emendable sub-shells: strings and arrays.
   369  
   370  * string sub-shells, `${ command }`, take the results from the sub-shell and
   371    return it as a single parameter. This saves the need to encapsulate the shell
   372    inside quotation marks.
   373  
   374  * array sub-shells, `@{ command }`, take the results from the sub-shell
   375    and expand it as parameters.
   376  
   377  **Examples:**
   378  
   379  ```
   380  touch ${ %[1,2,3] } # creates a file named '[1,2,3]'
   381  touch @{ %[1,2,3] } # creates three files, named '1', '2' and '3'
   382  ```
   383  
   384  The reason Murex breaks from the POSIX tradition of using backticks and
   385  parentheses is because Murex works on the principle that everything inside
   386  a curly bracket is considered a new block of code.
   387  
   388  ## Filesystem Wildcards (Globbing)
   389  
   390  While glob expansion is supported in the interactive shell, there isn't
   391  auto-expansion of globbing in shell scripts. This is to protect against
   392  accidental damage. Instead globbing is achieved via sub-shells using either:
   393  
   394  * `g`  - traditional globbing                      ({{ link "read more" "g" }})
   395  * `rx` - regexp matching in current directory only ({{ link "read more" "rx" }})
   396  * `f`  - file type matching                        ({{ link "read more" "f" }})
   397  
   398  **Examples:**
   399  
   400  All text files via globbing:
   401  ```
   402  g *.txt
   403  ```
   404  
   405  All text and markdown files via regexp:
   406  ```
   407  rx '\.(txt|md)$'
   408  ```
   409  
   410  All directories via type matching:
   411  ```
   412  f +d
   413  ```
   414  
   415  You can also chain them together, eg all directories named `*.txt`:
   416  ```
   417  g *.txt | f +d
   418  ```
   419  
   420  To use them in a shell script it could look something a like this:
   421  ```
   422  rm @{g *.txt | f +s}
   423  ```
   424  (this deletes any symlinks called `*.txt`)
   425  
   426  ## Brace expansion
   427  
   428  In [bash you can expand lists](https://en.wikipedia.org/wiki/Bash_(Unix_shell)#Brace_expansion)
   429  using the following syntax: `a{1..5}b`. In Murex, like with globbing, brace
   430  expansion is a function: `a a[1..5]b` and supports a much wider range of lists
   431  that can be expanded. ({{ link "read more" "a" }})
   432  
   433  ## Executables
   434  
   435  ### Aliases
   436  
   437  You can create "aliases" to common commands to save you a few keystrokes. For
   438  example:
   439  ```
   440  alias gc=git commit
   441  ```
   442  
   443  `alias` behaves slightly differently to Bash. ({{ link "read more" "alias" }})
   444  
   445  ### Public Functions
   446  
   447  You can create custom functions in Murex using `function`. ({{ link "read more" "function" }})
   448  ```
   449  function gc (message: str) {
   450      # shorthand for `git commit`
   451      
   452      git commit -m $message
   453  }
   454  ```
   455  
   456  ### Private Functions
   457  
   458  `private` functions are like [public functions](#public-functions) except they
   459  are only available within their own modules namespace. ({{ link "read more" "private" }})
   460  
   461  ### External Executables
   462  
   463  External executables (including any programs located in `$PATH`) are invoked
   464  via the `exec` builtin ({{ link "read more" "exec" }}) however if a command
   465  isn't an expression, alias, function nor builtin, then Murex assumes it is an
   466  external executable and automatically invokes `exec`.
   467  
   468  For example the two following statements are the same:
   469  
   470  1. `exec uname`
   471  2. `uname`
   472  
   473  Thus for normal day to day usage, you shouldn't need to include `exec`.
   474  
   475  ## Control Structures
   476  
   477  ### Using `if` Statements
   478  
   479  `if` can be used in a number of different ways, the most common being:
   480  ```
   481  if { true } then {
   482      # do something
   483  } else {
   484      # do something else
   485  }
   486  ```
   487  
   488  `if` supports a flexible variety of incarnation to solve different problems. ({{ link "read more" "if" }})
   489  
   490  ### Using `switch` Statements
   491  
   492  Because `if ... else if` chains are ugly, Murex supports `switch` statements:
   493  ```
   494  switch $USER {
   495      case "Tom"   { out "Hello Tom" }
   496      case "Dick"  { out "Howdie Richard" }
   497      case "Sally" { out "Nice to meet you" }
   498  
   499      default {
   500          out "I don't know who you are"
   501      }
   502  }
   503  ```
   504  
   505  `switch` supports a flexible variety of different usages to solve different
   506  problems. ({{ link "read more" "switch" }})
   507  
   508  ### Using `foreach` Loops
   509  
   510  `foreach` allows you to easily iterate through an array or list of any type: ({{ link "read more" "foreach" }})
   511  ```
   512  %[ apples bananas oranges ] | foreach fruit { out "I like $fruit" }
   513  ```
   514  
   515  ### Using `formap` Loops
   516  
   517  `formap` loops are the equivalent of `foreach` but against map objects: ({{ link "read more" "formap" }})
   518  ```
   519  %{
   520      Bob:     {age: 10},
   521      Richard: {age: 20},
   522      Sally:   {age: 30}
   523  } | formap name person {
   524      out "$name is $person[age] years old"
   525  }
   526  ```
   527  
   528  ## Stopping Execution
   529  
   530  ### The `continue` Statement
   531  
   532  `continue` will terminate execution of an inner block in iteration loops like
   533  `foreach` and `formap`. Thus _continuing_ the loop from the next iteration:
   534  ```
   535  %[1..10] | foreach i {
   536      if { $i == 5 } then {
   537          continue foreach
   538          # ^ jump back to the next iteration
   539      }
   540  
   541      out $i
   542  }
   543  ```
   544  
   545  `continue` requires a parameter to define while block to iterate on. This means
   546  you can use `continue` within nested loops and still have readable code. ({{ link "read more" "continue" }})
   547  
   548  ### The `break` Statement
   549  
   550  `break` will terminate execution of a block (eg `function`, `private`, `if`,
   551  `foreach`, etc):
   552  ```
   553  %[1..10] | foreach i {
   554      if { $i == 5 } then {
   555          break foreach
   556          # ^ exit foreach
   557      }
   558  
   559      out $i
   560  }
   561  ```
   562  
   563  `break` requires a parameter to define while block to end. Thus `break` can be
   564  considered to exhibit the behavior of _return_ as well as _break_ in other
   565  languages:
   566  ```
   567  function example {
   568      if { $USER == "root" } then {
   569          err "Don't run this as root"
   570          break example
   571      }
   572  
   573      # ... do something ...
   574  }
   575  ```
   576  
   577  `break` cannot exit anything above it's callers scope. ({{ link "read more" "break" }})
   578  
   579  ### The `return` Statement
   580  
   581  `return` ends the current scope (typically a function). ({{ link "read more" "return" }})
   582  
   583  ### The `exit` Statement
   584  
   585  `exit` terminates Murex. It is not scope aware; if it is included in a function
   586  then the whole shell will still exist and not just that function. ({{ link "read more" "exit" }})
   587  
   588  ### Signal: SIGINT
   589  
   590  This can be invoked by pressing `Ctrl` + `c`.
   591  
   592  This is functionally the same as `fid-kill`. (({{ link "read more" "fid-kill" }}))
   593  
   594  ### Signal: SIGQUIT
   595  
   596  This can be invoked by pressing `Ctrl` + `\`. ({{ link "read more" "terminal-keys" }})
   597  
   598  Sending SIGQUIT will terminate all running functions in the current Murex
   599  session. Which is a handy escape hatch if your shell code starts misbehaving.
   600  
   601  ### Signal: SIGTSTP
   602  
   603  This can be invoked by pressing `Ctrl` + `z`. ({{ link "read more" "job-control" }})