4

How might I set up node.js as a shell replacement for bash? For example I should be able to run vi('file') to open a file and cd('location') to change between directories.

Is this even possible?

6
  • 5
    Since node doesn't implement anything a POSIX shell expects, this would almost certainly fail and possibly do horrible things to your user. Writing a compliant shell in node is possible, but too broad of a question for SO. Commented Nov 23, 2015 at 16:10
  • do u want to execute exe from node ? or something like shell commands on top of the Node.js API shelljs? Commented Nov 24, 2015 at 8:42
  • @aishwatsingh Shelljs isn't bad but really is just built on top of the nodejs api. You can't use it as a real shell replacement to my knowledge. Commented Nov 25, 2015 at 6:57
  • Do you mean that you want to be able to log in and have your shell be the Node REPL, and yet still be able to reasonably use your computer? Commented Nov 30, 2015 at 18:12
  • 1
    In an attempt to keep this question semi up-to-date, a small Node POSIX project is available. [1] 3 years since question was asked. As noted, a real bash replacement will require Node POSIX support. Other steps still needed. 1. npmjs.com/package/posix Commented Sep 30, 2018 at 21:32

6 Answers 6

12
+25

Sure you can! It will become much less straightforward to use your computer, though.

First off, you will need to know how to set this up. While you could likely set your user shell in Linux to usr/bin/node, this will leave you with only a Node.js REPL with no additional programs set up. What you're going to want to do is write a setup script that can do all of the below setup/convenience steps for you, essentially something that ends with repl.start() to produce a REPL after setting everything up. Of course, since UNIX shell settings can't specify arguments, you will need to write a small C program that executes your shell with those arguments (essentially, exec("/usr/bin/node", "path/to/setup/script.js");) and set that as your UNIX shell.

The main idea here is that any commands that you use beyond the very basics must be require()d into your shell - e.g. to do anything with your filesystem, execute

var fs = require("fs")

and do all of your filesystem calls from the fs object. This is analogous to adding things to your PATH. You can get basic shell commands by using shelljs or similar, and to get at actual executable programs, use Node's built-in child_process.spawnSync for a foreground task or child_process.spawn for a background task.

Since part of your requirement is that you want to call each of your programs like a function, you will need to produce these functions yourself, getting something like:

function ls(path) {
    child_process.spawnSync('/bin/ls', [path], { stdio: 'inherit' });
}

for everything you want to run. You can probably do this programmatically by iterating through all the entries in your PATH and use closures to generate execute functions for each, assigning them to the global object so that you don't have to enter any prefixes.

Again, it will become much less straightforward to use your computer, despite having these named functions. You will need to use sh/bash/zsh explicitly in order to run shell commands you find on the Internet, and programs that specifically call up your user shell and expect it to be Bash-compatible (or if you're on a system where /bin/sh links to your user shell somehow) will no longer work. But I can certainly see the appeal of being able to leverage JavaScript in your command-line environment.


ADDENDUM: For writing this setup script, the REPLServer object returned by repl.start() has a context object that is the same as the global object accessible to the REPL session it creates. When you write your setup script, you will need to assign everything to the context object:

const context = repl.start('> ').context;
context.ls = function ls(path) { /* . . . */ }
context.cd = function cd(path) { /* . . . */ }
Sign up to request clarification or add additional context in comments.

4 Comments

For example I should be able to run vi('file') to open a file and cd('location') to change between directories. Which I believe yours don't work like such.
And would it need to work automatically, right off the bat, for everything that is in your path?
it would be nice to create a superset Js language that allowed functions like cd arg1 arg2 and ls as a function... I just realised I am describing CoffeeScript
@RayFoss thanks for the reference. Coffeescript actually looks awesome.
1

I think it would be an intersting proposition. Create a test account and tell it to use node as it's shell. See 'man useradd' for all options

$ useradd -s /usr/bin/node test
$ su - test

1 Comment

This isn't really an answer.
0

This works on mac and linux.

require('child_process').spawnSync('vi', ['file.txt'], { stdio: 'inherit' })

Comments

0

Created a NodeJS Shell that can be used as default shell.

Install (need NodeJS and NPM):

npm install -g biensure-nsh

Usage (to try):

nsh

If you like it, you can edit /etc/passwd to change your /usr/bin/bash to (in my case) /home/administrator/.npm-global/bin/nsh

For more information: https://github.com/biensurerodezee/nsh

Comments

-1

You could bootstrap a repl session with your own commands, then run the script

#!/bin/bash
node  --experimental-repl-await -i -e "$(< scripts/noderc.js)"`

This allows for things like:

> ls()
> run('vi','file.txt')
> await myAsyncFunc()

Comments

-1

I think you're looking for something like this https://youtu.be/rcwcigtOwQ0 !

If so.... YES you can!

If you like I can share my code. But I need to fix some bugs first!

tell me if you like.

my .sh function:

const hey = Object.create(null),
    sh = Object.create(null);;
  
hey.shell = Object.create(null);
hey.shell.run = require('child_process').exec;

sh.help = 'Execute an OS command';
sh.action = (...args) => {
    // repl_ is the replServer
    // the runningExternalProgram property is one way to know if I should 
    //       render the prompt and is not needed. I will create a better
    //        way to do this (action without if/decision)!
    repl_.runningExternalProgram = true;
    hey.shell.run(args.join(' '),
            (...args) => {
                ['error', 'log'].forEach((command, idx) => {
                    if (args[idx]) {
                        console[command](args[idx]);
                    }
                });
                repl_.runningExternalProgram = false;
            });
};

PS: to 'cd' into some directory you just need to change the process.cwd (current working directory)

PS2: to avoid need to write .sh for every OS program/command you can use Proxy on the global object.

1 Comment

Is there an easy way to remap characters and control characters?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.