'while/until loops in Nushell

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).



Solution 1:[1]

I spent entirely too long figuring this out myself, so I'm writing it up for others that are new to Nushell (no pun intended, just a happy accident). At some point, I'm sure the Nushell documentation will cover this, but it currently does not.

Short answer:

Implement while/until loops using recursive "custom commands" (a.k.a. recursive functions).

Detail:

I should have known better, since I've dabbled in functional languages in the past. But it's important to understand that Nushell's scripting language is a functional language. It's all too easy (and all too wrong) to fall back on the "habit" of trying to script it like Bash, Zsh, Fish, etc.

As the Nushell Book says in its introductory Thinking in Nushell:

Variables are immutable

Early on in Nushell's development we decided to see how long we could go using a more data-focused, functional style in the language.

Nushell's environment is scoped

And, with that in mind, the normal while/until constructs of procedural languages just aren't expected to work in a functional language like Nushell.

So 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)
$ $questions | where view_count > 100 && view_count < 110 | select view_count title link
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? # ? 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                        ?
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

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

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

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 NotTheDr01ds