In GNU/Linux I would do:
PROGPATH=/long/and/complicated/path/to/some/bin
$PROGPATH/program args...
but in Powershell if I try this:
$PROGPATH=\long\and\complicated\path\to\some\bin
$PROGPATH\program args...
I get:
At script.ps1:2 char:...
+ $PROGPATH\program args ...
+ ~~~~~~~~
Unexpected token '\program' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : UnexpectedToken
So how do I do this simple thing I know how to do in bash, in Powershell?
js2010's helpful answer shows the correct solution:
Because your command name/path contains a variable reference ($PROGPATH/...), you must invoke it with &.
The same applies if a grouping expression, (...) is used, or a subexpression, $(...) is involved.
Additionally, the same applies if a command name/path is quoted ('...' or "...")[1], as is required if the path contains spaces, for instance.
To put it differently: Direct invocation is only supported if the command name/path is a verbatim, unquoted string[1]; in all other cases, & must be used.
As for why:
&, the call operator is necessary to force interpretation of a statement as a command, i.e. to have it parsed in argument mode (see below), so as to result in command execution rather than expression evaluation.
PowerShell has two fundamental parsing modes:
argument mode, which works like a traditional shell, where the first token is a command name/path, such as a cmdlet or an external program, with subsequent tokens representing the arguments, which only require quoting if they contain shell metacharacters (chars. with special meaning to PowerShell, such as spaces to separate tokens).
expression mode, which works like expressions in programming languages.
PowerShell decides based on a statement's first token what parsing mode to apply:
If, among other things, the first token starts with a variable reference or is a quoted string, PowerShell parses in expression mode.
In expression mode, \ starts a new token, and unrecognized token \program results in the syntax error you saw.
(If you had used /, it would have been interpreted as the division operator, and program wouldn't be a valid divisor operand.)
[1] Note that if your executable path is a literal string (doesn't contain variable references of expressions) you may alternatively `-escape individual characters (spaces) in lieu of enclosing entire string in '...' or "...", in which case & is then not necessary; e.g.:
C:\Program` Files\Notepad++\notepad++.exe
With a literal string you can even employ partial single- or double-quoting as long as the first token is unquoted; e.g.:
C:\"Program Files"\Notepad++\notepad++.exe
Use the call operator "&". https://ss64.com/ps/call.html
Related: Executing a command stored in a variable from PowerShell
$progpath = 'c:\windows\system32'
& $progpath\notepad somefile.txt
Something with a space:
& 'C:\Program Files\internet explorer\iexplore' yahoo.com
Other options, adding to the path:
$env:path += ';C:\Program Files\internet explorer'
iexplore yahoo.com
And backquoting the spaces:
C:\Program` Files\internet` explorer\iexplore yahoo.com
Related
What specific syntax needs to be changed in the aws s3api put-object-tagging --bucket bucketName --key fileName.tar.gz --tagging TagSet={Key=public,Value=yes} command to prevent the error shown below when the command is run in PowerShell?
Note that the aws s3api put-object-tagging --bucket bucketName --key fileName.tar.gz --tagging TagSet={Key=public,Value=yes} command syntax works perfectly when run in windows cmd on the very same computer.
Here is the PowerShell log including the command and the error message on the same computer where this command works in windows cmd:
PS C:\Users\userName> aws s3api put-object-tagging --bucket bucketName --key fileName.tar.gz --tagging TagSet={Key=public,Value=yes}
At line:1 char:129
+ ... --key fileName.tar.gz --tagging TagSet={Key=public,Value=ye ...
+ ~
Missing argument in parameter list.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingArgument
PS C:\Users\userName>
Also note that I am new to PowerShell. I just sometimes now need to translate short scripts from other languages into PowerShell.
Update:
The answer below addresses your question as asked, based on your (seemingly incorrect) claim that the first command mentioned in your question worked as-is from cmd.exe. The points made below generally apply to the syntax of arguments passed to commands in PowerShell.
Based on a later comment, you state that what solved your problem was to pass a JSON string to the aws CLI's --tagging parameter:
You state that '{\"TagSet\": [{\"Key\":\"public\",\"Value\":\"yes\"}]}' worked for you (which represents the following verbatim JSON string: {"TagSet": [{"Key":"public","Value":"yes"}]).
Here too it is the quoting ('...') that makes the argument work, as discussed in the bottom section.
As an aside: This strange need to manually \-escape " characters embedded in an argument passed to external programs is indeed an - unfortunate - requirement in PowerShell up to at least v7.2.x, due to a long-standing bug. It may get fixed in a future version, which may require opt-in: see this answer.
However, apart from the PowerShell syntax problem that is the subject of your question, it looks like your original command simply used incorrect aws shorthand syntax (a shorter alternative to passing JSON): according to the docs for the put-object-tagging subcommand, the following should work - in addition to the required quoting ('...') for PowerShell's sake, note the [...] around {...} which were missing from your own original command: 'TagSet=[{Key=public,Value=yes}]'
Replace:
TagSet={Key=public,Value=yes}
with:
'TagSet={Key=public,Value=yes}'
That is, quote your argument, which prevents PowerShell from interpreting {...} as a script block, which is what caused your problem.
Note the use of a '...', i.e a single-quoted verbatim string, which is the best choice if an argument contains no embedded variable references or subexpression (for the latter, you need string interpolation in the form of "...", i.e., a double-quoted expandable string).
PowerShell differs from other shells in two notable respects:
its escape character is ` (the so-called backtick), not \ (as in POSIX-compatible shells such as Bash) or ^ (as in cmd.exe)
it has more metacharacters than other shells, such as {, }, and #
See this answer for more information.
As for the specific error message you saw:
The content of a script block ({ ... }) is parsed as PowerShell code, and the content in your case, Key=public,Value=yes, is parsed as a command, whose syntax happens to be invalid:
Key=public is interpreted as a command name
,Value=yes is parsed as its argument, which - due to the , - is parsed as an array, but a literal array passed as an argument must not start with ,, which is what the error message is trying to say: it's missing an array element before ,
You can more easily provoke this error by submitting the following on the command line:
foo , bar # -> Error "Missing argument in parameter list."
On linux I can rappresent the / char in a different way:
${HOME:0:1}
So, for example, cat ${HOME:0:1}etc${HOME:0:1}passwd would be treated like cat /etc/passwd
Is there any way I can do the same thing on windows via powershell and cmd.exe for the backslash?
PowerShell has no equivalent to the parameter expansions available in POSIX-compatible shells such as Bash, of which your substring extraction (${HOME:0:1} to get the substring of length 1 at character position 0, i.e the first char. of the value of variable $HOME) is an example (link is to the Bash manual).
However, PowerShell makes it easy:
to embed the results of arbitrary expressions and even whole statements inside expandable (double-quoted) string ("..."), using $(...), the subexpression operator.
to pass the results of any expression or command (pipeline) as an argument to a command, by enclosing it in (...), the grouping operator.
The following command variations are equivalent, and dynamically use the platform-appropriate path (directory) separator, i.e. / on Unix-like platforms, and \ on Windows:
# -> '/etc/passwd' on Unix
# -> '\etc\passwd' on Windows
Write-Output "$([System.IO.Path]::DirectorySeparatorChar)etc$([System.IO.Path]::DirectorySeparatorChar)passwd"
# Ditto.
Write-Output ('{0}etc{0}passwd' -f [System.IO.Path]::DirectorySeparatorChar)
See also:
[System.IO.Path]::DirectorySeparatorChar
-f, the string format operator
I came across a script that obfuscated its calls using the dot sourcing operator.
$ORMEFyak=.('n'+'ew-obje'+'ct') NeT.WebcLIENt;
This seems to be equivalent to:
$var = New-Object Net.WebClient
Running . "help" returns the contents of the help cmdlet.
Why exactly does the dot source operator act like this?
The behavior is not specific to ., the dot-sourcing operator; it equally applies to &, the call operator.
Only when you're invoking a script file (*.ps1) (or, less commonly, function or script block) do . and & behave differently: . runs the code directly in the caller's scope, whereas &, which is more typical, runs it in a child scope - see this answer for more information.
In the case of New-Object, as a cmdlet, you can technically use them interchangeably (or omit them altogether - see below), but for conceptual clarity it's better to use & unless actual dot-sourcing is required.
It isn't so much that they convert a string to a command, it is that their purpose is to execute a command given by its name, as a string, among other forms (see below).
The specific syntax used to provide or construct this name for . or & is incidental, and in your case a string-concatenation expression was used to obscure the name of the command being invoked.
However, specifying the name of a command unquoted, verbatim (if syntactically possible) is special in that you can then invoke without an operator - & is then implied; see below for an example.
Therefore, all of the following variations are equivalent (for the reasons stated above, I'm using &):
# Bareword (unquoted, verbatim) command name
# If you don't need dot-sourcing, you can invoke *as-is* -
# use of & is implied.
New-Object regex 'foo?'
# Optionally, use &
& New-Object regex 'foo?'
# If the command name *needs quoting*, is *(based on) a variable* or
# *built by an expression*, & is *required*
$cmd = 'New-Object'
& $cmd regex 'foo?'
& ('New-' + 'Object') regex 'foo?'
Note that in addition to acting on command names (strings), . and & can also invoke script blocks (e.g. & { New-Object regex 'foo?' }) and command-information objects returned by Get-Command (e.g., & (Get-Command New-Object) regex 'foo?')
A Perl system call must send the following string to the UnixShell:
'"XYZ"'
In my Perl script I have used the following command:
system("cleartool mkattr -replace ATTRIBUTE '"$attribute"' lbtype:$label");
Everything is well passed to the Shell Unix, except both uses of the quote character:
'
Indeed,
cleartool mkattr -replace ATTRIBUTE
The above command is passed as it is exactly what I want.
The Perl variables $attribute and $label are well interpreted.
But I don't know what to do to obtain exactly:
'"XYZ"'
Here XYZ is the value of the Perl variable $attribute
OS is AIX (Unix) and Shell is ksh. cleartool is the command line interface of Clearcase but no Clearcase skill is necessary to fix my problem.
If you want to execute a system command and don't have to use any shell syntax like redirects, it's usually better and safer to use the list form of system:
system(
'cleartool', 'mkattr', '-replace', 'ATTRIBUTE',
qq{"$attribute"}, qq{lbtype:$label}
);
# or, if you really want to pass both types of quotes:
system(
'cleartool', 'mkattr', '-replace', 'ATTRIBUTE',
qq{'"$attribute"'}, qq{lbtype:$label}
);
See perldoc -f system
It's not clear from your question if you want to pass '"XYZ"' or "XYZ".
See "Quote and Quote like Operators" and use qq{...}:
system(qq{cleartool mkattr -replace ATTRIBUTE '"$attribute"' lbtype:$label});
qq{...} is exactly like "..." except you can then use double quotes " in your string without escaping them.
You can use any character directly after the qq and must then use the same character to denote the end-of-string, i.e. qqX...X would work the same way. You would run into problems if your string contains Xes, so don't do that.
You can also use paired characters as delimiter ({}, (), <>) which is what you usually will see.
In Unix I could run myscript '"test"' and I would get "test".
In Windows cmd I get 'test'.
How can I pass double-quotes as a parameter? I would like to know how to do this manually from a cmd window so I don't have to write a program to test my program.
Another way to escape quotes (though probably not preferable), which I've found used in certain places is to use multiple double-quotes. For the purpose of making other people's code legible, I'll explain.
Here's a set of basic rules:
When not wrapped in double-quoted groups, spaces separate parameters:program param1 param2 param 3 will pass four parameters to program.exe: param1, param2, param, and 3.
A double-quoted group ignores spaces as value separators when passing parameters to programs:program one two "three and more" will pass three parameters to program.exe: one, two, and three and more.
Now to explain some of the confusion:
Double-quoted groups that appear directly adjacent to text not wrapped with double-quotes join into one parameter:hello"to the entire"world acts as one parameter: helloto the entireworld.
Note: The previous rule does NOT imply that two double-quoted groups can appear directly adjacent to one another.
Any double-quote directly following a closing quote is treated as (or as part of) plain unwrapped text that is adjacent to the double-quoted group, but only one double-quote:"Tim says, ""Hi!""" will act as one parameter: Tim says, "Hi!"
Thus there are three different types of double-quotes: quotes that open, quotes that close, and quotes that act as plain-text.
Here's the breakdown of that last confusing line:
" open double-quote group
T inside ""s
i inside ""s
m inside ""s
inside ""s - space doesn't separate
s inside ""s
a inside ""s
y inside ""s
s inside ""s
, inside ""s
inside ""s - space doesn't separate
" close double-quoted group
" quote directly follows closer - acts as plain unwrapped text: "
H outside ""s - gets joined to previous adjacent group
i outside ""s - ...
! outside ""s - ...
" open double-quote group
" close double-quote group
" quote directly follows closer - acts as plain unwrapped text: "
Thus, the text effectively joins four groups of characters (one with nothing, however):
Tim says, is the first, wrapped to escape the spaces
"Hi! is the second, not wrapped (there are no spaces)
is the third, a double-quote group wrapping nothing
" is the fourth, the unwrapped close quote.
As you can see, the double-quote group wrapping nothing is still necessary since, without it, the following double-quote would open up a double-quoted group instead of acting as plain-text.
From this, it should be recognizable that therefore, inside and outside quotes, three double-quotes act as a plain-text unescaped double-quote:
"Tim said to him, """What's been happening lately?""""
will print Tim said to him, "What's been happening lately?" as expected. Therefore, three quotes can always be reliably used as an escape.However, in understanding it, you may note that the four quotes at the end can be reduced to a mere two since it technically is adding another unnecessary empty double-quoted group.
Here are a few examples to close it off:
program a b REM sends (a) and (b)
program """a""" REM sends ("a")
program """a b""" REM sends ("a) and (b")
program """"Hello,""" Mike said." REM sends ("Hello," Mike said.)
program ""a""b""c""d"" REM sends (abcd) since the "" groups wrap nothing
program "hello to """quotes"" REM sends (hello to "quotes")
program """"hello world"" REM sends ("hello world")
program """hello" world"" REM sends ("hello world")
program """hello "world"" REM sends ("hello) and (world")
program "hello ""world""" REM sends (hello "world")
program "hello """world"" REM sends (hello "world")
Final note: I did not read any of this from any tutorial - I came up with all of it by experimenting. Therefore, my explanation may not be true internally. Nonetheless all the examples above evaluate as given, thus validating (but not proving) my theory.
I tested this on Windows 7, 64bit using only *.exe calls with parameter passing (not *.bat, but I would suppose it works the same).
I cannot quickly reproduce the symptoms: if I try myscript '"test"' with a batch file myscript.bat containing just #echo.%1 or even #echo.%~1, I get all quotes: '"test"'
Perhaps you can try the escape character ^ like this: myscript '^"test^"'?
Try this:
myscript """test"""
"" escape to a single " in the parameter.
The 2nd document quoted by Peter Mortensen in his comment on the answer of Codesmith made things much clearer for me. That document was written by windowsinspired.com. The link repeated: A Better Way To Understand Quoting and Escaping of Windows Command Line Arguments.
Some further trial and error leads to the following guideline:
Escape every double quote " with a caret ^. If you want other characters with special meaning to the Windows command shell (e.g., <, >, |, &) to be interpreted as regular characters instead, then escape them with a caret, too.
If you want your program foo to receive the command line text "a\"b c" > d and redirect its output to file out.txt, then start your program as follows from the Windows command shell:
foo ^"a\^"b c^" ^> d > out.txt
If foo interprets \" as a literal double quote and expects unescaped double quotes to delimit arguments that include whitespace, then foo interprets the command as specifying one argument a"b c, one argument >, and one argument d.
If instead foo interprets a doubled double quote "" as a literal double quote, then start your program as
foo ^"a^"^"b c^" ^> d > out.txt
The key insight from the quoted document is that, to the Windows command shell, an unescaped double quote triggers switching between two possible states.
Some further trial and error implies that in the initial state, redirection (to a file or pipe) is recognized and a caret ^ escapes a double quote and the caret is removed from the input. In the other state, redirection is not recognized and a caret does not escape a double quote and isn't removed. Let's refer to these states as 'outside' and 'inside', respectively.
If you want to redirect the output of your command, then the command shell must be in the outside state when it reaches the redirection, so there must be an even number of unescaped (by caret) double quotes preceding the redirection. foo "a\"b " > out.txt won't work -- the command shell passes the entire "a\"b " > out.txt to foo as its combined command line arguments, instead of passing only "a\"b " and redirecting the output to out.txt.
foo "a\^"b " > out.txt won't work, either, because the caret ^ is encountered in the inside state where it is an ordinary character and not an escape character, so "a\^"b " > out.txt gets passed to foo.
The only way that (hopefully) always works is to keep the command shell always in the outside state, because then redirection works.
If you don't need redirection (or other characters with special meaning to the command shell), then you can do without the carets. If foo interprets \" as a literal double quote, then you can call it as
foo "a\"b c"
Then foo receives "a\"b c" as its combined arguments text and can interpret it as a single argument equal to a"b c.
Now -- finally -- to the original question. myscript '"test"' called from the Windows command shell passes '"test"' to myscript. Apparently myscript interprets the single and double quotes as argument delimiters and removes them. You need to figure out what myscript accepts as a literal double quote and then specify that in your command, using ^ to escape any characters that have special meaning to the Windows command shell. Given that myscript is also available on Unix, perhaps \" does the trick. Try
myscript \^"test\^"
or, if you don't need redirection,
myscript \"test\"
I'm calling powershell from cmd, and passing quotes and neither escapes here worked. The grave accent worked to escape double quotes on this Win 10 surface pro.
>powershell.exe "echo la`"" >> test
>type test
la"
Below are outputs I got for other characters to escape a double quote:
la\
la^
la
la~
Using another quote to escape a quote resulted in no quotes.
As you can see, the characters themselves got typed, but didn't escape the double quotes.
Maybe you came here, because you wonder how to escape quotes that you need in the command that you pass to /c on cmd.exe? Well you don't:
CMD /c "MKDIR "foo bar""
will execute
MKDIR "foo bar"
which is really a behavior that I did not expect in the first glance.