get command line argument verbatim in nodejs - node.js

I want to get user command exactly the way they typed. The only allowed difference is ' can come in place of " and vice versa.
For example, if the user typed.
node test.js git commit -m "first message"
I want to log either of following on console.
You typed: git commit -m 'first message' //line A
You typed: git commit -m "first message" //line B
But there is not acceptable:
You typed: "git" "commit" "-m" "first message" //line C
You typed: 'git' 'commit' '-m' 'first message' // line D
As you can see above, quotes can be in different than the user provided (' can replace " and vice versa like in line B) but they can't be misplaced (like in line C and D). Hope this is clear.
Edit:
Edited the whole question to avoid confusion.

Your question is not very clear but assuming that you just want to log the arguments you are receiving you can do it like this:
let args = [];
// Get program arguments
process.argv.forEach((val, index) => {
if (index > 1)
args.push(val);
});
let command = args.join(" ");
console.log("You typed: ", command);
But note that you will lose the quotes when printing the output, as said by #Joe inthe comments, the shell scapes them before passing them to node

Related

^M still at the end of my string even after chop/chomp

I am wanting to pass a string variable in a ssh command. You can see in the code below I ssh to a server then cd to a directory that I pass a variable to. (cd $orig)
The variable is pulled from a file that I read in and put into an array.
I think that is where my error is because there might be unwanted hidden characters after I used the split command to read in from the file.
Here is the error I get:
ksh: /OnSight/jetplan/scripts/release/jscripts^M: not found
Can't open perl script "AddAlias.pl": No such file or directory
/OnSight/users/onsadm
SSHing to densbp53
/OnSight//scripts/release/jscripts
It can't find my script because the CD to the folder fails.
Sometimes the error says that 'end of file' can't be found. Like I'm doing a CD command with a EOF hidden symbol.
And here is the code:
for(my $j=0; $j < $#servName+1; $j++)
{
print "\nSSHing to $servName[$j]\n\n";
my $orig = $scriptfileLoc[$j];
#my $chopped = chop($orig);
chop($orig);
chomp($orig);
print ("\n$orig\n");
$sshstart = `ssh $servName[$j] "cd $orig; pwd; perl AddAlias.pl $aliasName $aliasCommand $addperl $servProfileLoc[$j]"`;
print $sshstart;
}
It outputs the $orig variable and it looks fine after the chop and chomp. (Which I've done both by themselves and still got the same error) So I pass it in my SSH command and it doesnt work.
I have a server file that holds all the server information, and yes it looks repetative I know.
densbp40:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp41:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp42:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp43:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp50:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp51:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp52:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp53:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp60:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp61:/export/home/.profile:/OnSight/scripts/release/jscripts
densbp62:/export/home/.profile:/OnSight/scripts/release/jscripts
tulsbp40:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp41:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp42:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp43:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp50:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp51:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp52:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
tulsbp53:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
densbcp1:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
densbcp2:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
densmsv1:/OnSight/.profile:/OnSight/scripts/jscripts
denamdp1:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
denamap1:/OnSight/users/profile:/OnSight/scripts/release/jscripts
denamap2:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
denfpev1:/OnSight/users/.profile:/OnSight/scripts/release/jscripts
This script asks the user to choose to send a file to ALL servers or just one.
To remove CR (^M) from the end of lines, use the following regex:
$orig =~ s/\r$//gm;
Anchoring at the line end guarantees that any other carriage return characters are not removed from your input. (You probably don't them there either, but to normalize line endings, it's better to not touch other characters).
g enables global matches (not only the first) and m enables multiline mode, so that $ matches the end of each line in a multiline string, not only the end of the string.
"^M" is carriage return a.k.a "\r". Use regex to remove it:
$orig =~ s/\r//g;

execute external program in lua without userinput as arguments in lua

I want to execute an external program in lua. Usually this can be done with
os.execute("run '"..arg0.."' 'arg1' arg2")
The problem with this approach is if I want to pass user input as string to an external program, user input could be '; evil 'h4ck teh system' ' and the script from above would execute like this:
/bin/bash -c "run ''; evil 'h4ck teh system' '' 'arg1' arg2"
Another problem occurs when I have '$var' as argument and the shell replaces this with its environment variable. In my particular case I have something like [[program 'set title "$My Title$"']] – so nested strings – and program parses "$My Title$" (with escape sequences) differently than '$My Title$' (as it is). Because I want to set the title as it, the best way is to have arguments like this: 'My Title'. But now the command have to be:
os.execute([[run "set title '$My Title$'"]])
But now – as I said – $My will be replaced with an empty string, because the environment does not know any variable named $My and because, I never wanted it to be replaced.
So I am looking for the usual approach with
execv("run", {"set title '"..arg0.."'", arg1, arg2})
local safe_unquoted = "^[-~_/.%w%%+,:#^]*$"
local function q(text, expand) -- quoting under *nix shells
-- "expand"
-- false/nil: $var and `cmd` must NOT be expanded (use single quotes)
-- true: $var and `cmd` must be expanded (use double quotes)
if text == "" then
text = '""'
elseif not text:match(safe_unquoted) then
if expand then
text = '"'..text:gsub('["\\]', '\\%0')..'"'
else
local new_text = {}
for s in (text.."'"):gmatch"(.-)'" do
new_text[#new_text + 1] = s:match(safe_unquoted) or "'"..s.."'"
end
text = table.concat(new_text, "\\'")
end
end
return text
end
function execute_commands(...)
local all_commands = {}
for k, command in ipairs{...} do
for j = 1, #command do
if not command[j]:match"^[-~_%w/%.]+$" then
command[j] = q(command[j], command.expand)
end
end
all_commands[k] = table.concat(command, " ") -- space is arguments delimiter
end
all_commands = table.concat(all_commands, ";") -- semicolon is commands delimiter
return os.execute("/bin/bash -c "..q(all_commands))
end
Usage examples:
-- Usage example #1:
execute_commands(
{"your/program", "arg 1", "$arg2", "arg-3", "~/arg4.txt"},
{expand=true, "echo", "Your program finished with exit code $?"},
{"ls", "-l"}
)
-- The following command will be executed:
-- /bin/bash -c 'your/program '\''arg 1'\'' '\''$arg2'\'' arg-3 ~/arg4.txt;echo "Your program finished with exit code $?";ls -l'
$arg2 will NOT be expanded into value because of single quotes around it, as you required.
Unfortunately, "Your program finished with exit code $?" will NOT be expanded too (unless you explicitly set expand=true).
-- Usage example #2:
execute_commands{"run", "set title '$My Title$'", "arg1", "arg2"}
-- the generated command is not trivial, but it does exactly what you need :-)
-- /bin/bash -c 'run '\''set title '\''\'\'\''$My Title$'\''\'\'' arg1 arg2'

How can I dynamically pass arguments to a node script using unix commands?

# 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.
$ 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"
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.
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)

How do I get the output of a shell command executed using into a variable from Jenkinsfile (groovy)?

I have something like this on a Jenkinsfile (Groovy) and I want to record the stdout and the exit code in a variable in order to use the information later.
sh "ls -l"
How can I do this, especially as it seems that you cannot really run any kind of groovy code inside the Jenkinsfile?
The latest version of the pipeline sh step allows you to do the following;
// Git committer email
GIT_COMMIT_EMAIL = sh (
script: 'git --no-pager show -s --format=\'%ae\'',
returnStdout: true
).trim()
echo "Git committer email: ${GIT_COMMIT_EMAIL}"
Another feature is the returnStatus option.
// Test commit message for flags
BUILD_FULL = sh (
script: "git log -1 --pretty=%B | grep '\\[jenkins-full]'",
returnStatus: true
) == 0
echo "Build full flag: ${BUILD_FULL}"
These options where added based on this issue.
See official documentation for the sh command.
For declarative pipelines (see comments), you need to wrap code into script step:
script {
GIT_COMMIT_EMAIL = sh (
script: 'git --no-pager show -s --format=\'%ae\'',
returnStdout: true
).trim()
echo "Git committer email: ${GIT_COMMIT_EMAIL}"
}
Current Pipeline version natively supports returnStdout and returnStatus, which make it possible to get output or status from sh/bat steps.
An example:
def ret = sh(script: 'uname', returnStdout: true)
println ret
An official documentation.
quick answer is this:
sh "ls -l > commandResult"
result = readFile('commandResult').trim()
I think there exist a feature request to be able to get the result of sh step, but as far as I know, currently there is no other option.
EDIT: JENKINS-26133
EDIT2: Not quite sure since what version, but sh/bat steps now can return the std output, simply:
def output = sh returnStdout: true, script: 'ls -l'
If you want to get the stdout AND know whether the command succeeded or not, just use returnStdout and wrap it in an exception handler:
scripted pipeline
try {
// Fails with non-zero exit if dir1 does not exist
def dir1 = sh(script:'ls -la dir1', returnStdout:true).trim()
} catch (Exception ex) {
println("Unable to read dir1: ${ex}")
}
output:
[Pipeline] sh
[Test-Pipeline] Running shell script
+ ls -la dir1
ls: cannot access dir1: No such file or directory
[Pipeline] echo
unable to read dir1: hudson.AbortException: script returned exit code 2
Unfortunately hudson.AbortException is missing any useful method to obtain that exit status, so if the actual value is required you'd need to parse it out of the message (ugh!)
Contrary to the Javadoc https://javadoc.jenkins-ci.org/hudson/AbortException.html the build is not failed when this exception is caught. It fails when it's not caught!
Update:
If you also want the STDERR output from the shell command, Jenkins unfortunately fails to properly support that common use-case. A 2017 ticket JENKINS-44930 is stuck in a state of opinionated ping-pong whilst making no progress towards a solution - please consider adding your upvote to it.
As to a solution now, there could be a couple of possible approaches:
a) Redirect STDERR to STDOUT 2>&1
- but it's then up to you to parse that out of the main output though, and you won't get the output if the command failed - because you're in the exception handler.
b) redirect STDERR to a temporary file (the name of which you prepare earlier) 2>filename (but remember to clean up the file afterwards) - ie. main code becomes:
def stderrfile = 'stderr.out'
try {
def dir1 = sh(script:"ls -la dir1 2>${stderrfile}", returnStdout:true).trim()
} catch (Exception ex) {
def errmsg = readFile(stderrfile)
println("Unable to read dir1: ${ex} - ${errmsg}")
}
c) Go the other way, set returnStatus=true instead, dispense with the exception handler and always capture output to a file, ie:
def outfile = 'stdout.out'
def status = sh(script:"ls -la dir1 >${outfile} 2>&1", returnStatus:true)
def output = readFile(outfile).trim()
if (status == 0) {
// output is directory listing from stdout
} else {
// output is error message from stderr
}
Caveat: the above code is Unix/Linux-specific - Windows requires completely different shell commands.
this is a sample case, which will make sense I believe!
node('master'){
stage('stage1'){
def commit = sh (returnStdout: true, script: '''echo hi
echo bye | grep -o "e"
date
echo lol''').split()
echo "${commit[-1]} "
}
}
For those who need to use the output in subsequent shell commands, rather than groovy, something like this example could be done:
stage('Show Files') {
environment {
MY_FILES = sh(script: 'cd mydir && ls -l', returnStdout: true)
}
steps {
sh '''
echo "$MY_FILES"
'''
}
}
I found the examples on code maven to be quite useful.
All the above method will work. but to use the var as env variable inside your code you need to export the var first.
script{
sh " 'shell command here' > command"
command_var = readFile('command').trim()
sh "export command_var=$command_var"
}
replace the shell command with the command of your choice. Now if you are using python code you can just specify os.getenv("command_var") that will return the output of the shell command executed previously.
How to read the shell variable in groovy / how to assign shell return value to groovy variable.
Requirement : Open a text file read the lines using shell and store the value in groovy and get the parameter for each line .
Here , is delimiter
Ex: releaseModule.txt
./APP_TSBASE/app/team/i-home/deployments/ip-cc.war/cs_workflowReport.jar,configurable-wf-report,94,23crb1,artifact
./APP_TSBASE/app/team/i-home/deployments/ip.war/cs_workflowReport.jar,configurable-temppweb-report,394,rvu3crb1,artifact
========================
Here want to get module name 2nd Parameter (configurable-wf-report) , build no 3rd Parameter (94), commit id 4th (23crb1)
def module = sh(script: """awk -F',' '{ print \$2 "," \$3 "," \$4 }' releaseModules.txt | sort -u """, returnStdout: true).trim()
echo module
List lines = module.split( '\n' ).findAll { !it.startsWith( ',' ) }
def buildid
def Modname
lines.each {
List det1 = it.split(',')
buildid=det1[1].trim()
Modname = det1[0].trim()
tag= det1[2].trim()
echo Modname
echo buildid
echo tag
}
If you don't have a single sh command but a block of sh commands, returnstdout wont work then.
I had a similar issue where I applied something which is not a clean way of doing this but eventually it worked and served the purpose.
Solution -
In the shell block , echo the value and add it into some file.
Outside the shell block and inside the script block , read this file ,trim it and assign it to any local/params/environment variable.
example -
steps {
script {
sh '''
echo $PATH>path.txt
// I am using '>' because I want to create a new file every time to get the newest value of PATH
'''
path = readFile(file: 'path.txt')
path = path.trim() //local groovy variable assignment
//One can assign these values to env and params as below -
env.PATH = path //if you want to assign it to env var
params.PATH = path //if you want to assign it to params var
}
}
Easiest way is use this way
my_var=`echo 2`
echo $my_var
output
: 2
note that is not simple single quote is back quote ( ` ).

getting raw command line arguments in node.js

How can I get the raw command line arguments in a node.js app,
given this example command:
node forwardwithssh.js echo "hello arguments"
process.argv will be [ "node", "/path/to/forwardwith.js", "echo", "hello arguments" ]
And there's no way to reconstruct the original echo "hello arguments" from that (ie. join(" " won't put quotes back).
I want the whole original raw string after the script name.
what I want is easily obtained in bash scripts with "$*", is there any equivalent way to get that in node.js?
Note: the intention in particular is to receive a command to be executed somewhere else (eg. thru ssh)
Wrap each of the args in single quotes, and escape and single quotes within each arg to '\'':
var cmd_string = process.argv.map( function(arg){
return "'" + arg.replace(/'/g, "'\\''") + "'";
}).join(' ');
That would give you a cmd_string containing:
'node' '/path/to/forwardwith.js' 'echo' 'hello arguments'
which can be run directly in another shell.

Resources