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" }})