* WIP sync close (shows ref count bug in stream)
* fix closing on PipeWriter and PipeReader
* remove old todos
* join
* Some shell changes
at least it compiles
* fix some compile errors
* fix ref/unref server on windows
* actually use the ref count in this places
* make windows compile again
* more tests passing
* Make shell compile again
* Slowly remove some `@panic("TODO SHELL")`
* Eliminate `@panic("TODO SHELL")` for BufferedWriter
* Holy cleansing of `@panic("TODO SHELL")`
at least it compiles now
* Okay now the shell compiles, but segfaults
* Fix compiler errors
* more stable stream and now Content-Range pass
* make windows compile again
* revert stuff until the fix is actually ready
* revert onDone thing
* Fix buffered writer for shell
* Fix buffered writer + shell/subproc.zig and windows build
* Fix for #8982 got lost in the merge
* Actually buffer subproc output
* Fix some stuff shell
* oops
* fix context deinit
* fix renderMissing
* shell: Fix array buffer
* more stable streams (#9053)
fix stream ref counting
* wip
* Remove `@panic("TODO")` on shell event loop tasks and Redirect open flags got lost in merge
* Support redirects
* fixes
cc @cirospaciari
* Update ReadableStreamInternals.ts
* Fix spurious error
* Update stream.js
* leak
* Fix UAF
cc @cirospaciari
* Fix memory leaks
* HOLY FUCK big refactor
* misc cleanup
* shell: Fix a bunch of tests
* clean up
* gitignore: fix ending newline
* get windows compiling again
* tidy
* hide linker warn with icu
* closeIfPossible
* Better leak test
* Fix forgetting to decrement reference count
* Update stdio.zig
* Fix shell windows build
* Stupid unreachable
* Woops
* basic echo hi works on windows
* Fix flaky test on Windows
* Fix windows regression in Bun.main (#9156)
* Fix windows regression in Bun.main
* Handle invalid handles
* Fix flaky test
* Better launch config
* Fixup
* Make this test less flaky on Windows
* Fixup
* Cygwin
* Support signal codes in subprocess.kill(), resolve file path
* Treat null as ignore
* Ignore carriage returns
* Fixup
* shell: Fix IOWriter bug
* shell: Use custom `open()`/`openat()`
* windows shell subproc works
* zack commit
* I think I understand WindowsStreamingWriter
* fix thing
* why were we doing this in tests
* shell: Fix rm
* shell: Add rm -rf node_modules/ test
* shell: use `.runAsTest()` in some places to make it easier to determine which test failed
* [autofix.ci] apply automated fixes
* woopsie
* Various changes
* Fix
* shell: abstract output task logic
* shell: mkdir builtin
* fixup
* stuff
* shell: Make writing length of 0 in IOWriter immediately resolve
* shell: Implement `touch`
* shell: basic `cat` working
* Make it compile on windows
* shell: Fix IOReader bug
* [autofix.ci] apply automated fixes
* fix windows kill on subprocess/process
* fix dns tests to match behavior on windows (same as nodejs)
* fix windows ci
* again
* move `close_handle` to flags in `PipeWriter` and fix shell hanging
* Fix `ls` not giving non-zero exit code on error
* Handle edgecase in is_atty
* Fix writer.flush() when there's no data
* Fix some tests
* Disable uv_unref on uv_process_t on Windows, for now.
* fix writer.end
* fix stdout.write
* fix child-process on win32
* Make this test less flaky on Windows
* Add assertion
* Make these the same
* Make it pass on windows
* Don't commit
* Log the test name
* Make this test less flaky on windows
* Make this test less flaky on windows
* Print which test is taking awhile in the runner
* fixups
* Fixups
* Add some assertions
* Bring back test concurrency
* shell: bring back redirect stdin
* make it compile again cc @zackradisic
* initialize env map with capacity
* some fixes
* cleanup
* oops
* fix leak, fix done
* fix unconsumedPromises on events
* always run expect
* Update child_process.test.ts
* fix reading special files
* Fix a test
* Deflake this test
* Make these comparisons easier
* Won't really fix it but slightly cleaner
* Update serve.test.ts
* Make the checks for if the body is already used more resilient
* Move this to the harness
* Make this test not hang in development
* Fix this test
* Make the logs better
* zero init some things
* Make this test better
* Fix readSocket
* Parallelize this test
* Handle EPipe and avoid big data
* This was a mistake
* Fix a bunch of things
* Fix memory leak
* Avoid sigpipe + optimize + delete dead code
* Make this take less time
* Make it bigger
* Remove some redundant code
* Update process.zig
* Merge and hopefully don't breka things along teh way
* Silence build warning
* Uncomment on posix
* Skip test on windows
* windows
* Cleanup test
* Update
* Deflake
* always
* less flaky test
* [autofix.ci] apply automated fixes
* logs
* fix uaf on shell IOReader
* stuff to make it work with mini event loop
* fix 2 double free scenarios, support redirections on windows
* shell: Make `1>&2` and `2>&1` work with libuv
* yoops
* Partial fix
* Partial fix
* fix build
* fix build
* ok
* Make a couple shell tests pass
* More logging
* fix
* fix
* Fix build issue
* more tests pass
* Deflake
* Deflake
* Use Output.panic instead of garbled text
* Formatting
* Introduce `bun.sys.File`, use it for `Output.Source.StreamType`, fix nested Output.scoped() calls, use Win32 `ReadFile` API for reading when it's not a libuv file descriptor.
This lets us avoid the subtle usages of `unreachable` in std.os when writing to stdout/stderr.
Previously, we were initializing the libuv loop immediately at launch due to checking for the existence of a bun build --compile'd executable. When the file descriptor is not from libuv, it's just overhead to use libuv
cc @paperdave, please tell me if Iany of that is incorrect or if you think this is a bad idea.
* Fix closing undefined memory file descriptors in spawn
cc @zackradisic
* pause instead of close
* Fix poorly-written test
* We don't need big numbers for this test
* sad workaround
* fixup
* Clearer error handling for this test
* Fix incorrect test
@electroid when ReadableStream isn't closed, hanging is the correct behavior when consuming buffered data. We cannot know if the buffered data is finished if the stream never closes.
* Fix build
* Remove known failing on windows
* Deflake
* Mark no longer failing
* show all the failing tests
* Sort the list of tests
* fix argument handling
* dont show "posix_spawn" as an error code on windows
* make bun-upgrade.test.ts pass on windows
* fix bunx and bun create again sorry
* a
* fix invalidexe because we should not be running javascript files as if they were exes
* Concurrency in test runner + better logging
* Revert "fix invalidexe because we should not be running javascript files as if they were exes"
This reverts commit da47cf8247.
* WIP: Unix fixes (#9322)
* wip
* [autofix.ci] apply automated fixes
* wip 2
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
* Update runner.node.mjs
* Update runner.node.mjs
* Document some environment variables
* shell: Make `Response` work with builtins
* Make it compile
* make pwd test pass
* [autofix.ci] apply automated fixes
* Fix printing garbage for source code previews
* Update javascript.zig
* Fix posix test failures
* Fix signal dispatch
cc @paperdave. Signals can be run from any thread. This causes an assertion failure when the receiving thread happens to not be the main thread. Easiest to reproduce on linux when you spawn 100 short-lived processes at once.
* windows
---------
Co-authored-by: cirospaciari <ciro.spaciari@gmail.com>
Co-authored-by: Zack Radisic <56137411+zackradisic@users.noreply.github.com>
Co-authored-by: Zack Radisic <zackradisic@Zacks-MBP-2.attlocal.net>
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Meghan Denny <meghan@bun.sh>
Co-authored-by: Zack Radisic <zack@theradisic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: dave caruso <me@paperdave.net>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
10 KiB
Bun Shell makes shell scripting with JavaScript & TypeScript fun. It's a cross-platform bash-like shell with seamless JavaScript interop.
{% callout type="note" %} Alpha-quality software: Bun Shell is an unstable API still under development. If you have feature requests or run into bugs, please open an issue. There may be breaking changes in the future. {% /callout %}
Quickstart:
import { $ } from "bun";
const response = await fetch("https://example.com");
// Use Response as stdin.
await $`echo < ${response} > wc -c`; // 120
Features:
- Cross-platform: works on Windows, Linux & macOS. Instead of
rimraforcross-env', you can use Bun Shell without installing extra dependencies. Common shell commands likels,cd,rmare implemented natively. - Familiar: Bun Shell is a bash-like shell, supporting redirection, pipes, environment variables and more.
- Globs: Glob patterns are supported natively, including
**,*,{expansion}, and more. - Template literals: Template literals are used to execute shell commands. This allows for easy interpolation of variables and expressions.
- Safety: Bun Shell escapes all strings by default, preventing shell injection attacks.
- JavaScript interop: Use
Response,ArrayBuffer,Blob,Bun.file(path)and other JavaScript objects as stdin, stdout, and stderr.
Getting started
The simplest shell command is echo. To run it, use the $ template literal tag:
import { $ } from "bun";
await $`echo "Hello World!"`; // Hello World!
By default, shell commands print to stdout. To quiet the output, call .quiet():
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // No output
What if you want to access the output of the command as text? Use .text():
import { $ } from "bun";
// .text() automatically calls .quiet() for you
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\n
To get stdout, stderr, and the exit code, use await or .run:
import { $ } from "bun";
const { stdout, stderr, exitCode } = await $`echo "Hello World!"`.quiet();
console.log(stdout); // Buffer(6) [ 72, 101, 108, 108, 111, 32 ]
console.log(stderr); // Buffer(0) []
console.log(exitCode); // 0
Redirection
A command's input or output may be redirected using the typical Bash operators:
<redirect stdin>or1>redirect stdout2>redirect stderr&>redirect both stdout and stderr>>or1>>redirect stdout, appending to the destination, instead of overwriting2>>redirect stderr, appending to the destination, instead of overwriting&>>redirect both stdout and stderr, appending to the destination, instead of overwriting1>&2redirect stdout to stderr (all writes to stdout will instead be in stderr)2>&1redirect stderr to stdout (all writes to stderr will instead be in stdout)
Bun Shell also supports redirecting from and to JavaScript objects.
Example: Redirect output to JavaScript objects (>)
To redirect stdout to a JavaScript object, use the > operator:
import { $ } from "bun";
const buffer = Buffer.alloc(100);
const result = await $`echo "Hello World!" > ${buffer}`;
console.log(result.exitCode); // 0
console.log(buffer.toString()); // Hello World!\n
The following JavaScript objects are supported for redirection to:
Buffer,Uint8Array,Uint16Array,Uint32Array,Int8Array,Int16Array,Int32Array,Float32Array,Float64Array,ArrayBuffer,SharedArrayBuffer(writes to the underlying buffer)Bun.file(path),Bun.file(fd)(writes to the file)
Example: Redirect input from JavaScript objects (<)
To redirect the output from JavaScript objects to stdin, use the < operator:
import { $, file } from "bun";
const response = new Response("hello i am a response body");
const result = await $`cat < ${response}`.text();
console.log(result); // hello i am a response body
The following JavaScript objects are supported for redirection from:
Buffer,Uint8Array,Uint16Array,Uint32Array,Int8Array,Int16Array,Int32Array,Float32Array,Float64Array,ArrayBuffer,SharedArrayBuffer(reads from the underlying buffer)Bun.file(path),Bun.file(fd)(reads from the file)Response(reads from the body)
Example: Redirect stdin -> file
import { $ } from "bun"
await $`cat < myfile.txt`
Example: Redirect stdout -> file
import { $ } from "bun"
await $`echo bun! > greeting.txt`
Example: Redirect stderr -> file
import { $ } from "bun"
await $`bun run index.ts 2> errors.txt`
Example: Redirect stdout -> stderr
import { $ } from "bun"
// redirects stderr to stdout, so all output
// will be available on stdout
await $`bun run ./index.ts 2>&1`
Example: Redirect stderr -> stdout
import { $ } from "bun"
// redirects stdout to stderr, so all output
// will be available on stderr
await $`bun run ./index.ts 1>&2`
Piping (|)
Like in bash, you can pipe the output of one command to another:
import { $ } from "bun";
const result = await $`echo "Hello World!" | wc -w`.text();
console.log(result); // 2\n
You can also pipe with JavaScript objects:
import { $ } from "bun";
const response = new Response("hello i am a response body");
const result = await $`cat < ${response} | wc -w`.text();
console.log(result); // 6\n
Environment variables
Environment variables can be set like in bash:
import { $ } from "bun";
await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\n
You can use string interpolation to set environment variables:
import { $ } from "bun";
const foo = "bar123";
await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\n
Input is escaped by default, preventing shell injection attacks:
import { $ } from "bun";
const foo = "bar123; rm -rf /tmp";
await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\n
Changing the environment variables
By default, process.env is used as the environment variables for all commands.
You can change the environment variables for a single command by calling .env():
import { $ } from "bun";
await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // bar
You can change the default environment variables for all commands by calling $.env:
import { $ } from "bun";
$.env({ FOO: "bar" });
// the globally-set $FOO
await $`echo $FOO`; // bar
// the locally-set $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz
You can reset the environment variables to the default by calling $.env() with no arguments:
import { $ } from "bun";
$.env({ FOO: "bar" });
// the globally-set $FOO
await $`echo $FOO`; // bar
// the locally-set $FOO
await $`echo $FOO`.env(undefined); // ""
Changing the working directory
You can change the working directory of a command by passing a string to .cwd():
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmp
You can change the default working directory for all commands by calling $.cwd:
import { $ } from "bun";
$.cwd("/tmp");
// the globally-set working directory
await $`pwd`; // /tmp
// the locally-set working directory
await $`pwd`.cwd("/"); // /
Reading output
To read the output of a command as a string, use .text():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.text();
console.log(result); // Hello World!\n
Reading output as JSON
To read the output of a command as JSON, use .json():
import { $ } from "bun";
const result = await $`echo '{"foo": "bar"}'`.json();
console.log(result); // { foo: "bar" }
Reading output line-by-line
To read the output of a command line-by-line, use .lines():
import { $ } from "bun";
for await (let line of $`echo "Hello World!"`.lines()) {
console.log(line); // Hello World!
}
You can also use .lines() on a completed command:
import { $ } from "bun";
const search = "bun";
for await (let line of $`cat list.txt | grep ${search}`.lines()) {
console.log(line);
}
Reading output as a Blob
To read the output of a command as a Blob, use .blob():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.blob();
console.log(result); // Blob(13) { size: 13, type: "text/plain" }
Builtin Commands
For cross-platform compatibility, Bun Shell implements a set of builtin commands, in addition to reading commands from the PATH environment variable.
cd: change the working directoryls: list files in a directoryrm: remove files and directoriesecho: print textpwd: print the working directorybun: run bun in bun
Partially implemented:
mv: move files and directories (missing cross-device support)
Not implemented yet, but planned:
mkdir: create directoriescp: copy files and directoriescat: concatenate files
Utilities
Bun Shell also implements a set of utilities for working with shells.
$.braces (brace expansion)
This function implements simple brace expansion for shell commands:
import { $ } from "bun";
await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]
$.escape (escape strings)
Exposes Bun Shell's escaping logic as a function:
import { $ } from "bun";
console.log($.escape('$(foo) `bar` "baz"'))
// => \$(foo) \`bar\` \"baz\"
If you do not want your string to be escaped, wrap it in a { raw: 'str' } object:
import { $ } from "bun";
await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`
// => bun: command not found: foo
// => bun: command not found: bar
// => baz
.sh file loader
For simple shell scripts, instead of /bin/sh, you can use Bun Shell to run shell scripts.
To do so, just run the script with bun on a file with the .sh extension.
echo "Hello World! pwd=$(pwd)"
$ bun ./script.sh
Hello World! pwd=/home/demo
Scripts with Bun Shell are cross platform, which means they work on Windows:
PS C:\Users\Demo> bun .\script.sh
Hello World! pwd=C:\Users\Demo
Credits
Large parts of this API were inspired by zx, dax, and bnx. Thank you to the authors of those projects.