Sometimes a PowerShell script gets called with arguments containing spaces and enclosed by double-quotes,
to ensure the string is interpreted as one single argument. A problem arises when the argument is empty and the double-quotes are passed containing nothing. PS sees the command line as non-empty, and tries to form an argument value from it. Except the double-quotes are stripped away, leaving an empty string as an argument value. Of course PS cannot actually use the empty value, so we want to validate command line arguments to disallow empty strings.
See the calling Cmd script, and the PS source, both commented. Tests 4 and 5 show the benefit of the function that scans the $args[] array and returns the first valid/non-empty argument.
Question: why can the function not be coded with just one ForEach loop?
Args.CMD:
#Echo Off
CD /D "%~dp0" & Rem Change to the folder where this script lives
CLS
Echo.
Rem PS script name is the same as this Cmd script
Set Script=%~n0.PS1
Rem Parameters of each call here are TestNumber, ExpectedExitCode, Argument1, Argument2
Rem Test 0: Try with zero arguments; expected exit code is 1
Call :TryMe 0 1
Rem Test 1: Try with non-useful arguments; expected exit code is 2
Call :TryMe 1 2 "" ""
Rem Test 2: Try with valid arguments, single words; expected exit code is 0
Call :TryMe 2 0 ArgOne ArgTwo
Rem Test 3: Try with valid arguments, multiple words; expected exit code is 0
Call :TryMe 3 0 "Arg One" "Arg Two"
Rem Test 4: Try with mixed arguments, single words; expected exit code is 0
Call :TryMe 4 0 "" ArgTwo
Rem Test 5: Try with mixed arguments, multiple words; expected exit code is 0
Call :TryMe 5 0 "" "Arg Two"
Echo All Done.
Pause
GoTo :EOF
:TryMe
Set TstNbr=%1
Set ExpCod=%2
Set Args12=%3 %4
Echo Test %TstNbr% of 5: PowerShell.EXE -F %Script% %Args12%
PowerShell.EXE -F %Script% %Args12%
Echo ErrorLevel is [%ErrorLevel%], expected was [%ExpCod%]
Pause
Echo.
GoTo :EOF
Args.ps1:
Set-StrictMode -Version 3.0 # disallow implicit variables
function ArgNonEmtFstGet( $arrArgs ) { # get the first non-empty argument
ForEach( $strArgs In $arrArgs ) { # <=== why is this double-indirection needed?
ForEach( $strArg In $strArgs ) { # scan all argument strings
$intArgLen = $strArg.Length # get length of current argument-string
If( $intArgLen -gt 0 ) { Return $strArg } # first time length is greater than zero, quit scanning and pass that argument back to caller
} # done scanning
} # <=== why is this double-indirection needed?
"" # when we finished scanning and did not return early, return a non-crashing value
} # ArgNonEmtFstGet
# Step 0: show we are alive
"Arg count: " + $args.Length # emit how many strings exist that *could be* arguments
# Step 1: look for any argument at all
If( $args.Length -eq 0 ) { # when there are zero strings that look like arguments
Write-Output "Putative argument required." # emit error message for 'no strings found'
Exit( 1 ) # abort with exit code
} # done with no strings found that *could be* arguments
"First putative argument: [" + $args[0] + "]" # emit the first argument-looking string
# Step 2: look for an argument that is actually meaningful
$strArg = ArgNonEmtFstGet( $args ) # get the first non-empty argument; that is our input
If( $strArg -eq "" ) { # when it is not a usable argument
Write-Output "Non-empty argument required." # emit error message for 'no real arguments found'
Exit( 2 ) # abort with exit code
} # done with no usable arguments found
"First non-empty argument: [$strArg]" # emit the first valid argument; this is our input
I can't reproduce your issue with your nested foreach-statement.
When I remove the inner loop I get the same result.
But I would suggest that you should avoid using the $args variable, because it accepts any input and doesn't allow named binding (only positional).
Just declare a param block in your PowerShell Script like below, then you don't need to loop over your arguments and additionally, you have named parameters you can use in your batch script. (Positional binding is still possible).
Sample script with param block and "ValidateScript" attribute, which validates the input to avoid null, empty or whitespace input.
param(
# first mandatory parameter. Default Position = 1 (if using positional binding)
[Parameter(Mandatory=$true)]
[ValidateScript(
{
if([String]::IsNullOrWhiteSpace($_)) {
throw [System.ArgumentException]'Your string is null, empty or whitespace!'
} else {
$true
}
}
)]
[string]
$varA,
# second mandatory parameter. Default Position = 2 (if using positional binding)
[Parameter(Mandatory=$true)]
[ValidateScript(
{
if([String]::IsNullOrWhiteSpace($_)) {
throw [System.ArgumentException]'Your string is null, empty or whitespace!'
} else {
$true
}
}
)]
[string]
$varB,
# You can just use this parameter if you want your arguments being absolutely dynamic
[Parameter(ValueFromRemainingArguments)]
[ValidateScript(
{
if([String]::IsNullOrWhiteSpace($_)) {
throw [System.ArgumentException]'Your string is null, empty or whitespace!'
} else {
$true
}
}
)]
[string[]]
$remainingArgs
)
# create ordered dictionary of mandatory arguments used for returning object
$properties= [ordered]#{
'Arg0' = "'$varA'"
'Arg1' = "'$varB'"
}
# add any optional remaining argument (if any)
$index = 1 # start index
foreach ($remainingArg in $remainingArgs) {
$index++
$properties.Add("Arg$index", "'$remainingArg'")
}
# return as object
[pscustomobject]$properties
As mentioned in the script, you can also just use the third parameter if you want your script being absolutely dynamic in terms of parameters.
More infos about attribute "ValidateScript": https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-5.1#validatescript-validation-attribute
PS:
Sample command when using named parameters in batch:
PowerShell.EXE -F %Script% -varA "%3" -varB "%4"
Related
This is my groovy Script on Jenkins
def gitURL = "http://bitbucket.webapp.intern.de/scm/myproject.git"
def command = "git ls-remote -h $gitURL"
def proc = command.execute() // line 3
proc.waitFor()
if ( proc.exitValue() != 0 ) {
println "Error, ${proc.err.text}"
System.exit(-1)
}
def branches = proc.in.text.readLines().collect { // line 9
it.replaceAll(/[a-z0-9]*\trefs\/heads\//, '') // line 10
}
return branches
First I do not understand anything. How on line 3 can you call execute on command which is supposed to be a string ??
What type is variable branches in line 9 ???
Is branches a String ??
What is it.replaceAll in line 10 ??? I know replaceAll as method from String. But it has 2 parameters.
I do not see 2 parameters here.
Now I somehow understand branches contains all branches. What I want to do. I only want to have
branches which contain "-REST" it their names. How can I do this ??
My intention was using java. But it does not work.
List<String> branchesNew = new ArrayList<String>();
for(String branch : branchesNew)
{
if(branch.contains("-REST"))
branchesNew.add(branch);
}
line 3: this is a Groovy feature, see http://groovy-lang.org/groovy-dev-kit.html#process-management
line 9: it's a collection (a list, for instance) given as output of the collect method. see also http://groovy-lang.org/groovy-dev-kit.html#_working_with_collections
line 10: the second argument is ''. it replaces the regexp matches (branch prefixes) with an empty string.
You can filter all your wanted branches using the findAll method on collections. Following the given example:
def branchesNew = proc.in.text.readLines().findAll{ it.contains('-REST') }
Look at Filtering and searching for all related manipulation methods.
I am using branch name to pass it into build script. $(env.BRANCH_NAME).
I would like to manipulate the value before using it. For example in case we build from trunk I want suffix for the build output be empty but in case of branch I want it to be -branch name.
currently I am doing it by defining environment section.
environment {
OUTPUT_NAME_SUFFIX = ($(env.BRANCH_NAME) == 'trunk') ? '': $(env.BRANCH_NAME)
}
I am getting this error:
WorkflowScript: 4: Environment variable values must either be single quoted, double quoted, or function calls. # line 4, column 62.
(env.BRANCH_NAME) == 'trunk') ? '': $(en
^
What the best way to define variables and eval their values in scope of pipeline.
TIA
You can use string interpolation to evaluate the expression:
environment {
OUTPUT_NAME_SUFFIX = "${env.BRANCH_NAME == 'trunk' ? '': env.BRANCH_NAME}"
}
This will fix the error you're getting, however pipeline does not allow you to have environment variables that are of 0 length, aka empty string (JENKINS-43632).
That means that setting OUTPUT_NAME_SUFFIX to '' is like unseting it. You might want to precalculate the whole name of your output, so that your env variable is never an empty string.
I have solved it by adding following code. So far had no issues with empty strings.
stage('Set Environmnet'){
steps {
script {
if(BRANCH_NAME == 'trunk'){
env.OUTPUT_NAME_SUFFIX = ''
}else if (BRANCH_NAME.startsWith("branches")){
env.OUTPUT_NAME_SUFFIX = "-" + BRANCH_NAME.substring(BRANCH_NAME.lastIndexOf("/")+1)
}else{
env.OUTPUT_NAME_SUFFIX = ''
}
}
}
}
I am writing a Lua 5.3 program and it requires arguments to be passed to it. I use the arg table to get the first argument: arg[1], but the 1st argument, according to the script, is nil even though I have passed an argument to the file.
Here's the code that I've written:
local strin = arg[1]:sub(2,arg[1]:len()-1) -- to remove the quote marks
local i = 0
for W in strin:gmatch(".") do
i = i + 1
if W == " " or W == "\t" then strin = strin:sub(i+1) else break end
end
print(strin)
I pass the argument to the file like this:
C:\Users\WhiteKid\Documents\Scripts>RemoveWhiteSpace.lua " hello world!"
It thinks arg[1] is a nil value when it is not. Is there a different way of getting the arguments passed to a lua script in Lua 5.3?
Since you are calling the .lua script directly (C:\Users\WhiteKid\Documents\Scripts>RemoveWhiteSpace.lua " hello world!"), you seem to have an association with a lua interpreter. You need to make sure you pass %1 or %* to the interpreter you are calling in that association. Alternatively, try calling the Lua interpreter and passing the script name and the parameters and it should work as you expect.
Also, you should check if arg[1] is present and check if the quotes are also there (as they may be removed before the parameters get to the script, so you should not always expect them).
I would like to create a procedure like this simple example:
proc name {args} {
foreach val $args {
puts $val
}
}
But I would like the procedure to handle variables that don't exist, something like the code shown below:
proc name {args} {
foreach val $args {
if [info exists $val] {
puts $val
}
}
}
The problem is that the code is not executed because as soon as I call the procedure with an unexisting variable it immediately stalls, prior to go into the code, saying that there is a variable that doesn't exist. Is it probable because the procedure checks argument existance before entering the body?.
I can make it work by changing args by several optional variables with predefined values, but that limits the procedure and makes it look bad.
Can I make a proc able to handle unexisting variables?
You can't pass a variable as an argument: arguments have to be values. You can pass a variable name as an argument and use that as a reference to the variable inside the procedure. Example:
proc name args {
foreach varname $args {
upvar 1 $varname var
if {[info exists var]} {
puts $var
}
}
}
(The call to upvar creates a link between the variable whose name is the value of the variable varname outside the procedure and the variable called var inside the procedure. This is one way to "pass a variable to a procedure".)
Then you can do this:
% set foo 1 ; set baz 3
% name foo bar baz
1
3
Note that if you try to invoke the procedure as
% name $bar
where bar is undefined, the interpreter tries (and fails) to evaluate it before calling the procedure. That might be what you are seeing.
Documentation:
upvar
If we look at the point where you are calling the command (procedures are commands; they're a subclass really) you'll see something like this in your code:
name $a $b $c
That's fine if all those variables exist, but if one doesn't, it will blow up even before name is called. In Tcl, $a means exactly “read the variable a and use its contents here”, unlike in some other languages where $ means “look out language, here comes a variable name!”
Because of this, we need to change the calling convention to be one that works with this:
name a b c
That's going to require the use of upvar. Like this:
proc name {args} {
foreach varName $args {
# Map the caller's named variable to the local name “v”
upvar 1 $varName v
# Now we can work with v in a simple way
if {[info exists v]} {
puts $v
}
}
}
You made a mistake here
if [info exists $val]
When info exists is used it should be checked against variable name, not the variable value.
Lets come to your actual question.
You can pass the arguments to the procedure as a key-value pair, then it is pretty simple.
proc user_info {args} {
#Converting the arguments into array
if {[catch {array set aArgs $args}]} {
puts "Please pass the arguments as key-value pair"
return 1
}
#Assume, we need to ensure these 3 arguments passed for sure.
set mandatoryArgs "-name -age -country"
foreach mArg $mandatoryArgs {
if {![info exists aArgs($mArg)]} {
puts "Missing mandatory argument '$mArg'"
return 1
}
}
}
user_info -name Dinesh
I want to know the difference between Exit and Return inside a proc.
I have a script that contains a lot of proc, one of them uses exit and some value ex:
proc someProc {code} {
exit $code
}
and another one is like:
proc multiply {value} {
set number [expr {$value * 5}]
return $number
}
Does the exit stop the running script or what is the difference?
The exit command makes the current process stop. The running program will be gone afterwards (though the file containing the code will still be there). Its optional argument is the error code to hand to the OS; the default is zero, for no-error-at-all.
The return command makes the current procedure call stop. The optional argument provides a way to say what the result of the call to the procedure is; the default is the empty string (given that Tcl doesn't have NULL/null/nil/etc. at all).
Internally, exit does a system call to the “stop running this program” OS API, and return throws an exception that the general procedure management code transforms into the result of the call.