How to use Unicode literals with the Node.js -e "evaluate script" commandline switch - node.js

Node.js has an -e commandline switch to evaluate code provided on the commandline rather than in a separate script file.
Oddly, I can't find official documentation for it online but the node executable self-documents it if you run node --help:
>node --help
Usage: node [options] [ -e script | script.js ] [arguments]
node debug script.js [arguments]
Options:
-v, --version print Node.js version
-e, --eval script evaluate script
-p, --print evaluate script and print result
Now there are several ways to use Unicode literals in JavaScript, all seemingly begin with \u.
But no matter how I try to quote or escape the string with the Unicode literal, the code always fails to execute. Both under official Node.js and also JXcore.
Node
>node -e console.log('hello \u00A9')
[eval]:1
console.log('hello
^^^^^^
SyntaxError: Unexpected token ILLEGAL
at Object.exports.runInThisContext (vm.js:53:16)
at Object.<anonymous> ([eval]-wrapper:6:22)
at Module._compile (module.js:435:26)
at node.js:578:27
at doNTCallback0 (node.js:419:9)
at process._tickCallback (node.js:348:13)
JXcore
>jx -e console.log('hello \u{00A9}')
SyntaxError: unterminated string literal ([eval] 1:12)
at ([eval]-wrapper:6:8)
at Module.prototype._compile (module.js:621:10)
at evalScript (node.js:1054:18)
at startup (node.js:419:7)
at node.js:1604:3
I've tried double \\ and quadruple \\\\. I've tried single and double quote characters to delimit the strings.
(I am only trying this under Windows, just in case this might work fine under *nix.)

Try
node -e "console.log('hello \u00A9')"
Double-quoting ensures that console.log('hello \u00A9') is interpreted as a single argument by node.
In your original attempt, node saw 2 arguments following -e (split by the space inside 'hello \u00A9'): console.log('hello and \u00A9'), and since the first argument following -e was not by itself a syntactically valid expression, you got a syntax error.
Background:
The scriptargument (-e's option-argument) containing the snippet of JavaScript code to execute must be a distinct argument on the command line, so that it can be distinguished from other arguments, such as the arguments to pass to it ([arguments]).
The way to make a string with embedded whitespace distinct as an argument is to quote it as a whole.
On Windows, this is typically done with double quotes, but it is ultimately up to the program invoked to interpret the arguments (after the shell may have expanded tokens, such as environment-variable references).
Since single- and double-quoting can be used interchangeably in JavaScript, you may even get away with swapping single- and double quotes: node -e 'console.log("hello \u00A9")'
(That definitely works on Unix, where the shell itself recognizes a single-quoted string as a distinct argument, and all programs are handed an array of parsed arguments rather than a command line to interpret themselves).

Related

Argument escaping not interpreted correctly when running node.js script from Windows PowerShell

Given the following script:
const yargs = require('yargs');
const argv =
yargs
.usage('Usage: $0 [--whatIf]')
.alias('d', 'directory')
.alias('wi', 'whatIf')
.nargs('d', 1)
.describe('d', 'alphabetize this directory')
.describe('whatIf', 'show what would happen if run')
.demandOption(['d'])
.argv;
console.log(argv.directory);
If I invoke the script from Windows PowerShell like so: node .\alphabetizer.js -d 'l:\my folder\Files - Some Files In Here\' --whatIf I get the output l:\my folder\Files - Some Files In Here\" --whatIf where I would expect just l:\my folder\Files - Some Files In Here\. It works OK with folder names that require no escaping, but it seems to get confused by the escaping.
If I examine process.argv, I can see the same escaping issue.
I have noticed that if I remove the trailing slash it will work. However, this still points to the node script not handling the input properly, because this should not be necessary with string set off by single quotes.
Is there a way to make this work?
Both Windows PowerShell (powershell.exe) and PowerShell [Core] v6+ (pwsh) are fundamentally broken with respect to quoting arguments for external programs properly - see this answer for background info.
Generally, PowerShell on Windows has to perform re-quoting behind the scenes in order to ensure that just "..."-quoting is used, given that external programs can't be assumed to understand '...'-quoting too when parsing their command line (which on Windows every program has to do itself).
Windows PowerShell is more broken with respect to arguments that end in \ and have embedded spaces, re-quoting them improperly; e.g.:
PS> foo.exe 'c:\foo \' bar
is translated into the following command line behind the scenes:
foo.exe "c:\ foo \" bar
This is broken, in that most applications - including PowerShell's own CLI - sensibly assume that the \" is an escaped " char. to be taken verbatim, thinking that the argument continues with  bar and then implicitly ends, despite the formal lack of a closing ".
PowerShell [Core] v6+ more sensibly translates the above to foo.exe "c:\foo \\" bar, where the \\ is interpreted as an escaped \, and the following " again has syntactic function.
If you're stuck with Windows PowerShell, your only choices are:
either: if possible, leave off the trailing \
otherwise: manually double it (\\), but only do so if the argument also contains spaces (otherwise, the \\ will be retained as-is, though in the case of filesystem paths that is usually benign).

converting a string passed from unix shell to a node program to a javascript-formatted one?

I'm using a bash script to send an argument to a node app like so:
testString="\nhello\nthere"
node ./myNodeScript.js $testString
The trouble comes when I use testString inside the node program after capturing it as process.argv[2] -- rather than expand the \n characters to newlines node prints them literally. I need a way to tell node to convert the argument to a javascript string, respecting the formatting characters. Is there a way to go about this?
Try to avoid confusing literal linefeeds and literal backslash followed by literal n.
If you want the string you pass to have linefeeds, you should ignore JavaScript string literal syntax and just pass the linefeeds as linefeeds:
$ cat myNodeScript.js
console.log("Node was passed this: " + process.argv[2])
$ cat myBashScript
testString='
hello
there'
printf 'Bash passes this: %s\n' "$testString"
node myNodeScript.js "$testString"
$ bash myBashScript
Bash passes this:
hello
there
Node was passed this:
hello
there
Arguments should contain data (linefeed) while script files should contain code (quoted linefeed or expanded \n as appropriate in the language). When you make sure not to confuse code and data, you can trivially handle both backslash-en and linefeeds in the same string with no surprises:
testString='
"\nhello\nthere" is JavaScript syntax for:
hello
there'
There are ways to express this on a single line in bash using \n for linefeeds and \\n for backslash-en, you just need to make sure that it remains as code, and doesn't accidentally make it into the variable as data.
Can you try this:
testString=$( printf "\nhello\nthere")
node ./myNodeScript.js "$testString"
And let me know if it works?

pass string with spaces to gcc [duplicate]

This question already has answers here:
How can I store a command in a variable in a shell script?
(12 answers)
Closed 4 years ago.
These work as advertised:
grep -ir 'hello world' .
grep -ir hello\ world .
These don't:
argumentString1="-ir 'hello world'"
argumentString2="-ir hello\\ world"
grep $argumentString1 .
grep $argumentString2 .
Despite 'hello world' being enclosed by quotes in the second example, grep interprets 'hello (and hello\) as one argument and world' (and world) as another, which means that, in this case, 'hello will be the search pattern and world' will be the search path.
Again, this only happens when the arguments are expanded from the argumentString variables. grep properly interprets 'hello world' (and hello\ world) as a single argument in the first example.
Can anyone explain why this is? Is there a proper way to expand a string variable that will preserve the syntax of each character such that it is correctly interpreted by shell commands?
Why
When the string is expanded, it is split into words, but it is not re-evaluated to find special characters such as quotes or dollar signs or ... This is the way the shell has 'always' behaved, since the Bourne shell back in 1978 or thereabouts.
Fix
In bash, use an array to hold the arguments:
argumentArray=(-ir 'hello world')
grep "${argumentArray[#]}" .
Or, if brave/foolhardy, use eval:
argumentString="-ir 'hello world'"
eval "grep $argumentString ."
On the other hand, discretion is often the better part of valour, and working with eval is a place where discretion is better than bravery. If you are not completely in control of the string that is eval'd (if there's any user input in the command string that has not been rigorously validated), then you are opening yourself to potentially serious problems.
Note that the sequence of expansions for Bash is described in Shell Expansions in the GNU Bash manual. Note in particular sections 3.5.3 Shell Parameter Expansion, 3.5.7 Word Splitting, and 3.5.9 Quote Removal.
When you put quote characters into variables, they just become plain literals (see http://mywiki.wooledge.org/BashFAQ/050; thanks #tripleee for pointing out this link)
Instead, try using an array to pass your arguments:
argumentString=(-ir 'hello world')
grep "${argumentString[#]}" .
In looking at this and related questions, I'm surprised that no one brought up using an explicit subshell. For bash, and other modern shells, you can execute a command line explicitly. In bash, it requires the -c option.
argumentString="-ir 'hello world'"
bash -c "grep $argumentString ."
Works exactly as original questioner desired. There are two restrictions to this technique:
You can only use single quotes within the command or argument strings.
Only exported environment variables will be available to the command
Also, this technique handles redirection and piping, and other shellisms work as well. You also can use bash internal commands as well as any other command that works at the command line, because you are essentially asking a subshell bash to interpret it directly as a command line. Here's a more complex example, a somewhat gratuitously complex ls -l variant.
cmd="prefix=`pwd` && ls | xargs -n 1 echo \'In $prefix:\'"
bash -c "$cmd"
I have built command processors both this way and with parameter arrays. Generally, this way is much easier to write and debug, and it's trivial to echo the command you are executing. OTOH, param arrays work nicely when you really do have abstract arrays of parameters, as opposed to just wanting a simple command variant.

how to escape whitespace in Node-RED exec node command

Situation:
In my Node-RED flow there is a call to another program where I use the exec node.
The arguments are set-up in a function node and passed as the msg.payload to the exec node.
This works fine and returns the expected results as long as the command has no space in it.
Problem:
A customer cannot use the call to the command on his system, because the path tp the installation of the program contains a whitespace.
What I tried and didn't work:
Escaping the whitespace with ^ as read in another question: /opt/path^ toProgram^ directory/program
Escaping the whitespace with \\: /opt/path\\ toProgram\\ directory/program
Quoting the whole string with "": "/opt/path toProgram directory/program"
Quoting the whole string and escaping the whitespace: "/opt/path\\ toProgram\\ directory/program"
Quoting the whole string with '': '/opt/path toProgram directory/program'
Leaving the command line empty/""and combining any of the above points with its arguments to one string (in the function node that sets up the arguments for that exec node) and passing it on as the msg.payload -> input parameters in the exec node config panel. No Success.
What's not the problem:
The program itself works fine, on mine and on the customers system, it's only the path that is different
Other than that specific command string with whitespace, the configuration of the exec node and its msg.payload ( = input parameters or arguments) as well as the "use spawn() instead of exec()?" is fine and works
Request
Is there any other way to escape the whitespace that I'm not aware of so the path to the program can be found? From my understanding the whitespace is interpreted as a separator for the input arguments, which it should be, but not on the command string.
This should be a quick fix in my opinion, however nothing seems to work that usually works in node, js, php or bash.. thanks in advance for any hints and ideas!
Environment:
Node-RED version: v0.15.2 |
Node.js version: v5.12.0 |
Mac OS X 10.11.6 = Darwin 15.6.0 x64 LE
Screenshots
This is the part of the flow:
This config works:
This config does not work:
I've just tested this and option 3 (Quoting the path with "") works fine.
Putting "/home/foo/foo bar/date" in the command section of the exec node executed the binary correctly
After a good nights sleep the solution (at least what I thought at the time) was to adapt the exec node in node-red itself: The original code appended the additional arguments (the ones separated by whitespace) to the command, and then slice the whole string by whitespace into an array (node.cmd is the path to the command with or without whitespace):
before: var arg = node.cmd; arg += " "+msg.payload; arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
and the (earlier, incomplete) solution was to prepend the command with the builtin array.unshift(<value>) function to the array after the slice operation, so that the command path with the whitespace is not affected by the array creation:
after: var arg = ""; ...add msg.payload and slice arg string into array.. arg.unshift(node.cmd);
The file can be found at: nodered node 75-exec.js on github, in the lines 46-51
Update to real solution:
After further investigation it was clear, that the Use spawn() instead of exec()? option was the problem: the command part with whitespace in double quotes and using exec() worked fine. And my approach couldn't be used when the command path contained an argument, e.g. in /home/foo bar/date -r 1000.
Following the issue at the official github repository of node-red the command part to the arguments array at the beginning after the slice but instead to have the command path with whitespace in quotes and later unwrapping them, copying the behaviour when exec() is used. He added this line in the original file after line 51: if (/^".*"$/.test(cmd)) { cmd = cmd.slice(1,-1); } and now both spawn() and exec() work the same - credits to him, unfortunately I don't know his username here.

'less' the file specified by the output of 'which'

command 'which' shows the link to a command.
command 'less' open the file.
How can I 'less' the file as the output of 'which'?
I don't want to use two commands like below to do it.
=>which script
/file/to/script/fiel
=>less /file/to/script/fiel
This is a use case for command substitution:
less -- "$(which commandname)"
That said, if your shell is bash, consider using type -P instead, which (unlike the external command which) is built into the shell:
less -- "$(type -P commandname)"
Note the quotes: These are important for reliable operation. Without them, the command may not work correctly if the filename contains characters inside IFS (by default, whitespace) or can be evaluated as a glob expression.
The double dashes are likewise there for correctness: Any argument after them is treated as positional (as per POSIX Utility Syntax Guidelines), so even if a filename starting with a dash were to be returned (however unlikely this may be), it ensures that less treats that as a filename rather than as the beginning of a sequence of options or flags.
You may also wish to consider honoring the user's pager selection via the environment variable $PAGER, and using type without -P to look for aliases, shell functions and builtins:
cmdsource() {
local sourcefile
if sourcefile="$(type -P -- "$1")"; then
"${PAGER:-less}" -- "$sourcefile"
else
echo "Unable to find source for $1" >&2
echo "...checking for a shell builtin:" >&2
type -- "$1"
fi
}
This defines a function you can run:
cmdsource commandname
You should be able to just pipe it over, try this:
which script | less

Resources