|
1 | 1 | # Thinking in Nu |
2 | 2 |
|
3 | | -To help you understand - and get the most out of - Nushell, we've put together this section on "thinking in Nushell". By learning to think in Nushell and use the patterns it provides, you'll hit fewer issues getting started and be better setup for success. |
| 3 | +Nushell is different! It's common (and expected!) for new users to have some existing "habits" or mental models coming from other shells or languages. |
4 | 4 |
|
5 | | -So what does it mean to think in Nushell? Here are some common topics that come up with new users of Nushell. |
| 5 | +The most common questions from new users typically fall into one of the following topics: |
6 | 6 |
|
7 | | -## Nushell isn't bash |
| 7 | +[[toc]] |
8 | 8 |
|
9 | | -Nushell is both a programming language and a shell. Because of this, it has its own way of working with files, directories, websites, and more. We've modeled this to work closely with what you may be familiar with other shells. Pipelines work by attaching two commands together: |
| 9 | +## Nushell isn't Bash |
| 10 | + |
| 11 | +### It can sometimes look like Bash |
| 12 | + |
| 13 | +Nushell is both a programming language and a shell. Because of this, it has its own way of working with files, directories, websites, and more. We've modeled this to work closely with what you may be familiar with other shells. Pipelines work by attaching two commands together, just like in other shells. For example, the following commandline works the same in both Bash and Nushell on Unix/Linux platforms: |
10 | 14 |
|
11 | 15 | ```nu |
12 | | -> ls | length |
| 16 | +curl -s https://api.github.com/repos/nushell/nushell/contributors | jq '.[].login' |
| 17 | +# => returns contributors to Nushell, ordered by number of contributions |
13 | 18 | ``` |
14 | 19 |
|
15 | | -Nushell, for example, also has support for other common capabilities like getting the exit code from previously run commands. |
| 20 | +Nushell has many other similarities with Bash (and other shells) and many commands in common. |
| 21 | + |
| 22 | +::: tip |
| 23 | +While the above commandline works, in Nushell there's no need to use the `curl` and `jq` commands for this, since Nushell has a built-in [`http get` command](/commands/docs/http_get.md) and handles JSON data natively. For example: |
| 24 | + |
| 25 | +```nu |
| 26 | +http get https://api.github.com/repos/nushell/nushell/contributors | select login contributions |
| 27 | +``` |
16 | 28 |
|
17 | | -While it does have these amenities, Nushell isn't bash. The bash way of working, and the POSIX style in general, is not one that Nushell supports. For example, in bash, you might use: |
| 29 | +::: |
| 30 | + |
| 31 | +::: warning Thinking in Nushell |
| 32 | +Nushell borrows concepts from many shells and languages. You'll likely find many of Nushell's features familiar. |
| 33 | +::: |
| 34 | + |
| 35 | +### But it's not Bash |
| 36 | + |
| 37 | +Because of this, however, it's sometimes easy to forget that some Bash (and POSIX in general) style constructs just won't work in Nushell. For instance, in Bash, it would be normal to write: |
18 | 38 |
|
19 | 39 | ```sh |
20 | | -> echo "hello" > output.txt |
| 40 | +# Redirect using > |
| 41 | +echo "hello" > output.txt |
| 42 | +# But compare (greater-than) using the test command |
| 43 | +test 4 -gt 7 |
| 44 | +echo $? |
| 45 | +# => 1 |
| 46 | +``` |
| 47 | + |
| 48 | +In Nushell, however, the `>` is used as the greater-than operator for comparison. This is more in line with modern programming expectations. |
| 49 | + |
| 50 | +```nu |
| 51 | +4 > 10 |
| 52 | +# => false |
| 53 | +``` |
| 54 | + |
| 55 | +Since `>` is an operator, redirection to a file in Nushell is handled through a pipeline command that is dedicated to saving content - `[save](/commands/docs/save.md)`: |
| 56 | + |
| 57 | +```nu |
| 58 | +"hello" | save output.txt |
| 59 | +``` |
| 60 | + |
| 61 | +::: warning Thinking in Nushell |
| 62 | +We've put together a list of common Bash'isms and how to accomplish those tasks in Nushell in the [Coming from Bash](./coming_from_bash.md) Chapter. |
| 63 | +::: |
| 64 | + |
| 65 | +## Implicit Return |
| 66 | + |
| 67 | +Users coming from other shells will likely be very familiar with the `echo` command. Nushell's |
| 68 | +[`echo`](/commands/docs/echo.md) might appear the same at first, but it is _very_ different. |
| 69 | + |
| 70 | +First, notice how the following output _looks_ the same in both Bash and Nushell (and even PowerShell and Fish): |
| 71 | + |
| 72 | +```nu |
| 73 | +echo "Hello, World" |
| 74 | +# => Hello, World |
21 | 75 | ``` |
22 | 76 |
|
23 | | -In Nushell, we use the `>` as the greater-than operator. This fits better with the language aspect of Nushell. Instead, you pipe to a command that has the job of saving content: |
| 77 | +But while the other shells are sending the argument straight to _standard output_, Nushell's `echo` is |
| 78 | +simply _returning a value_. Nushell then _renders_ the return value of a command, or more technically, an _expression_. |
| 79 | + |
| 80 | +More importantly, Nushell _implicitly returns_ the value of an expression. This is similar to PowerShell or Rust in many respects. |
| 81 | + |
| 82 | +And an expression can be more than just a pipeline. Even custom commands (similar to functions in many languages, but we'll cover them more in depth in a [later chapter](./custom_commands.md)) automatically, implicitly _return_ the last value. There's no need for an `echo` or even a [`return` command](/commands/docs/return.md) to return a value - It just _happens_. |
| 83 | + |
| 84 | +In other words, the string _"Hello, World"_ and the return value from `echo "Hello, World"` are equivalent: |
24 | 85 |
|
25 | 86 | ```nu |
26 | | -> "hello" | save output.txt |
| 87 | +"Hello, World" == (echo "Hello, World") |
| 88 | +# => true |
| 89 | +``` |
| 90 | + |
| 91 | +An example with a custom command definition: |
| 92 | + |
| 93 | +```nu |
| 94 | +def latest-file [] { |
| 95 | + ls | sort-by modified | last |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +The _result_ of that pipeline (its _"value"_) becomes the _return value_ of the `latest-file` custom command. |
| 100 | + |
| 101 | +::: warning Thinking in Nushell |
| 102 | +Most anywhere you might write `echo <something>`, in Nushell, you can just write `<something>` instead. |
| 103 | +::: |
| 104 | + |
| 105 | +## Single Return Value per Expression |
| 106 | + |
| 107 | +It's important to understand that an expression can only return a single value. If there are multiple subexpressions inside an expression, only the **_last_** value is returned. |
| 108 | + |
| 109 | +A common mistake is to write a custom command definition like this: |
| 110 | + |
| 111 | +```nu:line-numbers |
| 112 | +def latest-file [] { |
| 113 | + echo "Returning the last file" |
| 114 | + ls | sort-by modified | last |
| 115 | +} |
| 116 | +
|
| 117 | +latest-file |
27 | 118 | ``` |
28 | 119 |
|
29 | | -**Thinking in Nushell:** The way Nushell views data is that data flows through the pipeline until it reaches the user or is handled by a final command. You can simply type data, from strings to JSON-style lists and records, and follow it with `|` to send it through the pipeline. Nushell uses commands to do work and produce more data. Learning these commands and when to use them helps you compose many kinds of pipelines. |
| 120 | +New users might expect: |
| 121 | + |
| 122 | +- Line 2 to output _"Returning the last file"_ |
| 123 | +- Line 3 to return/output the file |
| 124 | + |
| 125 | +However, remember that `echo` **_returns a value_**. Since only the last value is return, the Line 2 _value_ is discarded. Only the file will be returned. |
| 126 | + |
| 127 | +To make sure the first line is _displayed_, use the [`print` command](/commands/docs/print.md): |
| 128 | + |
| 129 | +def latest-file [] { |
| 130 | +print "Returning last file" |
| 131 | +ls | sort-by modified | last |
| 132 | +} |
| 133 | + |
| 134 | +Also compare: |
| 135 | + |
| 136 | +```nu |
| 137 | +40; 50; 60 |
| 138 | +``` |
| 139 | + |
| 140 | +::: tip |
| 141 | +A semicolon is the same as a newline in a Nushell expression. The above is the same as: |
| 142 | + |
| 143 | +```nu |
| 144 | +40 |
| 145 | +50 |
| 146 | +60 |
| 147 | +``` |
| 148 | + |
| 149 | +or |
| 150 | + |
| 151 | +```nu |
| 152 | +echo 40 |
| 153 | +echo 50 |
| 154 | +echo 60 |
| 155 | +``` |
| 156 | + |
| 157 | +::: |
| 158 | + |
| 159 | +In all of the above: |
| 160 | + |
| 161 | +- The first value is evaluated as the integer 40 but is not returned |
| 162 | +- The second value is evaluated as the integer 50 but is not returned |
| 163 | +- The third value is evaluated as the integer 60, and since it is the last |
| 164 | + value, it is is returned and displayed (rendered). |
| 165 | + |
| 166 | +::: warning Thinking in Nushell |
| 167 | +When debugging unexpected results, be on the lookout for: |
| 168 | + |
| 169 | +- Subexpressions (e.g., commands or pipelines) that ... |
| 170 | +- ... output a (non-`null`) value ... |
| 171 | +- ... where that value isn't returned from the parent expression. |
| 172 | + |
| 173 | +These can be likely sources of issues in your code. |
| 174 | +::: |
| 175 | + |
| 176 | +## Every Command Returns a Value |
| 177 | + |
| 178 | +Some languages have the concept of "statements" which don't return values. Nushell does not. |
| 179 | + |
| 180 | +In Nushell, **_every command returns a value_**, even if that value is `null` (the `nothing` type). Consider the following multiline expression: |
| 181 | + |
| 182 | +```nu:line-numbers |
| 183 | +let p = 7 |
| 184 | +print $p |
| 185 | +$p * 6 |
| 186 | +``` |
| 187 | + |
| 188 | +1. Line 1: The integer 5 is assigned to `$p`, but the return value of the |
| 189 | + [`let` command](/commands/docs/let.md) itself is `null`. However, because it is not the last |
| 190 | + value in the expression, it is not displayed. |
| 191 | +2. Line 2: The return value of the `print` command itself is `null`, but the `print` command |
| 192 | + forces its argument (`$p`, which is 5) to be _displayed_. As with Line 1, the `null` return value |
| 193 | + is discarded since this isn't the last value in the expression. |
| 194 | +3. Line 3: Evaluates to the integer value 42. As the last value in the expression, this is the return |
| 195 | + result, and is also displayed (rendered). |
| 196 | + |
| 197 | +::: warning Thinking in Nushell |
| 198 | +Becoming familiar with the return values of common commands will help you understand how |
| 199 | +to combine simple commands to achieve complex results. |
| 200 | + |
| 201 | +`help <command>` will show the signature, including the output type(s), for each command in Nushell. |
| 202 | +::: |
30 | 203 |
|
31 | 204 | ## Think of Nushell as a Compiled Language |
32 | 205 |
|
33 | | -An important part of Nushell's design and specifically where it differs from many dynamic languages is that Nushell converts the source you give it into something to run, and then runs the result. It doesn't have an `eval` feature which allows you to continue pulling in new source during runtime. This means that tasks like including files to be part of your project need to be known paths, much like includes in compiled languages like C++ or Rust. |
| 206 | +In most other shells, code is is only _evaluated_. In Nushell, code is: |
34 | 207 |
|
35 | | -For example, the following doesn't make sense in Nushell, and will fail to execute if run as a script: |
| 208 | +1. Stage 1: Parsed |
| 209 | +2. Stage 2: If the code parsed correctly, the result is then Evaluated |
| 210 | + |
| 211 | +The Nushell Parser is key to many of Nushell's and the REPL features such as: |
| 212 | + |
| 213 | +- Accurate and expressive error messages |
| 214 | +- Earlier and robust detection of error conditions |
| 215 | +- IDE integration |
| 216 | +- The type system |
| 217 | +- The module system |
| 218 | +- Completions |
| 219 | +- Custom command argument parsing |
| 220 | +- Syntax highlighting |
| 221 | +- Real-time error highlighting |
| 222 | +- Profiling and debugging commands |
| 223 | +- (Future) Formatting |
| 224 | +- (Future) Saving IR (Intermediate Results) "compiled" results for faster execution |
| 225 | + |
| 226 | +It may be useful to think of this parsing stage as _compilation_ in languages like Rust or C++. This means that all of the code that will evaluated in Stage 2 must be _known_ and available during the parsing stage. However, this also means that Nushell cannot currently support an `eval` construct as with many _dynamic_ languages such as Bash or Python. |
| 227 | + |
| 228 | +For example, the following cannot run as a single expression (e.g., a script): |
36 | 229 |
|
37 | 230 | ```nu |
38 | | -"def abc [] { 1 + 2 }" | save output.nu |
39 | | -source "output.nu" |
40 | | -abc |
| 231 | +"print Hello" | save output.nu |
| 232 | +source output.nu |
| 233 | +# => Error: nu::parser::sourced_file_not_found |
| 234 | +# => |
| 235 | +# => × File not found |
| 236 | +# => ╭─[entry #5:2:8] |
| 237 | +# => 1 │ "print Hello" | save output.nu |
| 238 | +# => 2 │ source output.nu |
| 239 | +# => · ────┬──── |
| 240 | +# => · ╰── File not found: output.nu |
| 241 | +# => ╰──── |
| 242 | +# => help: sourced files need to be available before your script is run |
41 | 243 | ``` |
42 | 244 |
|
43 | | -The [`source`](/commands/docs/source.md) command will grow the source that is compiled, but the [`save`](/commands/docs/save.md) from the earlier line won't have had a chance to run. Nushell runs the whole block as if it were a single file, rather than running one line at a time. In the example, since the output.nu file is not created until after the 'compilation' step, the [`source`](/commands/docs/source.md) command is unable to read definitions from it during parse time. |
| 245 | +This is problematic because: |
| 246 | + |
| 247 | +1. Line 1 gets parsed but not evaluated. In other words, `output.nu` is not created during the parsing stage, but only during evaluation. |
| 248 | +2. Line 2 gets parsed, but `output.nu` is not available. This results in the error. |
44 | 249 |
|
45 | | -Another common issue is trying to dynamically create the filename to source from: |
| 250 | +::: note |
| 251 | +Typing these as two _separate_ lines in the **_REPL_** will work since the first line will be parsed and evaluated, then the second line will be parsed and evaluated. |
| 252 | + |
| 253 | +The limitation only occurs when both are parsed _together_ as a single expression, which could be part of a script, block, closure, or other expression. |
| 254 | +::: |
| 255 | + |
| 256 | +Another common scenario when coming from another shell might be trying to dynamically create a filename that will be sourced: |
46 | 257 |
|
47 | 258 | ```nu |
48 | | -> source $"($my_path)/common.nu" |
| 259 | +let my_path = "~/nushell-files" |
| 260 | +source $"($my_path)/common.nu" |
| 261 | +# => Error: |
| 262 | +# => × Error: nu::shell::not_a_constant |
| 263 | +# => │ |
| 264 | +# => │ × Not a constant. |
| 265 | +# => │ ╭─[entry #6:2:11] |
| 266 | +# => │ 1 │ let my_path = "~/nushell-files" |
| 267 | +# => │ 2 │ source $"($my_path)/common.nu" |
| 268 | +# => │ · ────┬─── |
| 269 | +# => │ · ╰── Value is not a parse-time constant |
| 270 | +# => │ ╰──── |
| 271 | +# => │ help: Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally. |
| 272 | +# => │ |
| 273 | +# => ╭─[entry #6:2:8] |
| 274 | +# => 1 │ let my_path = "~/nushell-files" |
| 275 | +# => 2 │ source $"($my_path)/common.nu" |
| 276 | +# => · ───────────┬─────────── |
| 277 | +# => · ╰── Encountered error during parse-time evaluation |
| 278 | +# => ╰──── |
49 | 279 | ``` |
50 | 280 |
|
51 | | -This doesn't work if `my_path` is a regular runtime variable declared with `let`. This would require the |
52 | | -evaluator to run and evaluate the string, but unfortunately Nushell needs this information at compile-time. |
| 281 | +Because the `let` assignment is not resolved until evaluation, the parser-keyword `source` will fail during parsing if passed a variable. |
53 | 282 |
|
54 | | -However, if `my_path` is a [constant](/book/variables#constant-variables), then this |
55 | | -would work, since the string can be evaluated at compile-time: |
| 283 | +As noted in the error message, however, this can work if `my_path` can be defined as a [constant](/book/variables#constant-variables) since constants can be (and are) |
| 284 | +resolved during parsing. |
56 | 285 |
|
57 | 286 | ```nu |
58 | 287 | > const my_path = ([$nu.home-path nushell] | path join) |
59 | 288 | > source $"($my_path)/common.nu" # sources /home/user/nushell/common.nu |
60 | 289 | ``` |
61 | 290 |
|
62 | | -**Thinking in Nushell:** Nushell is designed to use a single compile step for all the source you send it, and this is separate from evaluation. This will allow for strong IDE support, accurate error messages, an easier language for third-party tools to work with, and in the future even fancier output like being able to compile Nushell directly to a binary file. |
| 291 | +::: tip |
| 292 | +The output of many Nushell commands can be a constant value if all of the command's inputs are also constant. |
| 293 | + |
| 294 | +You can see which Nushell commands operate this way using: |
| 295 | + |
| 296 | +```nu |
| 297 | +help commands | where is_const |
| 298 | +``` |
| 299 | + |
| 300 | +::: |
| 301 | + |
| 302 | +::: important |
| 303 | +For more in-depth explanation of this section, see [How Nushell Code Gets Run](how_nushell_code_gets_run.md). |
| 304 | +::: |
63 | 305 |
|
64 | | -For more in-depth explanation, check [How Nushell Code Gets Run](how_nushell_code_gets_run.md). |
| 306 | +::: warning Thinking in Nushell |
| 307 | +Nushell is designed to use a single Parsing stage for each expression or file. This Parsing stage occurs before and is separate from Evaluation. While this enables many of Nushell's features, it also means that users need to understand the limitations it creates. |
| 308 | +::: |
65 | 309 |
|
66 | 310 | ## Variables are Immutable by Default |
67 | 311 |
|
68 | 312 | Another common surprise for folks coming from other languages is that Nushell variables are immutable by default. Coming to Nushell, you'll want to spend some time becoming familiar with working in a more functional style, as this tends to help write code that works best with immutable variables. |
69 | 313 |
|
70 | | -**Thinking in Nushell:** If you're used to using mutable variables for different tasks, it will take some time to learn how to do each task in a more functional style. Nushell has a set of built-in capabilities to help with many of these patterns, and learning them will help you write code in a more Nushell-style. The added benefit of speeding up your scripts by running parts of your code in parallel is a nice bonus. |
| 314 | +Immutable variables are also key to Nushell's [`par-each` command](/commands/docs/par-each.md), which allows you to operate on multiple values in parallel using threads. |
71 | 315 |
|
72 | 316 | See [Immutable Variables](variables.html#immutable-variables) and [Choosing between mutable and immutable variables](variables.html#choosing-between-mutable-and-immutable-variables) for more information. |
73 | 317 |
|
| 318 | +**Thinking in Nushell:** If you're used to relying on mutable variables, it may take some time to relearn how to code in a more functional style. Nushell has many functional features and commands that operate on and with immutable variables. Learning them will help you write code in a more Nushell-idiomatic style. |
| 319 | + |
| 320 | +A nice bonus is the performance increase you can realize by running parts of your code in parallel. |
| 321 | +::: |
| 322 | + |
74 | 323 | ## Nushell's Environment is Scoped |
75 | 324 |
|
76 | 325 | Nushell takes multiple design cues from compiled languages. One such cue is that languages should avoid global mutable state. Shells have commonly used global mutation to update the environment, but Nushell steers clear of this approach. |
77 | 326 |
|
78 | | -In Nushell, blocks control their own environment. Changes to the environment are scoped to the block where they happen. |
| 327 | +In Nushell, blocks control their own environment. Changes to the environment are scoped to the block where they occur. |
79 | 328 |
|
80 | | -In practice, this lets you write some concise code for working with subdirectories, for example, if you wanted to build each sub-project in the current directory, you could run: |
| 329 | +In practice, this lets you write (for example) more concise code for working with subdirectories. Here's an example that builds each sub-project in the current directory: |
81 | 330 |
|
82 | 331 | ```nu |
83 | | -> ls | each { |row| |
84 | | - cd $row.name |
85 | | - make |
| 332 | +ls | each { |row| |
| 333 | + cd $row.name |
| 334 | + make |
86 | 335 | } |
87 | 336 | ``` |
88 | 337 |
|
89 | | -The [`cd`](/commands/docs/cd.md) command changes the `PWD` environment variables, and this variable change does not escape the block, allowing each iteration to start from the current directory and enter the next subdirectory. |
| 338 | +The [`cd`](/commands/docs/cd.md) command changes the `PWD` environment variables, but this variable change does not survive past the end of the block. This allows each iteration to start from the current directory and then enter the next subdirectory. |
90 | 339 |
|
91 | | -Having the environment scoped like this makes commands more predictable, easier to read, and when the time comes, easier to debug. Nushell also provides helper commands like [`def --env`](/commands/docs/def.md), [`load-env`](/commands/docs/load-env.md), as convenient ways of doing batches of updates to the environment. |
| 340 | +Having a scoped environment makes commands more predictable, easier to read, and when the time comes, easier to debug. Nushell also provides helper commands like [`load-env`](/commands/docs/load-env.md) as a convenient way of loading multiple updates to the environment at once. |
92 | 341 |
|
93 | | -_There is one exception here, where [`def --env`](/commands/docs/def.md) allows you to create a command that participates in the caller's environment._ |
| 342 | +::: note |
| 343 | +[`def --env`](/commands/docs/def.md) is an exception to this rule. It allows you to create a command that changes the parent's environment. |
| 344 | +::: |
94 | 345 |
|
95 | | -**Thinking in Nushell:** - The coding best practice of no global mutable variables extends to the environment in Nushell. Using the built-in helper commands will let you more easily work with the environment in Nushell. Taking advantage of the fact that environments are scoped to blocks can also help you write more concise scripts and interact with external commands without adding things into a global environment you don't need. |
| 346 | +::: note Thinking in Nushell |
| 347 | +Use scoped-environment to write more concise scripts and prevent unnecessary or unwanted global environment mutation. |
| 348 | +::: |
0 commit comments