Command Line
Run this site’s tools as commands: generate UUIDs, check an ABN, look up a BSB
⏳Loading...
About the Command Line
Most of the tools on this site are a page with a box on it. That suits work you do once, but it is slow for the small jobs you do twenty times a day. This is the same logic reached by typing instead of clicking: uuid -n 5 gives you five UUIDs, abn invents a test ABN while abn 51824753556 checks one, and bsb 062-000 names the branch a BSB belongs to.
It is a real command line, not a recording. Flags work in the forms you would expect, both -n 5 and --count=5. Quoting works, so a value that starts with a dash can be passed through. Pipes, redirects and && work, and there is a filesystem for them to write into. Tab completes a command name, a flag or a path, the arrow keys walk back through your history, Ctrl+R searches it backwards the way it does in a shell, and the screen clears with Ctrl+L, or with Cmd+K on a Mac the way it does in a terminal application. Ctrl+C stops a line that is taking longer than you meant it to.
What it can do
- uuid: UUIDs in any version, one or a hundred
- nanoid, ulid, ksuid, objectid: the shorter identifiers, for a URL or a database key
- abn, acn, tfn, medicare: check or generate Australian identifiers
- ird, nzbn: check or generate New Zealand identifiers
- iban: check an international bank account number, or generate one for a given country
- bsb, nz-bank: check a bank routing number, and for a BSB, name the branch
- password, phone: invent a password, or numbers that fit a real national dialling plan
- base64, json, jwt, hash: encode, reformat, decode a token, take a digest
- color, cron: convert a colour and check its contrast, read a cron expression back in English
- ls, cd, cat, echo, touch, mkdir, cp, mv, rm: a filesystem that lasts as long as the tab
- save, load: move a file or a folder between that filesystem and your own disk
- mount, umount: graft a real folder in at
~/mnt, read-write, so nothing has to be moved at all - copy: send the far end of a pipe to your clipboard instead of the screen
- grep, cut, head, tail, wc, sort, uniq, diff: something for the far end of a pipe
- sed, edit: change what a file says, by describing the change or by typing it
- for, while, if, case, sh: repeat a command over a list, choose between two or between several, or run a file of them
- test, seq: ask a yes or no question, and count, which is what the loops need
- env, declare, export, local, unset, set: keep a value to use again, say what kind of thing a name is, and choose how strict a script is about them
- read, getopts: take a line at a time from a file, from a pipe or from you, and read the flags a script was given
- printf, eval: build a string exactly, and run one you built
- ps, jobs, kill, wait, fg, sleep: run something in the background with
&, see what is running, and stop it - nc, ss: let two of those talk to each other over a port, and see who is listening. Python reaches the same sockets through
import socket - python: real CPython, as a program or as a prompt, for the jobs a shell is the wrong language for
- js: the browser’s own JavaScript, as a script or as a prompt, in a sandbox with no network and no storage
- ruby: real CRuby, as a program or as a prompt, with your files reached through
FileandDirthe way Ruby reaches files anywhere - lua: the official Lua 5.4, as a program or as a prompt, a quarter of a megabyte for the whole language
- sqlite3: the official SQLite against a database file in your home directory, as a query or as a prompt
- git: version control over these files, local only: commit, branch, diff and stash, with the whole history staying in this tab
- tools, open: list the rest of the site, and jump to any of it
Text in, text out
The commands that transform a value all take it as an argument or through a pipe, so they chain. cat token.txt | jwt -p | json -s decodes a JWT and prints its claims with the keys in order. cut -d , -f 2 people.csv | sort | uniq -c counts how many rows share a column, which is the shape of question a spreadsheet makes harder than it should be. And cat emails.txt | hash -l -a sha256 hashes a list a line at a time.
A few of these deliberately stop short of what the matching page does. jwt decodes a token but will not verify one, because checking a signature needs the key and a key typed at a prompt is a key left in the scrollback. password generates but does not run bcrypt or scrypt, which are slow by design and would block the terminal while they worked. Both pages have the other half.
Pipes, redirects and somewhere to put things
| sends one command’s output into the next, and > sends it to a file instead of to the screen. That is what turns a list of answers into a piece of work: uuid -n 100 > ids.txt keeps a hundred of them, cat suppliers.txt | abn -q prints the ABNs in a list that are valid and sends the rest to the screen as rejects, and abn -n 20 | abn generates twenty and checks them straight back.
The other two arrows are there as well. < reads a file into a command as though it had been piped in, so sort < names.txt and wc -l < ids.txt both work, and 2> sends what the command said about its work to a file rather than to the screen, leaving the answer where it was: cat list.txt | abn -q > valid.txt 2> rejects.txt sorts a list into two files. One of each per command, and >> and 2>> add to a file instead of replacing it. 2>&1 joins the two rather than splitting them, so cat notes.txt 2>&1 | grep missing searches what the command complained about, and &> log.txt is the same thing written short: both streams into one file.
A file can also be typed rather than saved first. cat << EOF > notes.txt takes the lines below it up to a line that is just EOF, reading $name inside them unless you quote the word, and <<< "$text" is the same idea in one line, for handing a value to a command that wanted a file. And { echo one; echo two; } > log.txt treats several commands as one so that they share a redirect, rather than making you get a > and a >> in the right order.
( echo one; echo two ) is the same idea handing back what it changed, which is what makes it worth having separately: (cd /bin; ls) leaves you where you were, and (x=1; use $x) leaves x as it found it. The variables, the functions, the arguments, the set options and the working directory all go back; the files it wrote do not, which is what a real shell does too. A ( only opens one where a command name would have gone, which is the one place this differs from a shell and the reason color rgb(255,0,0) and echo (note) still mean what they look like.
There are three directories. /bin is the commands themselves, read-only, so cat /bin/iban tells you what iban is for. /home is yours and starts empty. And /dev holds one file, null, which takes anything written to it and keeps none of it, so cat missing.txt 2> /dev/null throws a complaint away the way it does in a real shell. Tab completes a path the same way it completes a command name.
These files are kept in this browser, so a reload finds them where you left them. They are kept where only this page can reach them, they are never uploaded, and nothing else about them changes: the same files are what the Code Editor edits and what python reads. rm removes one, and the Code Editor has a Forget button that removes all of them at once.
Worth knowing rather than buried: something you type at this prompt and write to a file is on the disk of the machine you typed it on until you remove it. Variables, functions and Python’s names are not kept, which is the same split a real machine makes. save is still the only thing here that writes outside the page.
Open this site in two tabs and both of them are looking at the same files, the way two terminals on one machine are. A file written here shows up over there in the moment, without a reload, and the Code Editor redraws its tree when it does. What each tab keeps to itself is the shell rather than the disk: the working directory, the variables and the Python names are per tab, so a cd in one window does not move the other.
Two tabs writing at the same instant are put together rather than one of them winning: each keeps the files it changed, a file removed in one window goes in both, and only a file changed in both at once has to be decided. The window you were looking at wins that, and the Code Editor says which file it was. The other copy is gone rather than filed somewhere.
Changing a file that is already there
Everything above reads a file or writes a new one. Two commands change one. sed 's/old/new/' notes.txt replaces text inside each line, and an address in front of that narrows it to one line, so sed '2d' notes.txt drops the second line and sed '/^#/d' notes.txt drops the comments.
There is no -i and there does not need to be. A redirect here writes its file after the whole line has finished rather than before it starts, so sed 's/a/b/' f.txt > f.txt reads the old contents and writes the new ones. Put it in a loop and it is a change across a directory: for f in $(ls); do sed 's/TODO/DONE/g' $f > $f; done.
For the times a change is easier to type than to describe, edit notes.txt opens the file in a window with the whole thing in front of you, and saving writes it back. It is the one command that waits for you rather than the other way round. Closing it without saving leaves the file alone and reports a failure, so edit build.sh && sh build.sh does nothing when you change your mind.
A script is coloured while you write it. Python, shell, JavaScript, HTML, CSS, JSON, Markdown, YAML and XML are recognised by the name of the file, and a file with no useful name is recognised by its first line, so typing #!/bin/sh at the top of one is enough. Anything else is left in plain text, which is what a list of identifiers wants to be.
Running one thing after another
&& runs the next command only if this one worked, || only if it did not, and ; runs it either way. So uuid -n 100 > ids.txt && wc -l ids.txt writes the file and then checks its own work, and load || echo nothing came in has something to say when you close the file picker. A ! in front turns the answer round, so if ! test -f lock.txt; then echo free; fi asks whether something did not happen without an empty then.
Which means every command here has an opinion about whether it worked, and for a few of them that is more interesting than it sounds. grep counts finding nothing as not having worked, so grep 5182 ids.txt && save ids.txt only saves the file when there was something in it worth saving. diff says the same about finding a difference, an invalid number says it about being invalid, and a save you cancelled says it too: nothing went wrong, and the file still is not there.
A mistake anywhere on the line stops all of it, which is where this parts company with a real shell. Nothing here is a separate process, so running half a line and then admitting it cannot finish would leave output on the screen from work you did not get. Fix the typo, press Up, and nothing has happened twice.
Choosing, and naming what you do twice
case $f in *.txt) wc -l $f;; *.md) head $f;; esac compares one value against several patterns and takes the first that matches, which is the chain of elifs said once. | puts several patterns on one branch and *) is the branch for everything else.
How a branch ends says what happens next. ;; stops there, ;& runs the next branch’s body without asking whether its patterns match, which is how two answers share a tail, and ;;& goes on trying the patterns after it, which turns a case from the first match into every match.
[[ ]] is the question test cannot ask, because && and || live inside the brackets rather than between two commands: [[ -f $f && $f == *.txt ]] is one question with two halves, and the pattern on the right of == is a pattern where test would only ever have compared text. =~ beside it takes a regular expression rather than a pattern, so it asks what appears anywhere in the value rather than what the whole of it looks like, and the pieces it matched are left in ${BASH_REMATCH[1]} and the ones after it.
greet() { echo hi; } gives a run of commands a name. It reads its arguments the way a script does, with $1 and $#, and for f; do inside one walks them without having to name a list; return leaves early and local n=0 keeps a variable inside the call. A function lasts as long as the tab, as a variable does and unlike the files, which are kept.
Keeping a value to use again
name=value remembers something and $name reads it back, with ${name} for when the next character would otherwise look like part of the name. env lists what is set, unset name forgets it, and $? is the status of the last command, 0 when it worked.
A name can carry its own fallback. ${name:-default} reads the default when nothing is set, ${name:=default} also remembers it, ${name:+other} answers only when something is set, and ${name:?why} says why it could not carry on. Drop the colon to count a name set to nothing as set. ${#name} is how long the value is.
A name can hold several values instead of one. files=(*.txt) and files=($(ls)) both make an array, ${files[0]} is the first, ${files[-1]} the last, and ${#files[@]} how many there are. The reason to have one is "${files[@]}", which is one argument per value however many spaces are in the names, and it is the only expansion here that turns one word into several. Its star form, "${files[*]}", joins them into one word instead. Type help array for the rest.
A subscript can be a word instead of a number, once the name has been declared to take one. declare -A colours, then colours[red]=#f00, and ${colours[red]} reads it back. ${!colours[@]} is the keys, so for k in "${!colours[@]}" walks the pairs, and unset colours[red] removes one. The declaration is the whole of what makes the brackets take a word: without it a subscript is a sum, and red and blue are both the sum 0.
A ! inside the braces asks about the name rather than the value. ${!ref} reads ref and then reads whatever it named, which is the only way to reach a variable whose name a script worked out; ${!host_@} is every name beginning with host_; and ${!files[@]} is which of an array’s places are filled rather than what is in them, so for i in ${!files[@]} walks it by index.
The same braces take a value apart, which is what saves reaching for a tool to do it. ${p##*/} is the name at the end of a path and ${p%/*} is the directory in front of it, because # cuts a pattern off the front and % off the back, doubled to cut the longest match instead of the shortest. ${f/a/b} swaps the first match for something else and ${f//a/b} swaps them all, ${f:2:3} takes three characters from the third, and ${f^^} and ${f,,} are the same letters in the other case. Type help ${ for the whole list.
$(command) is a command used as a value, which is what makes the rest of it worth having: here=$(pwd), uuid -n $count, or echo hits > $(cat name.txt), since the file a redirect names is expanded like anything else. Backticks are the older spelling of the same thing and work too, so a script pasted in from somewhere older still runs.
$((n + 1)) works out a sum in the same position, in whole numbers, reading a variable with or without its $ and counting an unset one as nothing, so total=$((total + n)) works on the first turn of a loop. It only ever works a number out: there is no n++ and no assignment inside the brackets, since an expansion that quietly changed the session would be the only one here that did.
One rule here differs from every shell, on purpose: expansion never splits a value into several arguments. $name is one argument however many spaces are in it, and $(cmd) is one argument holding the output lines joined by spaces. Unquoted splitting is the largest single source of surprise in shell scripts, and nothing here needs it, because a list of values travels down a pipe and a pipe is line-oriented already.
Single quotes do not expand, and neither does anything an expansion produced. A value that happens to contain $(rm -r /home) is text, not a command waiting for somebody to say its name. Variables live in the tab alongside the files, and go the same way when you reload.
The exception, and it is the only one, is eval, which reads its arguments as a line and runs them. That is what it is for: cmd="wc -l" followed by $cmd looks for a command called wc -l, since nothing here splits an expansion, and eval $cmd is the spelling that runs it. It is also the one command that will run a string somebody else wrote, so treat what you hand it the way you would treat a script you did not read.
Doing it to every file, and only sometimes
for f in $(ls); do wc -l $f; done runs the same command once for each thing in a list, and if grep -q invalid ids.txt; then echo found one; fi runs it only when a question comes back yes. while repeats for as long as the question keeps coming back yes, until is the same loop asking the opposite, and break and continue leave one early. A number after either of those says how many loops it is about, so break 2 inside a loop inside a loop leaves both.
Two small commands exist because of these. test, written [ ] if you prefer, asks the questions no other command answers: whether a file is there, whether a string is empty, whether one number is larger than another. And seq counts, for the loops whose numbers are worked out as the line runs: for n in $(seq $first $last).
When you know the numbers as you type them, write them as a brace. for i in {1..10} counts, {1..10..2} counts in twos, and {a,b} chooses between words, which works wherever a word can go rather than only in a loop: touch file{1..3}.txt makes three files. A brace is expanded before anything is read, so {1..$n} is text and not a range, and quoting one keeps the braces themselves. This is the reason a shell script pasted in from somewhere else usually runs as written.
*.txt is the other way one word becomes several: it is matched against the files in this tab before any command sees it, so cat *.txt and for f in *.txt both work, with ? standing for exactly one character and [ab] for any one of the characters in it, or [a-z] as a range and [!ab] for everything it does not name. A pattern that matches nothing is handed on as text, as in a shell. It splits what you typed and never a value, so a variable holding a star is a star, and only the last part of a path may hold one.
For the lines of a file rather than the files themselves, while read -r line; do echo "[$line]"; done < notes.txt is the loop that does not split them, and read is the only command here that takes its input a line at a time rather than all at once. The redirect goes on the done and opens the file once for the whole loop, which is what lets each turn take the next line, and the read that finds nothing left is what ends it. A pipe works the same way, so ls | while read -r f; do wc -l $f; done is fine too. Several names split the line and the last one takes whatever is left, so read -r name rest puts a whole sentence in rest.
There is one cursor per loop, and everything in the body reads from it in the order it asks. So printf "a\nb\n" | { read one; cat; } gives the first line to read and the rest to cat, the same as a shell, and a command that never looks at its input never moves the cursor: an echo in the body leaves the file where the read beside it will find it. That holds for anything a pipe can feed, so echo a | if grep -q a; then ... and a function called from a pipeline both read what was piped in.
With nothing piped or redirected into it, it asks you instead: read -rp "Your name: " name; echo "hello $name" stops at the first command and waits, with -p standing where you are about to type and the two left on one line afterwards. Without it the prompt is a ?. Ctrl+D says there are no more, which is how a while read with nothing behind it ends, and Ctrl+C stops the whole line. A file that runs out never turns into a question: a loop over one ends at the end of it rather than asking you to type the rest, and a -p on a read that is taking its lines from a file is ignored rather than printed.
A loop is the one place a value gets split on its spaces. Everywhere else $name stays one argument, but a list is exactly what is being written after in, so for f in $(ls) walks the files rather than looping once over the whole directory. Quoting still keeps a value whole.
You do not have to fit a loop on one line. Press Enter with a block still open and the prompt turns into a > and waits for the rest of it, the same as a shell; Ctrl+C abandons the whole block. Ending a line with a \ does the same for a command that is simply long, carrying it on down the next line. A loop prints as it goes, a turn at a time, so you can watch a long one work rather than waiting for the whole thing.
There are three limits a real shell has no need for, and all three are about the page rather than the work. Ten thousand lines from one typed line, because every one of them becomes part of this page; what that line sends to a file, down a pipe or into a variable is not counted against it and is never cut short, however long it is. A hundred thousand lines made altogether, which is the one that stops a loop with no way out of filling the tab. And twenty thousand kept on the screen across everything you have run, after which the oldest are forgotten from the top the way a terminal’s scrollback forgets: whole commands fall off rather than being trimmed inside, and what you are looking at stays. A loop that prints nothing meets none of them and runs until you stop it.
Writing it down and running it
sh build.sh runs a file of commands, which is where a loop actually belongs: almost nobody types one at a prompt. Write the script in your own editor, drop it on the terminal the way you would any other file, and run it. # starts a comment and a #! line at the top is ignored, so a script written for a real shell reads the same here.
Whatever you write after the file belongs to the script. sh greet.sh world puts world in $1, with $# counting them, $@ holding all of them and $0 naming the script. They last exactly as long as that script runs, so one script calling another gets its own and hands back what it found.
Flags among them belong to the script as well, so sh deploy.sh -v -n 3 hands all four words over rather than looking for a -v of its own. getopts is how the script reads them, one per call, which is why it is always written inside a while: while getopts "n:v" opt; do case $opt in n) count=$OPTARG;; esac; done, and then shift $((OPTIND - 1)) to get past them to the rest. Letters bundle, a value may be written against its letter or after it, and help getopts has the whole of it.
The line most scripts start with works here too. set -e stops at the first command that failed, so a script cannot carry on over a step that did not work, and it looks past a failure something was asking about: the test of an if, everything but the last command of an && list, anything turned round by a !. set -u refuses to read a name nobody set, which turns a typo in a variable into an error rather than an empty string, and with a subscript it asks about the one place, so ${arr[5]} past the end and ${m[nope]} for a key nobody set are caught too. ${name:-} is how to say one is optional on purpose. set -x prints each command as it runs with a + in front, after expansion, which is the fastest way to see which of two names was wrong. And set -o pipefail reports the stage of a pipeline that failed rather than only the last one. A plus turns each of them back off.
It runs in this session rather than one of its own, which is the opposite of what sh does in a terminal and the more useful of the two here: the files it writes and the variables it sets are still there when it finishes. It can do anything you could have typed, including save and copy, so a script you did not write is code you did not read, the same as anywhere else. The one thing it does differently is open: going to another page ends the script there, since the terminal it was printing to goes with it.
Two things at once
A line ending in & runs in the background and gives the prompt straight back, so python slow.py & leaves you free to type while it works. What it prints lands above the prompt as it happens, without eating the line you are halfway through. jobs lists what you started and walked away from, ps is the wider question and always includes itself, wait holds the prompt until a job is done, and fg brings one back so Ctrl+C reaches it. kill asks a process to stop by the pid those commands print. Ctrl+C is aimed at whatever the terminal you typed it in is running in front of you, so a job you deliberately put behind you carries on.
Two processes can talk. nc -l 7000 waits for somebody, nc 7000 joins them, and what one sends the other prints: nc -l 7000 & and then echo hello | nc 7000. ss says who is listening and who has whom. A port belongs to the process holding it and goes back the moment that process ends, however it ends.
The two processes do not have to share a tab. Every tab of this site is on one port space, so a server started here answers a nc, a curl or a Python client typed in another window, and two windows side by side are a real client and server. ss has a TAB column for exactly that: a dash is this tab, a name like tab-2 is another one. Pids stay each tab's own, so kill only ever reaches processes here; a foreign listener ends when its own tab ends it, or closes. In a browser without SharedWorker, sockets stay within their tab and everything else is unchanged.
A redirect can be the client on its own, as it can in bash: echo hi > /dev/tcp/localhost/7000 says something and cat < /dev/tcp/localhost/7000 hears the answer. There is no such directory, here or on a real machine: the shell reads the name and opens a socket, so ls /dev shows nothing of the sort and cat on that path finds nothing.
Python is on the same table, and through the name it is written under everywhere else: import socket is the standard library shape, so a file with bind, listen, accept and sendall in it runs here unedited and runs on a real machine afterwards. So does http.server: python server.py & and then echo 'GET / HTTP/1.0' | nc 8000 is a real HTTP server and a client, both in this tab. The client can be Python too, http.client and all: this page runs two interpreters, so a server and the program talking to it are two programs rather than a queue.
A script is on the same table too, through sockets, and serve(handler) is written on it: the handler takes a Request and gives back a Response, which is what Cloudflare Workers, Deno and Bun all take. So serve app.py 8000 & and serve app.js 8000 & are the same command in two languages, neither handler shape invented here, and a Python client can call the JavaScript one.
curl is the short way to ask any of them. curl localhost:8000/api prints what came back, and the flags are the ones curl has, so a line copied out of a README runs: -X, -H, -d, -o, -i, -L, -f, -s, -v. So are the exit statuses, which is why curl -sf localhost:8000/health && echo up means here what it means anywhere. A host that is not this machine is refused by name, and there is no https://, because a socket that never leaves has no certificate to check.
What is not here says so instead of pretending: no UDP, no timeouts, no non-blocking mode.
None of that is a network, and there is not one to be had: a browser cannot open a socket to a host, and nothing here would if it could. Both ends are always processes in this browser, on this site. A host may be given and two programs naming the same one will meet, but it is a label rather than a place, and localhost is the only machine there is.
When the shell is the wrong language
Some jobs are a loop and a pipe, and some are neither. python -c "print(sum(range(101)))" runs CPython, and it is the real one: built for WebAssembly, but the same interpreter, so a traceback is a traceback, re has lookbehind and decimal rounds the way the documentation says it does. A smaller Python that got the easy things right and the hard things quietly wrong would cost more to explain than it saved to download. python script.py and cat script.py | python both work, so a file you dropped on the terminal runs without a middle step.
It sees the same files you do. Your home directory goes in with the program and whatever it leaves behind comes back out, so python -c "open('out.txt','w').write('hi')" is followed by cat out.txt without either side being told the other exists. That is the point of having it here rather than in a tab of its own: the awkward step in the middle of a piece of shell work can be five lines of Python and then carry on down the pipe.
It behaves like a program at a terminal. Output arrives as it prints rather than all at the end, so a long one is something you can watch. input() reads from this prompt: the marker turns into a ? while a program is waiting for you, what you type goes to it rather than to the shell, and it is shown back the way a terminal shows what you typed, on the screen but not down the pipe. Ctrl+C stops a program that will not end, including one sitting at a prompt, and what it printed first stays where it is.
python on its own opens a prompt at the interpreter rather than running anything. The marker becomes >>>, and ... while a block is unfinished, which a blank line closes the way it does at any Python prompt. The value of a bare expression is shown, _ is the last one, and Tab completes from the names the session has rather than from commands and files, since those are not what could be typed there.
What you bind lasts until you reload, the way a file written with > does, and leaving is not the end of it: exit() or Ctrl+D goes back to the shell, and typing python again finds everything where it was. Going back is often the point, since the file you just wrote is sitting in the directory the shell is standing in. An error at that prompt is printed and survived rather than being the end of anything, and so is Ctrl+C: the loop stops, the traceback is a traceback, and what you had bound is still bound.
One thing it does not have, for the same reason everything else here does what it does. There is no pip, because installing a package means fetching it from somewhere, and nothing on this site talks to another host; the whole standard library is there and that is the whole of it, so import requests will never work any more than a browser can open a socket.
The interpreter is a few megabytes and arrives the first time you type python, which takes a second or so. After that it is cached like everything else, so it starts immediately and works with the network off. All of it works from the Ctrl+K prompt on any page as well as from here, including Ctrl+C and input(): those two share memory with the interpreter, which a browser only allows a page that has asked to be isolated from every other site, and every page of this site asks.
JavaScript, kept at arm’s length
js build.js runs a file, js -e "console.log(1 + 1)" runs a line, and cat build.js | js runs what came down the pipe. It is the engine the browser already has rather than an imitation, so the language is whatever version it speaks and await works at the top of a file. The same home directory goes in and comes back, through an fs object with read, write, exists, list, remove and mkdir, all of them ordinary calls rather than promises.
It is the one language here that had to be kept away from the page, because it is the language the page is written in. A script evaluated on this thread would hold the same globals the terminal does, so it runs in a worker built for that run and killed when it ends. Ctrl+C is that worker being killed, which is why it stops an endless loop as surely as it stops anything else, and why nothing a script leaves behind can reach the next one.
Inside it there is no network and no storage: XMLHttpRequest, WebSocket, indexedDB and caches are all absent, and so is Worker, which is what stops a script starting a fresh one and getting them back. It is a fence rather than a jail: it is a list of names, it is written down in the source, and a browser that grows a new way out grows it there too.
fetch is the one on that list that was replaced rather than removed, and the reason is worth saying. A script written anywhere else says fetch, and a runtime that answers with a name of its own instead runs no code anybody wrote. So the name stays and the far end changes: fetch('http://localhost:8000/') reaches whatever serve started on 8000 in this tab, and hands back a real Response. Any other host is refused by name. What mattered was never that the word was missing, only that no request leaves.
There is no require, no import and no npm install, for the reason there is no pip. There is a prompt: js on its own opens one, the way python does. The marker becomes >, the value of a bare expression is shown with _ holding the last one, await works at it, and what a line binds is still bound on the next, in a realm that lives as long as the tab and survives exit(). The one thing to know before you need it: Ctrl+C there still stops a line by killing the realm, names and all, because that certainty is the whole reason Ctrl+C here always lands. The files are never part of the loss; those live with the shell.
Ruby, the third interpreter
ruby script.rb runs a file, ruby -e "puts (1..100).sum" runs a line, and ruby on its own opens a prompt, for which irb is the same door. It is real CRuby built for WebAssembly, the way the Python here is real CPython: blocks, procs and method_missing behave the way the book says, a backtrace is a backtrace, and the standard library is the standard library. The interpreter is nine megabytes and arrives the first time you ask for it; after that it is cached like everything else and works with the network off.
It reaches your files the way Ruby reaches files anywhere: File.read, File.write, Dir.glob and Dir.mkdir are the actual methods, not lookalikes, and they see your home directory from wherever the shell is standing. What a program writes is here afterwards, so ruby -e "File.write('out.txt', 'hi')" then cat out.txt works without either side being told about the other.
The prompt is irb with this terminal’s manners: the marker becomes >>, every line’s value is echoed with =>, _ is the last one, and a name bound on one line is still bound on the next. A block left open turns the marker into ... until the end arrives, strings included, since a Ruby string may span lines. Ctrl+C stops a line the way the JavaScript prompt stops one, by killing the VM the names live in, and says so when it happens; the files are never part of that loss. exit or Ctrl+D goes back, and leaving is not forgetting.
The same line is drawn in the same place: no gem install, for the reason there is no pip and no npm, and no network from inside a program. What there is instead is the tab’s own socket table, through TCPSocket and TCPServer in their stdlib shape, so a Ruby client can call whatever nc -l or serve started and a Ruby accept loop reads the way the book says. serve app.rb 8000 & is the ready-made version: the file holds a Rack app, the triple puma would take, and each request is one trip into the interpreter.
Lua, the fourth interpreter
lua script.lua runs a file, lua -e "print(2^10)" runs a line, and lua on its own opens a prompt. It is the official Lua 5.4 built for WebAssembly, so metatables, coroutines and the standard library behave the way the book says, and it is the featherweight of the four: the whole interpreter is a quarter of a megabyte, less than this page’s font, so the first run costs a blink.
It reaches your files the way Lua reaches files anywhere: io.open, io.lines, loadfile and require are the actual functions, they see your home directory from wherever the shell is standing, and what a program writes is here afterwards. arg carries the script’s arguments with its name at index zero, and os.exit(1) means what it means anywhere.
The prompt has this terminal’s manners: every line’s values are echoed with => and tables are written out rather than shown as addresses, _ is the last value, and a global bound on one line is still bound on the next, while local lasts one line the way it does at the standalone prompt. A block left open turns the marker into ... until the end arrives. Ctrl+C stops a line the way the other prompts stop one, by killing the engine the names live in, and says so when it happens; the files are never part of that loss. os.exit() or Ctrl+D goes back, and leaving is not forgetting.
The same line is drawn in the same place: no LuaRocks, and no network from inside a program. What there is instead is the tab’s own socket table, through a socket module in LuaSocket’s shape, so a Lua client can call whatever nc -l or serve started and a server loop reads the way the book says. serve app.lua 8000 & is the ready-made version: the file holds a function taking a request table and answering status, headers, body, and each request is one trip into the interpreter.
It was also the first language here with a debugger, and no longer the only one: lua --debug file.lua, python --debug file.py and ruby --debug file.rb all run a file under it, pausing on breakpoints set in the Code Editor, where the gutter, the step buttons and the locals live; the flag is what the Code Editor’s Debug button types. It is the real interpreter standing still, a trace hook holding the whole worker on shared memory until a button answers, and a plain run never arms the hook, so ordinary runs cost what they always cost. With no editor open on the page the flag arms nothing and the program simply runs.
SQLite, a database as a file
sqlite3 data.db "select * from people" runs a query, cat setup.sql | sqlite3 data.db runs a script, and sqlite3 data.db on its own opens the prompt, marker and all. It is the official SQLite compiled to WebAssembly by its own project, so the SQL is the whole SQL: window functions, CTEs, the JSON functions, triggers and views, with errors in sqlite3’s own words.
The database is an ordinary file in your home directory. It is rewritten after every input, so ls -l data.db always shows the last committed state, a Ctrl+C costs at most the statement it stopped, and dragging the file out hands you a database any sqlite3 anywhere will open. Dragging one in works the same way in reverse. sqlite3 with no file is a transient in-memory database, matching its manners elsewhere.
At the prompt, statements end with ; and a line without one continues on the next, exactly as the real prompt behaves. The open connection is the session, so a transaction can span lines and temp tables live between them; the dot-commands are the useful subset, .tables, .schema, .mode, .headers, .import, .read and .dump, with .help to list them and .exit or Ctrl+D to leave.
Rows draw as a box you can read, and the flags are for pipes: sqlite3 --json data.db "select ..." | json composes with the JSON tooling, --csv feeds anything that eats CSV, and .import rows.csv people goes the other way, creating a missing table from the header row. Nothing leaves the browser: the engine, the file and every row stay in this tab.
Git, history without a network
git init starts a repository where you stand, and the local verbs work the way their manual says: status, add, commit, log, diff, branch, checkout, stash, reset, config. It is real git plumbing, from isomorphic-git, and the objects under .git are git’s own: a folder saved as a zip opens on any machine with its history intact, git log and all.
There are no remotes, and that is the design rather than a gap: nothing on this site talks to another host, so clone, push, pull and fetch are refused by name with the reason. The repository lives whole in this tab, survives a refresh with the rest of the tree, and rm -rf .git un-inits it the way it always has.
Commits are signed you <you@this-tab> until git config user.name "..." says otherwise, and .gitignore works. The everyday safety nets are the point: git diff before committing, git checkout file.txt to put a file back the way the last commit had it, git stash when a half-done change is in the way, and a branch when an experiment deserves its own line of history.
Getting files in and out
Drag a file onto the terminal, or type load to pick one, and it lands in the working directory ready to be read. That is usually the point of all this: cat suppliers.csv | abn -q checks a column of numbers that started life in a spreadsheet. Dragging a whole folder works too, and the folders inside it are kept, up to 2 MB a file. A file that is not text arrives as itself: a folder of pages keeps its images, and serve will answer with them.
save ids.txt sends a file back the other way, and save work sends a directory as a zip, which you can drop back onto the terminal later to get the directory again. You can skip the middle step entirely: uuid -n 100 | save ids.txt never writes anything in the tab at all.
Loading reads; it does not upload. The bytes go from the file you chose into this page and no further, the same as everything else here. Saving is the one moment the site touches your disk, it happens because you typed a command, and your browser decides where the file lands.
Mounting a folder instead
mount skips the moving altogether. It opens your browser's folder picker, and the folder you choose appears at ~/mnt/<name>, read-write: every command works on it, and so do python, js, ruby, lua and the editor. What a line changes under ~/mnt reaches the real folder as the line finishes, deletions included, so a script that rewrites twenty files has rewritten the twenty files.
One rule makes that safe to offer: nothing is ever removed from the real folder that this tab did not carry in. A file too big to carry, and anything another program creates after the mount, is invisible here and therefore untouchable. The other honest limit is that the tab does not watch the disk: edits made outside it are not seen until mount -r <name>, and a refresh takes the disk as the truth.
A mount lives as long as the tab. Reloading lets it go without touching the disk; umount <name> settles anything the current line still owed first. The picker is the consent step and the browser's own: Chrome and Edge have it, Firefox and Safari do not yet, and mount says so rather than half working.
Getting an answer out without a file
Most of what you type here is wanted somewhere else a second later, and a file is a heavy way to carry one line. uuid -n 5 | copy ends the pipe at your clipboard, and copy ids.txt does the same with something already in the filesystem. A single value arrives without a trailing line break, so pasting it into a form field gives you the value and not an accidental Enter.
Selecting output with the mouse copies it too, the way iTerm2 and the X terminals do, which is quicker than any command for the two lines in front of you. What you dragged across stays marked until you start the next selection, and a small copied appears for a moment where you let go, so nothing about it has to be guessed at.
It only goes one way. There is no paste command and nothing here reads your clipboard, because a terminal that could read it could read whatever came out of your password manager a moment earlier.
It follows you around the site
This page is not the only way in. Press Ctrl+K, or Cmd+K on a Mac, on any page of this site, or use the terminal button in the header, and the same prompt opens over whatever you are doing. Escape closes it and puts the cursor back where it was. It is the same terminal with the same commands, so anything described here works there.
Once the prompt has the cursor, that chord means what it means in a terminal application: it clears the screen rather than closing the window. Pressing it out of habit halfway through a piece of work should tidy the scrollback, not throw the work away.
Checking and generating
Seven of the nine identifier commands do both, and which one you get falls out of what you typed. A number after the command is a number to check, so abn 51824753556 checks it. Nothing after the command means invent one, so abn hands you a test ABN the same way uuid and password do. -g still works and still wins, so anything you had written down keeps meaning what it meant. The other two, bsb and nz-bank, only ever check, for the reason below.
A pipe counts as having been given something, even when it carried nothing. cat suppliers.txt | abn on an empty file says nothing rather than inventing a number, because that question has already been answered and making one up would be the command inventing a fact about your file.
Why some commands refuse to generate
There is no bsb --generate, and no generator for New Zealand bank accounts. An ABN or a test TFN that passes a checksum is just arithmetic, but six digits that happen to be an allocated BSB name somebody’s real branch, and an account number that passes the check digit routine names somebody’s real account. Type help bsb and the terminal says so itself.
Ctrl+C, and what it leaves behind
Commands do not run on the same thread as the page. They run in a Web Worker alongside it, which means a line doing real work, a hundred thousand UUIDs, a hash of every line of a large file, cannot stop the page scrolling or the prompt taking keys. It also means a line can print while it is still running, which is why a loop or a script shows its work rather than appearing all at once at the end. And it means there is something to interrupt.
Ctrl+C asks the line to stop, and it notices at its next command or its next turn of a loop. What it had already done stays done: a script stopped halfway through writing five files leaves the ones it had written, the way a real shell leaves what a killed cp had copied. It reaches only what the terminal you typed it in is running, so the Ctrl+K overlay and this page cannot stop each other, and a job you put in the background carries on until you kill it.
Some commands never look up. A single command counting to a hundred million has no next statement to notice at, so when nothing has stopped after a quarter of a second the thread is taken back by force: the engine is killed, and the session that comes back is the one the last finished line left. That is the one case where a cancel can undo, and it takes everything running with it. What was already printed stays on screen either way, since that is a record of what happened rather than part of the session. With nothing running, Ctrl+C clears what you have typed, which is the other thing it does in a shell.
Nothing you type is sent anywhere
Every command runs in your browser tab. There is no server to receive a command, because this site is a set of static files. The one exception is the branch details behind bsb, which are fetched from this site as a small data file the first time you ask for one, and never include what you typed.
BSB branch data comes from Australian Payments Network Limited (AusPayNet), "BSB Directory". Any interpretation of it is ours, not AusPayNet’s.
It does not download what you do not use
The page knows the name, the flags and the help text of every command from the start, which is how it can complete a name or reject a typo instantly. The code that does the work is fetched per command, the first time you run it, so the page starts out roughly the size of one tool rather than 33. Opening it and typing uuid downloads the UUID generator and nothing else.
It goes one level further where a command has a library behind it. hash uses the browser’s own cryptography for SHA-1 and SHA-2 and costs nothing extra; MD5, SHA-3 and RIPEMD-160 fetch an implementation only when you name one. uuid -v 5 hashes with the browser’s SHA-1 and costs nothing; version 3 wants MD5, which no browser will provide, so that one version fetches a library and the other seven do not. The cron parser arrives when you type cron and not before.
python is the case that makes the rule worth having. A whole interpreter is larger than the rest of this page put together by some way, and somebody who opened the terminal to decode a token should not pay for it on the chance that they might. So it is fetched on the first python you type and never before, and once fetched it is kept, which is why the second one starts at once and works offline.
Frequently Asked Questions
What is the Command Line tool?
It is a terminal in your browser that runs the same logic as the tool pages on this site, reached by typing instead of clicking. Typing "uuid -n 5" gives you five UUIDs, "abn" on its own invents a test Australian Business Number, "abn 51824753556" checks one, and "bsb 062-000" names the branch a BSB belongs to. It is aimed at the small jobs you repeat many times a day, where opening a page and clicking a button is slower than typing six characters.
Which commands are available?
Sixty-five, in five groups. uuid, nanoid, ulid, ksuid, objectid, password and phone invent a value. abn, acn, tfn and medicare check or generate Australian identifiers, ird and nzbn do the same for New Zealand, iban checks or generates an international bank account number, and bsb and nz-bank check bank routing numbers with bsb also naming the branch. base64, json, jwt, hash, color and cron transform or explain a value you already have. ls, cd, cat, echo, touch, mkdir, cp, mv, rm, save, load and copy work on a small filesystem, and edit opens one of its files in a window you can type into. grep, sed, cut, head, tail, wc, sort, uniq and diff give a pipe somewhere to end. tools lists everything else on the site, open jumps to any of it, env and unset handle the variables you set, and sh runs a file of commands with test and seq there to serve the loops, while python runs CPython for the jobs a shell is the wrong language for. Type help to see the current list, or help followed by a command name for its flags and examples, or help for to read about the loops.
Can it decode a JWT, hash a string or convert a colour?
Yes, and they chain, which is the reason to do it here rather than on the pages. "cat token.txt | jwt -p | json -s" decodes a JSON Web Token and prints its claims with the keys in order. "hash -a sha256 hello" takes a digest, with -l to hash each line of a list separately. "base64 -d" decodes either alphabet, padded or not. "color #3b82f6 --on white" converts a colour between notations and reports its contrast under both WCAG 2 and APCA. "cron '0 9 * * 1-5'" says what a schedule means and when it next runs. Each of these mirrors a tool page, and each is downloaded only when you first type it.
Will the jwt command check a signature?
No, on purpose. Verifying a token means supplying the key, and a key typed at a prompt sits in the scrollback of a page you can scroll back through, which is a worse place for it than almost anywhere else. So the command decodes the header and the payload, reads the time claims, and says on every run that the signature was not checked, because a decoded token proves nothing about who wrote it. The JWT Decoder page has the verifying half if you need it. The password command draws the same line for the same kind of reason: it generates, but the bcrypt and scrypt hashing stays on its page, where a slow algorithm does not block a prompt you are typing at.
Can I pipe one command into another?
Yes. A vertical bar sends one command output into the next, and a greater-than sign writes it to a file instead of the screen, with two of them appending rather than replacing. "uuid -n 100 > ids.txt" keeps a hundred UUIDs, "cat suppliers.txt | abn -q" prints the valid ABNs in a list and reports the rest as rejects, and "abn -n 20 | abn" generates twenty and checks them straight back. Only the results travel down a pipe: warnings and errors go to the screen wherever they happen, the same way they do in a real shell. A less-than sign reads a file into a command as though it had been piped in, so "sort < names.txt" works, and "2>" sends what a command said about its work to a file rather than to the screen, so "cat list.txt | abn -q > valid.txt 2> rejects.txt" sorts a list into two files. One redirect of each kind per command, and "2>>" appends like ">>" does. A file can be typed rather than saved first: "cat << EOF > notes.txt" takes the lines below it up to a line that is just EOF, reading "$name" inside them unless the word is quoted, and "<<< "$text"" is the same idea in one line for handing a value to a command that wanted a file. "{ echo one; echo two; } > log.txt" treats several commands as one so they share a redirect, and "( echo one; echo two )" is the same idea handing back what it changed, so "(cd /bin; ls)" leaves you where you were and "(x=1; use $x)" leaves x as it found it. The variables, functions, arguments, set options and working directory all go back; the files it wrote do not, which is what a real shell does too. A ( only opens one where a command name would have gone, which is the one place this differs from a shell and the reason "color rgb(255,0,0)" and "echo (note)" still mean what they look like.
Can I run one command only if another one worked?
Yes. Two ampersands run the next command only if this one worked, two vertical bars only if it did not, and a semicolon runs it either way. "uuid -n 100 > ids.txt && wc -l ids.txt" writes the file and then checks its own work, and "load || echo nothing came in" has something to say when you close the file picker. An exclamation mark in front of a command turns its answer round, so "if ! test -f lock.txt; then echo free; fi" asks whether something did not happen without an empty then. A few commands have a more interesting idea of working than you might expect: grep counts finding nothing as not having worked, so "grep 5182 ids.txt && save ids.txt" only saves a file with something in it, diff says the same about finding a difference, an invalid number says it about being invalid, and a save you cancelled says it too. One thing differs from a real shell: a mistake anywhere on the line stops all of it, so "echo one; wcc" prints the typo and nothing else. Nothing here is a separate process, so running half a line before admitting it cannot finish would leave you with output from work you did not get.
Can I keep a value and use it again?
Yes. "name=value" remembers something, "$name" or "${name}" reads it back, "env" lists what is set and "unset name" forgets it. A name can carry its own fallback: "${name:-default}" reads the default when nothing is set, "${name:=default}" also remembers it, "${name:+other}" answers only when something is set, "${name:?why}" says why it could not carry on, and dropping the colon counts a name set to nothing as set; "${#name}" is how long the value is. A name can hold several values instead of one: "files=(*.txt)" and "files=($(ls))" both make an array, "${files[0]}" is the first, "${files[-1]}" the last and "${#files[@]}" how many there are, with the subscript worked out rather than read so "${files[i + 1]}" counts. The reason to have one is ""${files[@]}"", which is one argument per value however many spaces are in the names, and it is the only expansion here that turns one word into several; the star form joins them into one word instead. "files+=(more.txt)" adds to the end and "files[2]=x" writes one value. A subscript can be a word rather than a number once the name has been declared to take one: "declare -A colours" and then "colours[red]=#f00", with "${colours[red]}" reading it back, "${!colours[@]}" being the keys, "for k in "${!colours[@]}"" walking the pairs and "unset colours[red]" removing one. The declaration is what makes the brackets take a word, since a subscript is otherwise a sum and "red" and "blue" are both the sum 0. The same braces take a value apart, which saves reaching for a tool to do it: "#" cuts a pattern off the front and "%" off the back, doubled to cut the longest match rather than the shortest, so "${p##*/}" is the name at the end of a path and "${p%/*}" is the directory in front of it. "${f/a/b}" swaps the first match for something else and "${f//a/b}" swaps them all, leaving the second half out to delete. "${f:2}" drops the first two characters, "${f:2:3}" takes three from there and "${f: -3}" counts back from the end, with the offsets worked out rather than read. "${f^^}" and "${f,,}" are the same letters in the other case. Type "help ${" for the whole list. "$(command)" is the output of a command used as a value, so "here=$(pwd)" and "uuid -n $count" both work, and "$?" is the status of the last command, 0 when it worked. Backticks are the older spelling of a substitution and work too, so a script pasted in from somewhere older still runs. "$((n + 1))" works out a sum in the same position, in whole numbers, reading a variable with or without its dollar and counting an unset one as nothing, so "total=$((total + n))" works on the first turn of a loop; it only ever works a number out, so there is no "n++" and no assignment inside the brackets. Several variables can be set on one command, as in "a=1 b=2", but one written in front of a command is refused rather than quietly dropped, because nothing here reads an environment. One rule differs from a real shell and is worth knowing: expansion never splits a value into several arguments, so "$name" is one argument however many spaces are in it and "$(cmd)" is one argument holding the output lines joined by spaces. Unquoted splitting is the largest source of surprise in shell scripts and nothing here needs it, since a list of values travels down a pipe instead. Two more: single quotes do not expand, and what an expansion produced is never expanded again, so a value that happens to contain "$(rm -r /home)" is text rather than a command waiting to be named. The one exception is "eval", which reads its arguments as a line and runs them, which is how a variable holding "wc -l" becomes a command rather than the name of one; it is also the only command that will run a string somebody else wrote, so treat what you hand it the way you would treat a script you did not read. Variables live in the tab and are gone when you reload, unlike the files, which are kept.
What happens if a command takes too long?
Press Ctrl+C. Commands run in a Web Worker rather than on the same thread as the page, so a line doing real work never stops the page scrolling or the prompt taking keys, and there is something left to interrupt. It is also what lets a line print while it is still running, so a loop or a script shows its work rather than arriving all at once at the end. Ctrl+C does not interrupt the command the way a shell does, it throws the whole line away: the engine stops outright and the session that comes back is the one the line started with. So a cancelled line wrote no files, set no variables and changed no directory, however far through it got, while everything the lines before it did is untouched. What it had already printed stays on the screen, since the scrollback is a record of what happened rather than part of the session. That is cleaner than a real shell, where a command killed halfway leaves halfway behind. With nothing running, Ctrl+C clears what you have typed instead, which is the other thing it does in a shell. If you have selected text, Ctrl+C is left alone and copies, since that is what it means on Windows and Linux.
Can I loop over a list of files, or run something only sometimes?
Yes. "for f in $(ls); do wc -l $f; done" runs a command once for each thing in a list, "if grep -q invalid ids.txt; then echo found one; fi" runs one only when a question comes back yes, and while and until repeat for as long as a question keeps its answer. break and continue leave a loop early. Two small commands are there because these needed them: test, which can be written as [ ], asks whether a file exists, whether a string is empty or whether one number is larger than another, and seq counts, so "for n in $(seq $first $last)" is how a loop counts through numbers it worked out. When the numbers are ones you typed, write a brace instead: "for i in {1..10}" counts, "{1..10..2}" counts in twos and "{a,b}" chooses between words. A brace works wherever a word can go rather than only in a loop, so "touch file{1..3}.txt" makes three files, and it is expanded before anything is read, so "{1..$n}" is text rather than a range and quoting one keeps the braces. A pattern is the other way one word becomes several: "*.txt" is matched against the files in this tab before any command sees it, with "?" standing for exactly one character, so "cat *.txt" and "for f in *.txt" both work, "[ab]" stands for any one of the characters in it with "[a-z]" as a range and "[!ab]" for everything it does not name, and a pattern that matches nothing is handed on as text. It matches what you typed rather than a value, so a variable holding a star is a star, and only the last part of a path may hold a pattern. For the lines of a file rather than the files themselves, "while read -r line; do echo "[$line]"; done < notes.txt" is the loop that does not split them: read is the only command here that takes its input a line at a time rather than all at once, the redirect on the done opens the file once for the whole loop, and the read that finds nothing left is what ends it. A pipe works the same way, so "ls | while read -r f; do wc -l $f; done" is fine too, and several names split the line with the last one taking whatever is left. A loop is the one place a value is split on its spaces, since a list is exactly what is being written after "in"; quoting keeps a value whole as it does everywhere else. You can type a block across several lines: press Enter with one still open and the prompt becomes a > until you close it, and Ctrl+C abandons the block. Ending a line with a backslash does the same for a command that is simply long, carrying it on down the next line. A loop prints as it goes, a turn at a time, rather than holding everything back until it has finished, so a long one is something you can watch. There are three limits a real shell has no need for, and all three are about the page rather than the work. Ten thousand lines from one typed line, since every one of them becomes part of this page; what that line sends to a file, down a pipe or into a variable is not counted against it and is never cut short, so "x=$(cat big.txt)" keeps the whole file and a loop of thirty thousand turns ending in "done > out.txt" writes all thirty thousand lines. A hundred thousand lines made altogether, which is the one that stops a loop with no way out of filling the tab. And twenty thousand kept on the screen across everything you have run, after which the oldest are forgotten from the top the way a terminal scrollback forgets: whole commands fall off rather than being trimmed inside, and what you are looking at stays. A loop that prints nothing meets none of them and runs until you press Ctrl+C.
Can I write a script and run it?
Yes, with sh. Write the file in your own editor, drop it on the terminal the way you would any other file, and type "sh build.sh". A # starts a comment and a #! line at the top is ignored, so a script written for a real shell reads the same here. Anything you write after the file is the script's: "sh greet.sh world" puts world in $1, with $# counting them, $@ holding all of them and $0 naming the script, and all of those last exactly as long as that script runs. The line most scripts start with works here too: "set -e" stops at the first command that failed while looking past a failure something was asking about, such as the test of an if or everything but the last command of an && list; "set -u" refuses to read a name nobody set, which turns a typo in a variable into an error rather than an empty string, and asks about the one place when there is a subscript, so "${arr[5]}" past the end and "${m[nope]}" for a key nobody set are caught too, with "${name:-}" for the ones that really are optional; "set -x" prints each command as it runs, after expansion, with a + in front; and "set -o pipefail" reports the stage of a pipeline that failed rather than only the last one. A plus turns each of them back off. It runs in the session you are already in rather than in one of its own, which is the opposite of what sh does in a terminal and the more useful of the two here: files it writes and variables it sets are still there when it finishes, and so is anything set turned on. A script can do anything you could have typed, including save and copy, so one you did not write is code you did not read, the same as anywhere else. The one command that behaves differently inside a script is open: going to another page ends the script there, because the terminal it was printing to goes with it.
Can I run Python in it?
Yes, and it is real CPython rather than something that resembles it: the same interpreter, built for WebAssembly, so a traceback is a traceback, re has lookbehind, decimal rounds the way the documentation says and the whole standard library behaves as it does anywhere else. "python -c 'print(sum(range(101)))'" runs a snippet, "python script.py" runs a file, and "cat script.py | python" runs one off a pipe. It sees the same files the shell does: your home directory goes in with the program and whatever it leaves behind comes back, so writing a file in Python and then reading it with cat works without either side being told the other exists. That is the reason to have it here rather than in a tab of its own, since the awkward step in the middle of a piece of shell work can be five lines of Python and then carry on down the pipe. It prints as it goes rather than all at the end, input() reads from the prompt with the marker turning into a ? while a program waits for you, and Ctrl+C stops one that will not end, including one sitting at input(). Typing python on its own opens a prompt at the interpreter instead: the marker becomes >>>, and ... while a block is unfinished, which a blank line closes the way it does at any Python prompt. The value of a bare expression is shown, _ is the last one, Tab completes from the names the session has rather than from commands and files, and an error is printed and survived rather than being the end of anything, Ctrl+C included, so a loop you stop leaves everything you had bound still bound. What you bind there lasts as long as the tab, unlike a file written with > which is kept, and leaving the prompt is not the end of it: exit() or Ctrl+D goes back to the shell, and typing python again finds it all where it was, with the file you just wrote sitting in the directory the shell is standing in. The one thing it does not have is pip, because installing a package means fetching it from somewhere and nothing on this site talks to another host, so the standard library is the whole of what is there and "import requests" will never work any more than a browser can open a socket. The interpreter is a few megabytes and is fetched the first time you type python, which takes a second or so, after which it is cached and starts at once, offline included. All of it works from the Ctrl+K prompt on any page as well as from the Command Line page, Ctrl+C and input() included: those two share memory with the interpreter, which a browser only allows a page that has asked to be isolated from every other site, and every page of this site asks.
Can I change a file, not just read it?
Two ways. sed describes the change: "sed 's/old/new/' notes.txt" replaces text inside every line, a g after it replaces every match on a line rather than the first, and an address in front narrows it to one line, so "sed '2d' notes.txt" drops the second line and "sed '/^#/d' notes.txt" drops the comments. There is no -i flag and none is needed, because a redirect here writes its file after the whole line has finished rather than before it starts: "sed 's/a/b/' f.txt > f.txt" reads the old contents and writes the new ones. That is what makes a change across a directory one line, as in "for f in $(ls); do sed 's/TODO/DONE/g' $f > $f; done". The other way is edit, which opens the file in a window you can type into, with line numbers, and writes it back when you save. It is the one command that waits for you rather than the other way round, and closing the window without saving leaves the file exactly as it was and reports a failure, so "edit build.sh && sh build.sh" does nothing when you change your mind. Use sed when you can describe the change and edit when you want to see the whole file.
Does the edit window colour code?
Yes, for the kinds of file it can recognise: Python, shell, JSON, Markdown, YAML and XML. The name decides, so "edit run.py" and "edit build.sh" open already coloured. A file whose name says nothing is read instead: put "#!/bin/sh" or "#!/usr/bin/env python3" on the first line and the rest of the file follows, as you type it rather than the next time you open it. Everything else stays in one colour, which is what a list of identifiers or a page of notes wants to be. Each parser is fetched the first time a file asks for it and never otherwise, so editing a text file downloads nothing extra and editing a shell script does not drag a Python one along with it.
Can I open my own files in it, or take files out?
Both. Drag a file onto the terminal, or type load to pick one, and it lands in the working directory, so a column of numbers from a spreadsheet can go straight into a checker with "cat suppliers.csv | abn -q". Dragging a folder works too and keeps the folders inside it. Loading reads the file: nothing is uploaded, because there is nowhere to upload it to. Going the other way, "save ids.txt" hands a file to your browser and "save work" hands over a directory as a zip, which you can drop back on the terminal later to get the directory back. Up to 2 MB a file either way. There is a third arrangement that skips the copying: "mount" opens your browser's folder picker, in Chrome and Edge, and the folder appears at ~/mnt read-write, with what you change reaching the real folder as each line finishes and "umount" letting it go. Nothing is ever removed from the real folder that the tab did not carry in first.
Can I get the output onto my clipboard?
Two ways. Ending a line with copy puts the result there instead of on the screen, so "uuid -n 5 | copy" hands you five UUIDs ready to paste and "copy ids.txt" does the same with a file you already have. pbcopy and clip are accepted as the same command, since that is what the habit types. A single value comes across without a trailing line break, which matters when the paste lands in a form field. The other way is the mouse: selecting output in the terminal copies it as it does in iTerm2. What you selected stays marked until the next selection, the cursor goes back to the prompt so you can keep typing, and a small "copied" appears for a moment where you let go. Nothing goes the other way, because a terminal that could read your clipboard could read what your password manager put there.
Where do the files go, and do they stay there?
They are kept in this browser, so a reload finds them where you left them and so does coming back tomorrow. There are three directories: /bin holds the commands themselves and is read-only, so "cat /bin/iban" tells you what iban does, /home starts empty and is yours to write in, and /dev holds one file, null, which takes anything written to it and keeps none of it, so "cmd 2> /dev/null" throws a complaint away the way it does in a real shell. Nothing is uploaded and nothing is written outside the page: what is kept is kept where only this site can reach it, and "save" is the one command that puts a file on your own disk. To be rid of them, "rm" removes one and the Code Editor at /tools/ide has a Forget button that removes all of them at once. Variables and functions are not kept and go when the tab does, which is the same split a real machine makes.
What happens if I have this open in two tabs?
They share the files, the way two terminals on one machine do. Write a file at this prompt and it appears in the other tab straight away, without a reload, and in the Code Editor's file tree too. What each tab keeps to itself is the shell rather than the disk: the working directory, the variables and the Python names are per tab, so a "cd" in one window does not move the other, and if another window removes the directory you are standing in, this one quietly goes home. Saving in both windows at once does not cost you either save. Each keeps the files it changed and the two are put together, a file removed in one window goes in both, and only a file changed in both at the same moment has to be decided, where the window you were looking at wins and the Code Editor says which file it was. Forget everything empties every window rather than only the one it was pressed in.
Does anything I type get sent to a server?
No. Every command runs inside your browser tab, and this site is a set of static files with no server to receive a command in the first place. The single exception is the branch details behind the bsb command: those live in a small data file on this site that is fetched the first time you ask for one. That request carries no part of what you typed, only which block of BSB numbers is needed.
When does a command check a number and when does it invent one?
It depends on whether you gave it something to check. A number after the command is a number to check, so "abn 51824753556" checks it. Nothing after the command means invent one, so "abn" on its own hands you a test ABN the same way "uuid" and "password" hand you a UUID and a password. The -g flag still works and still wins, so an older line like "abn -g -n 20" keeps doing exactly what it did. A pipe counts as having been given something even when it carried nothing, which is the case worth knowing: "cat suppliers.txt | abn" on an empty file says nothing rather than inventing a number, because that question has been answered and making one up would be the command inventing a fact about your file.
Why will bsb not generate a number?
Because it would be generating somebody real. An ABN or a test TFN that passes its checksum is a piece of arithmetic and belongs to nobody, which is why those commands can invent one. Six digits that happen to be an allocated BSB name a real branch of a real bank, and a New Zealand account number that passes the check digit routine names a real account. So this site checks both and invents neither. Typing "help bsb" says the same thing in the terminal.
Does opening this page download every tool on the site?
No, and that is deliberate. The page starts out knowing only the name, flags and help text of each command, which is enough to complete a name or reject a typo the moment you type it. The code that does the actual work is downloaded per command, the first time you run it. Opening the page and running uuid fetches the UUID generator alone, and the other commands stay on the server until you ask for them. It goes one level deeper where a command has a library behind it: hash uses the cryptography already in your browser for SHA-1 and SHA-2 and fetches nothing, while MD5, SHA-3 and RIPEMD-160 pull in an implementation only when you name one, and the cron parser arrives when you type cron and not before.
Can I open the command line from other pages?
Yes. Press Ctrl+K on Windows and Linux, or Cmd+K on a Mac, from any page on this site and the same prompt opens over what you are doing. Escape closes it and returns the cursor to wherever it was, so it does not interrupt a tool you are halfway through. On a Mac the shortcut is deliberately Cmd+K rather than Ctrl+K, because Ctrl+K deletes to the end of the line in Mac text fields and in the editors several of these tools are built on. Once the prompt has the cursor the same chord clears the screen instead of closing the window, which is what it does in a terminal application and what a hand that has typed it for years expects.
What keyboard shortcuts does it support?
Tab completes a command name, filling in as much as is unambiguous and listing the candidates when several match, completes a flag from the command's own list once the word starts with a dash, and completes a file path once you are past the command word. The up and down arrows walk back and forward through the commands you have already run, and Ctrl+R searches them backwards the way it does in a shell: type any part of an earlier command, Ctrl+R again for an older match, Enter to run what it found, Escape to put back what you were typing. The shell and the Python prompt each keep their own history, so Up at one never offers a line written for the other. Ctrl+L clears the screen, as does Cmd+K once the prompt has the cursor, and as does typing clear. Flags accept the usual forms, so "-n 5", "--count 5" and "--count=5" all mean the same thing, and quoting works if you need to pass a value that starts with a dash.