3

How do you do while/until loops in Nushell script?

Since Nushell has a fairly amazing table/JSON parsing system, I've been trying to work with the Stack Exchange API through it.

One of the first challenges is looping over the multiple possible pages of results from an API call. My (normally procedural, sometimes OOP) background had me reaching for a construct in Nushell like:

let page = 1
let re = (http (echo "/2.3/questions?fromdate=1648771200&todate=1648944000&order=desc&sort=activity&site=askubuntu&page=" $page) | from json)
let questions = $re.items

while ($re.has_more) {
    let page = page + 1
    let re = (http (echo "/2.3/questions?fromdate=1648771200&todate=1648944000&order=desc&sort=activity&site=askubuntu&page=" $page) | from json)
    let questions = $questions | append $re.items
}

... or the equivalent until construct.

How would I accomplish this in Nushell?

Note - Using httpie in the above example since it automagically handles the gzip compression that the Stack API requires (unlike wget or Nushell's internal fetch command).

NotTheDr01ds
  • 15,620
  • 5
  • 44
  • 70

1 Answers1

5

Short answer:

As of Nushell 0.72, while loops are now supported, along with mutable variables to assist in the handling of such loops. until-style loops are not supported, but can be replicated through while or loop (albeit not as concisely).

Detail:

The example in the question can now be handled with:

let baseUri = "https://api.stackexchange.com/2.3/questions?fromdate=1648771200&todate=1648944000&order=asc&sort=creation&site=askubuntu&pagesize=100"
mut page = 1
let pageUri = ((echo $baseUri "&page=" $page) | str collect)
mut result = (http $pageUri | from json )
mut questions = $result.items
while ($result.has_more) {
    $page += 1
    print $page
    let pageUri = ((echo $baseUri "&page=" $page) | str collect)
    $result = (http $pageUri | from json )
    $questions = ($questions | append  $result.items)
}
$questions | where view_count > 100 && view_count < 110 | select view_count title link

Result:

╭───┬────────────┬──────────────────────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ # │ view_count │                                          title                                           │                                                 link                                                 │
├───┼────────────┼──────────────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 0 │        103 │ Find reason for &quot;apache2.service: Failed with result &#39;exit-code&#39;.&quot; and │ https://askubuntu.com/questions/1400332/find-reason-for-apache2-service-failed-with-result-exit-code │
│   │            │ &quot;Failed to start The Apache HTTP Server.&quot;                                      │ -and-failed-t                                                                                        │
│ 1 │        103 │ Public folder is forbidden in nginx                                                      │ https://askubuntu.com/questions/1400333/public-folder-is-forbidden-in-nginx                          │
│ 2 │        101 │ WSL Nano scrolling up to see terminal                                                    │ https://askubuntu.com/questions/1400431/wsl-nano-scrolling-up-to-see-terminal                        │
╰───┴────────────┴──────────────────────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────╯

And yes, Nushell actually pretty-prints the results table.

Of course, since this is more of an until-style loop, it's not as concise as it could be, since it must repeat logic before and within the while loop.

It's also possible to use the new early-loop termination feature in 0.72 (break) for a more concise until-style:

let baseUri = "https://api.stackexchange.com/2.3/questions?fromdate=1648771200&todate=1648944000&order=asc&sort=creation&site=askubuntu&pagesize=100"
mut page = 1
mut questions = []
loop {
    let pageUri = ((echo $baseUri "&page=" $page) | str collect)
    let result = (http $pageUri | from json )
    $questions = ($questions | append  $result.items)
    if $result.has_more {
        $page += 1
    } else {
      break
    }
}

However, my original, pre-0.72 answer, using recursive functions, is still a valid way of handling this (and may be more concise at times), but do keep in mind that Nushell does not have tail-recursion.

Using recursion, a basic "while" loop in Nushell might look something like:

def wloop [] {
    let re = (random bool)
    if ($re) { 
        print $re
        wloop
    }
}
$ wloop
$ wloop
$ wloop
true
$ wloop
true
true
true

And a corresponding until-loop might look like:

def uloop [] {
    let re = (random bool)
    print $re
    if ($re) { uloop }
}
$ uloop
false
$ uloop
false
$ uloop
true
false

If you need to modify a variable, keep in mind that it is scoped to its block, so you'll need to pass it back in to the recursive function. For instance, to work with the Stack Exchange API and update the page number for each call:

$ let baseUri = "https://api.stackexchange.com/2.3/questions?fromdate=1648771200&todate=1648944000&order=asc&sort=creation&site=askubuntu&pagesize=100"
$ def getAskUbuntuQuestionPageLoop [ page? ] {
    let page = if ( $page == null ) {1} else {$page}
    let pageUri = ((echo $baseUri "&page=" $page) | str collect)
    let re = (http $pageUri | from json )
    
    if ($re.has_more) {
        $re.items | append (getAskUbuntuQuestionPageLoop ($page + 1))
    } else {
        $re.items
    }
}
$ let questions = (getAskUbuntuQuestionPageLoop)

Note that each future call is appended to the current results.

Also note that the return results must be the last statement executed in the function.

Side-note: Personal opinion -- I envision that Nushell will eventually add a yield keyword to allow generator expressions. This will simply the above example further by allowing it inside a reduce that can accumulate the results.

NotTheDr01ds
  • 15,620
  • 5
  • 44
  • 70