5
# index.js
console.log(process.argv) // expect this to print [.., .., '1']

# terminal
$ echo 1 | node index.js // just prints [.., ..]

What's the trick? How do I dynamically pass arguments to a node script from the command line via unix commands like echo, ls, ps aux, and so on?

Note: I see that I can read the output of unix commands from within my script using stdin, but what I'd like is to truly pass arguments to the script from the command line.

Joseph Fraley
  • 1,360
  • 1
  • 10
  • 26
  • No need to pipe, also [duplicate](http://stackoverflow.com/questions/4351521/how-do-i-pass-command-line-arguments) – dramzy Dec 31 '16 at 03:24
  • Pipes determine what's on stdin, not what's in your argument list.That's not just for `node`, that's how UNIX pipelines work *everywhere*. – Charles Duffy Dec 31 '16 at 03:26
  • 1
    ...which is to say: Is your real question (1) how to read piped content, or (2) how to read command-line arguments? – Charles Duffy Dec 31 '16 at 03:28
  • The current answer corresponds to question 1, my link to question 2, – dramzy Dec 31 '16 at 03:29
  • @Charles Duffy my real question is: how do i dynamically pass arguments to a node script from the command line via unix commands like echo, ls, ps aux, and so on. – Joseph Fraley Dec 31 '16 at 03:30
  • 1
    piping data input is not the same as passing arguments, please rephrase your question to not say "pass arguments", unless you want to call your script as `node index.js 1` – zzzzBov Dec 31 '16 at 03:37
  • Thanks @zzzzBov, I did update my question as such. – Joseph Fraley Dec 31 '16 at 03:39
  • @JosephFraley, if you did, then why do I still see ["pass arguments"](https://www.youtube.com/watch?v=G2y8Sx4B2Sk) in the question twice along with the title? – zzzzBov Dec 31 '16 at 03:40
  • 2
    There's significant impedance mismatch between what's easily represented in an input stream (a la stdin) and an argv array. An argv entry can contain literally any character other than NUL, but easily generating NUL-delimited streams on stdin takes some work. A stdin stream can be of completely arbitrary length, whereas an argv list is limited to what'll fit inside `ARG_MAX` -- excluding the amount of space used by exported environment variables. – Charles Duffy Dec 31 '16 at 03:42
  • 2
    And then there are all the questions of exactly *how* the former gets converted to the latter. If you want `echo '"foo bar" baz' | ...` to treat `foo bar` as one word and `baz` as another, for instance, you just asked for a whole bunch of extra work to implement a POSIX-compliant parser. – Charles Duffy Dec 31 '16 at 03:43
  • @zzzzBov thanks, I misread your desired edit. I feel the question as written reflects my actual intent. I expect this use of unix pipes is a natural thing to try, so the fact that my question fundamentally misunderstands the issue is still potentially valuable to other users. – Joseph Fraley Dec 31 '16 at 03:44
  • @CharlesDuffy thanks, that makes sense. I guess it depends what your expectations are. I don't have a naïve intuition about how I'd expect your example to work in my imagined scenario. – Joseph Fraley Dec 31 '16 at 03:48
  • 1
    ...incidentally, there are hacks with xargs that will *sorta* do what you're asking for, but badly -- splitting an input stream too long to fit onto an argument list into multiple invocations (so `echo one two three four five | xargs node yourscript` could run `node yourscript one two three` and `node yourscript four five` as two separate commands). – Charles Duffy Dec 31 '16 at 03:48

3 Answers3

6
$ echo 1 | node index.js

In this command echo prints 1 to the standard output which is redirected (via pipe) to the standard input of the node command that accepts index.js argument. If you want to read the string printed by echo, read the standard input, e.g.:

var text = '';

process.stdin.setEncoding('utf8');
process.stdin.on('readable', function () {
  var chunk = process.stdin.read();
  if (chunk !== null) {
    text += chunk;
  }
});
process.stdin.on('end', function () {
  console.log(text);
});

How do I dynamically pass arguments to a node script from the command line via unix commands like echo, ls, ps aux, and so on.?

With a pipe you can only redirect the bulk output from a command. You may use command substitution to pass the outputs of multiple commands as strings, e.g.:

node index.js --arg1="$(ls -ld /tmp)" --arg2="$(stat -c%a /tmp)"

Assign the output of the commands to shell variables in order to make your script more readable:

arg1="$(ls -ld /tmp)"
node index.js --arg1="$arg1"
Ruslan Osmanov
  • 20,486
  • 7
  • 46
  • 60
  • this is useful, but doesn't really achieve what I want. I actually want to dynamically generate arguments to the script – Joseph Fraley Dec 31 '16 at 03:34
  • @JosephFraley, then you've misunderstood how `|` works. `|` does not, *will not*, and **should not** create dynamic values accessible via `process.argv`. – zzzzBov Dec 31 '16 at 03:39
  • @JosephFraley, ...why do you want to use a pipeline for the purpose? The better-practice way to collect arguments is into a native-shell array. – Charles Duffy Dec 31 '16 at 03:39
  • @zzzzBov what's the danger? can you point to any resources about why this use of pipes is problematic? @CharlesDuffy I want to use pipes because it's convenient. An example use case is I have a bunch of queries written in separate files in some dir. I want to pass the names of these files to a node script that will read the files and use them to query a db. but i also want to be able to manually pass file names or hand-written query strings as arguments. `ls queries | node index.js` would be handy – Joseph Fraley Dec 31 '16 at 03:42
  • 1
    @JosephFraley, `ls queries | node index.js` is really, *really* problematic. See [ParsingLs](http://mywiki.wooledge.org/ParsingLs) as a starting point -- `node index.js queries/*` will deal with a file created with `touch $'queries/hello\ncruel\nworld'` gracefully, recognizing it as just one argument and passing its name in a way that `node` can open. The output from `ls` for that case, by contrast, will be far less useful. – Charles Duffy Dec 31 '16 at 03:46
  • 1
    @JosephFraley, "what's the danger?" it's not about danger, it's about how that particular input is read in node. That said, what do you expect to happen if you pipe data while passing arguments? What happens when you write something like `echo 1 | node index.js 2`? How do you resolve that discrepancy? – zzzzBov Dec 31 '16 at 03:46
  • @zzzzBov naïvely I'd expect `node index.js 2 1`, but I see your point. I appreciate how it's a weird thing to try when one properly understands what pipe is doing. but I don't think it's insane. – Joseph Fraley Dec 31 '16 at 03:49
1

A friend of mine showed me this:

$ node index.js `echo 1 2 3 4`

Actually does exactly what I want. This would result in:

// index.js
process.argv // [.., .., '1', '2', '3', '4']

The difference between this and @RuslanOsmanov answer is that the above will pass in the output as all the arguments to the node process, whereas:

$ node --arg1=`echo 1` --arg2=`echo 2`

Requires an individual command for each individual argument.

It would not work as expected with ls if your filenames contain spaces, as space characters are treated as argument delimiters.

See What does the backtick - ` - do in a command line invocation specifically with regards to Git commands? for more about this use of back ticks.

Joseph Fraley
  • 1,360
  • 1
  • 10
  • 26
0

You’ve been using the process.stdout writable stream implicitly every time you’ve called console.log(). Internally, the console.log() function calls process.stdout.write() after formatting the input arguments. But the console functions are more for debugging and inspecting objects. When you need to write structured data to stdout, you can call process.stdout.write() directly.

Say your program connects to an HTTP URL and writes the response to stdout. Stream#pipe() works well in this context, as shown here:

var http = require('http');
var url = require('url');
var target = url.parse(process.argv[2]);
var req = http.get(target, function (res) {
res.pipe(process.stdout);
});

Before you can read from stdin, you must call process.stdin.resume() to indicate that your script is interested in data from stdin. After that, stdin acts like any other readable stream, emitting data events as data is received from the output of another process, or as the user enters keystrokes into the terminal window.

The following listing shows a command-line program that prompts the user for their age before deciding whether to continue executing.

To improve @Joseph's answer, you can use both command in between back ticks() and $(enter code here)` :

$ node --arg1=`echo 1` --arg2=$(echo 2)

enter image description here

uzay95
  • 16,052
  • 31
  • 116
  • 182