Skip to content

PIPE: Add pipe support for passing data into and out of terminal commands#2525

Open
ficocelliguy wants to merge 104 commits into
bitburner-official:devfrom
ficocelliguy:pipe_support
Open

PIPE: Add pipe support for passing data into and out of terminal commands#2525
ficocelliguy wants to merge 104 commits into
bitburner-official:devfrom
ficocelliguy:pipe_support

Conversation

@ficocelliguy

Copy link
Copy Markdown
Contributor

A number of players have asked for support for pipes in the terminal, or have been confused as to its lack of support:
https://discord.com/channels/415207508303544321/415207839246581781/1411811521935315016
https://discord.com/channels/415207508303544321/415207839246581781/1275557408726323260
https://discord.com/channels/415207508303544321/415207508303544323/932487013922390016
https://discord.com/channels/415207508303544321/415207508303544323/933463698431967283
https://discord.com/channels/415207508303544321/415207508303544323/573291980801703946

So, I've added support for them!

image

Deployed and testable at:
https://ficocelliguy.github.io/bitburner-src-2/

Features (and test cases):

  • echo command added
  • piping to text file support added - will overwrite if > is used, otherwise will append ( >> )
echo test;
echo test data > newFile.txt;
echo overwrite that data > newFile.txt;
  • support for grepping string literals added
  • Support for passing pipe output as additional args for any terminal command added
  • support for multiple pipes in a chain added
echo Hello World > newFile.txt;
ls | grep .txt;
ls | grep .txt > fileList.txt;
  • new ns property stdin added: it is a port handle that receives any data passed to the script via pipes
  • Support for piping to write the contents of script JS files added
  • Support for piping script terminal output to another output location added
echo "export function main(ns) { ns.tprint('Input received: ', ns.stdin.read() ); }" > myScript.js;
echo arg1 arg2 | myScript.js;
echo arg3 arg4 | myScript.js > newLogFile.txt;
  • piping from tail output into additional sources added
run myScript arg1; tail myScript arg1 >> tailLogFile.txt;

ficocelliguy and others added 21 commits February 18, 2026 19:48
… react warning form updating terminal after a command that causes navigation
…cts.md

Co-authored-by: David Walker <d0sboots@gmail.com>
…cts.md

Co-authored-by: David Walker <d0sboots@gmail.com>
…cts.md

Co-authored-by: David Walker <d0sboots@gmail.com>
…d in the terminal (excluding long-running processes)
…ntAndBypassPipes in places where the content isn't intended to be piped

@d0sboots d0sboots left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna have to review the whole thing from the top aren't I 😭

Comment thread src/Documentation/doc/en/programming/terminal_pipes_and_redirects.md Outdated

```
[home /]> echo test123 >> newFile.txt
[home /]> cat < myfile.txt | cat

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use your grep example from above here, to show how < can be used to avoid needing a pipe.
grep "error" < myfile.txt

I get that you want to show the "only first" property, so maybe it can be "in addition to" instead of replacing.


### Semicolons: `;`

Semicolons let you run multiple independent commands on one line. Each command is separate — they do not share pipes or redirects.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add something about sequencing here. Specifically, in Bitburner (unlike in unix), each command runs in parallel IIRC, and this is noticable if you try to do hack;hack.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long-running commands are awaited one at a time when separated by semicolons in dev currently, afaik as a result of the TerminalAction changes you recently worked on
image

Comment thread src/Documentation/doc/en/programming/terminal_pipes_and_redirects.md Outdated

### Piping into scripts

When a script is part of a pipe chain, it can read from stdin using `ns.getStdin()`. The script receives whatever the previous command wrote to stdout.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just say "the terminal" instead of stdout, it's clearer in our case.

@ficocelliguy ficocelliguy Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"read from the terminal" is what current grep -p does, unlike getStdin. stdin isn't the visible terminal broadly, but only data piped in from an output redirect of some sort. I've re-worded this to try to improve the clarity.

Comment thread src/Terminal/commands/common/editor.ts Outdated
export function commonEditor(command: string, { args, server, vim }: EditorParameters, allowZeroFiles = false): void {
if (args.length < 1 && !allowZeroFiles) {
return Terminal.fatal(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
return Terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`, getTerminalStdIO());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your logic for turning these, and things like these, back to error?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fatal explicitly closes the provided stdIO. For places where that didn't seem important, I used error. I can use whichever you think makes sense, though, for things like this.

if (args.length !== 0) return Terminal.fatal("Incorrect usage of hack command. Usage: hack");
if (server.purchasedByPlayer) return Terminal.fatal("Cannot hack your own machines!");
if (!server.hasAdminRights) return Terminal.fatal("You do not have admin rights for this machine!");
if (args.length !== 0) return Terminal.fatal("Incorrect usage of hack command. Usage: hack", stdIO);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the whole point of fatal was that it didn't need this extra argument?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fatal is used to close the StdIO, as opposed to error which just logs red text. There are only a handful of places where there isn't a StdIO already. More importantly, I kept running into places that missed passing the StdIO to fatal after the big merge conflict resolution, so it seemed more future-proof to require it.

Comment thread src/Terminal/getTabCompletionPossibilities.ts Outdated
Comment thread src/Terminal/Terminal.ts Outdated
Comment thread src/NetscriptFunctions.ts
}
const handle = new PortHandle(stdinHandle.n);
// Provide only the methods the player needs to access (or write to) stdio
return {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a new set of closures for every handle. We definitely want to be returning a class instead. If PortHandle isn't that class for some reason, then we should make a new one - but my impression was that PortHandle was that class.

@ficocelliguy ficocelliguy Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PortHandle is a class. I did this to avoid exposing the port number (which are not valid for player use cases.) if a player did something like ns.peek(ns.getStdin().n) it would throw an error about port numbers needing to be positive.

I could make a new class to do something similar, exposing only the relevant methods? or maybe I can just ignore this odd use case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants