I have a bash script that generates a program incantation (docker run ... with a bunch of arguments) and prints it to standard output. I want to run the output from powershell, with the optional possibility to tack on other arguments afterwards.
I've tried
D:\> & $(wsl ./my-bash-script.sh) some more args
but I get the error
The term 'docker run --rm -ti [<redacted>]' is not recognized as the name of cmdlet, function, script file, or operable program. Check the spelling of the name, or ifa path was included, verify that the path is correct and try again.
Apparently, & interprets the entire output as a single command, when in fact it's only the first word of the output that is a command; the rest is arguments.
Is there a way to do what I want?
Yes, & only supports a command name or path as its first argument - you cannot include arguments.
What you need is Invoke-Expression:
Invoke-Expression "$(wsl ./my-bash-script.sh) some more args"
Note: You'd only need & inside the string if the .sh script output a command with a quoted executable path.
Also note that PowerShell will not always interpret a given command line the same way as a POSIX-like shell (such as Bash), given that it recognizes additional metacharacters at the start of arguments; notably, arguments prefixed with # or { will not be taken as literals by PowerShell.
As an aside: While this is a legitimate use case for Invoke-Expression - assuming you trust the output from your .sh script - Invoke-Expression should generally be avoided.
Related
Ex:
cmd /C start C:\Users\Bob Builder\Desktop\New Folder\test.exe
I'm trying to use cmd to start a file but since there are spaces in the path, cmd is throwing an error after Bob.
Error:
"Windows cannot find C:\Users\Bob. Make sure you typed the name
correctly, then try again."
The system cannot find the file C:\Users\Bob.
Its simply failing to accept the spaces. It's driving me crazy because I'm spoiled with C# working out of the box. I don't know much about this, I have been spending way too much time trying to figure this out. Some help would be greatly appreciated.
In order for a path that contains spaces to be recognized as a single path (argument), it must be quoted.
In order for an executable to execute in the current console window, synchronously, with its streams connected to the calling shell, it must be invoked directly, not via start.
Direct invocation from cmd.exe (only "..." quoting supported):
"C:\Users\Bob Builder\Desktop\New Folder\test.exe"
From PowerShell:
& 'C:\Users\Bob Builder\Desktop\New Folder\test.exe'
Note:
PowerShell also supports '...' strings (single-quoted), which are verbatim strings that are preferable to "..." (double-quoted) ones if you do not require expansion of variables (string interpolation) - see the conceptual about_Quoting_Rules help topic.
For syntactic reasons, PowerShell requires the use of &, the call operator to invoke commands that are quoted and/or contain variable references - see this answer for details.
By contrast, use start in cmd.exe / Start-Process in PowerShell (whose built-in alias is also start) to launch an executable in a new window (on Windows), asynchronously, with no (direct) ability to capture the launched executable's output:
From cmd.exe:
start "title" "C:\Users\Bob Builder\Desktop\New Folder\test.exe"
Note:
Specifying "title" - i.e. a self-chosen (console) window title - is required for syntactic reasons in this case: without it, the double-quoted path itself would be interpreted as the window title, and the - implied - executable to launch would be another cmd.exe instance.
Note that if you launch a GUI application this way, the title argument is irrelevant, because no new console window is created.
Conversely, if you launch a console application specified by double-quoted path and therefore must use a title argument, note that "" will result in the new window having no title.
From PowerShell (parameter -FilePath is positionally implied):
Start-Process 'C:\Users\Bob Builder\Desktop\New Folder\test.exe'
Note:
Start-Process does not support specifying a window title, so you may want to call cmd.exe's internal start command for that (or other features not supported by Start-Process, such as specifying the process priority).
To work around quoting problems, invoke cmd.exe's start from PowerShell by passing the entire start command as a single string to cmd /c:
cmd /c 'start "title" "C:\Users\Bob Builder\Desktop\New Folder\test.exe"'
cmd /C start "C:\Users\Bob Builder\Desktop\New Folder\test.exe"
Quotes are your friend. Sometimes even double quotes are too!
Seems like cmd won't work for me. Powershell worked with this script:
$env:Path += ";C:\Users\Bob Builder\Desktop\New Folder\"
test.exe
I am trying to run a Bash command where an alias exists in PowerShell Core.
I want to clear the bash history. Example code below:
# Launch PowerShell core on Linux
pwsh
# Attempt 1
history -c
Get-History: Missing an argument for parameter 'Count'. Specify a parameter of type 'System.Int32' and try again.
# Attempt 2
bash history -c
/usr/bin/bash: history: No such file or directory
# Attempt 3
& "history -c"
&: The term 'history -c' is not recognized as the name of a cmdlet, function, script file, or operable program.
It seems the issue is related to history being an alias for Get-History - is there a way to run Bash commands in PowerShell core with an alias?
history is a Bash builtin, i.e. an internal command that can only be invoked from inside a Bash session; thus, by definition you cannot invoke it directly from PowerShell.
In PowerShell history is an alias of PowerShell's own Get-History cmdlet, where -c references the -Count parameter, which requires an argument (the number of history entries to retrieve).
Unfortunately, Clear-History is not enough to clear PowerShell's session history as of PowerShell 7.2, because it only clear's one history (PowerShell's own), not also the one provided by the PSReadLine module used for command-line editing by default - see this answer.
Your attempt to call bash explicitly with your command - bash history -c - is syntactically flawed (see bottom section).
However, even fixing the syntax problem - bash -c 'history -c' - does not clear Bash's history - it seemingly has no effect (and adding the -i option doesn't help) - I don't know why.
The workaround is to remove the file that underlies Bash's (persisted) command history directly:
if (Test-Path $HOME\.bash_history) { Remove-Item -Force $HOME\.bash_history }
To answer the general question implied by the post's title:
To pass a command with arguments to bash for execution, pass it to bash -c, as a single string; e.g.:
bash -c 'date +%s'
Without -c, the first argument would be interpreted as the name or path of a script file.
Note that any additional arguments following the first -c argument would become the arguments to the first argument; that is, the first argument acts as a mini-script that can receive arguments the way scripts usually do, via $1, ...:
# Note: the second argument, "-", becomes $0 in Bash terms,
# i.e. the name of the script
PS> bash -c 'echo $0; echo arg count: $#' self one two
self
arg count: 2
I wish to run the command:
mocha -i -g 'database|network|skip'
With the pipe being part of the arguments to mocha. However powershell thinks 'network' is a program that I am trying to pipe to:
network : The term 'network' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path
was included, verify that the path is correct and try again.
Some research mentioned the --% operator to stop Powershell parsing
STOP PARSING: --%
The stop-parsing symbol (--%), introduced in Windows PowerShell 3.0,
directs Windows PowerShell to refrain from interpreting input as
Windows PowerShell commands or expressions.
however running:
mocha --% -i -g 'database|network|skip'
Still gives the same error. Which makes sense, since:
The stop-parsing symbol is effective only until the next newline or
pipeline character.
How can I run a command with a pipe symbol in powershell?
Edit: Image attached for person who refuses to believe me even after issue has been replicated, and answered by two people:
Using an example with find you will get similar results.
find /c "this|that|andtheotherthing" C:\temp\EventCombMT.txt
find : FIND: Parameter format not correct
At line:1 char:1
+ find /c "this|that|andtheotherthing" C:\temp\EventCombMT.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (FIND: Parameter format not correct:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Pretty sure this is because the quotes are consumed by the PowerShell interpreter and that leave your mocha command with an unquoted string. Doubling up the quotes is another was to prevent this.
find /c '"this|that|andtheotherthing"' C:\temp\EventCombMT.txt
It seems this is not the case with mocha? In comments it was determined that we need to reverse the quote set from that seen in the find examples.
There are better ways to call external commands as you have seen in your linked post. Like using the call operator and a hashtable. You would still have to address the quotes though. You could escape a set of double quotes as well to get a similar effect.
find /c "`"this|that|andtheotherthing`"" C:\temp\EventCombMT.txt
For this to be correct though it does not really match the error you are getting. While I am correct about a solution I might be wrong about my interpretation of the issue. PowerShell should not care about the pipe character regardless of how it is quoted. That is what the quotes are for.
Thanks to PowerShell and external commands done right. You can quote the pipe character to stop powershell treating it as a pipe. In this case:
mocha -g 'network"|"database"|"skip'
Works perfectly. As #matt has since mentioned, you can also run the (much neater):
mocha -i -g '"database|network|skip"'
I have to run the following command remotely on another server, the arguments are prefixed with !=:
wdrspc.exe !=BATCHTEST1,LGTY_PLAN_01
This works (but the exe fails because I'm not passing in any arguments):
psexec \\kiklogiappsd "c:\Program Files (x86)\Logility\SPC8.0\wdrspc.exe"
This does not work (psexec says system cannot find the file specified):
psexec \\kiklogiappsd "c:\Program Files (x86)\Logility\SPC8.0\wdrspc.exe !=BATCHTEST1,LGTY_PLAN_01"
I'm stumped, is it the != syntax throwing off psexec?
The correct command line would be:
psexec \\kiklogiappsd "c:\Program Files (x86)\Logility\SPC8.0\wdrspc.exe" !=BATCHTEST1,LGTY_PLAN_01
The program arguments should not be in quotes with the program path, or they'll be interpreted as part of the program path. When you quote a space in a command line argument, that tells the shell that the space is part of the argument, rather than separating two arguments. That's why you quote anything in C:\Program Files (x86). But you want an argument-separating space between the program path and its arguments.
I can't seem to call this executable correctly in my psake deploy script.
If I do this:
exec { "$ArchiverOutputDir\NServiceBus.Host.exe /install" }
It simply outputs this (and is clearly not calling the executable - just outputting the value of that expression):
c:\ReloDotNet2_ServiceEndpoints\Archiver\NServiceBus.Host.exe /install
But if I do this:
exec { c:\ReloDotNet2_ServiceEndpoints\Archiver\NServiceBus.Host.exe /install }
I get the expected output from the executable.
How do I correctly call an executable with a variable in the path to the executable in psake? If this is actually a PowerShell issue, please feel free to correct the question to reflect that insight.
I
Classic PowerShell issue. Try this instead:
exec { & "$ArchiverOutputDir\NServiceBus.Host.exe" /install }
PowerShell not only executes commands, it also evaluates expressions e.g.:
C:\PS> 2 + 2
4
C:\PS> "hello world"
hello world
What you have given to PowerShell at the beginning of a pipeline is a string expression which it faithfully evaluates and prints to the console. By using the call operator &, you're telling PowerShell that the following thing is either the name of a command (in a string) to be executed or a scriptblock to be executed. Technically you could also use . "some-command-name-or-path". The only difference is that for PowerShell commands, & creates a new scope to execute the command in and . doesn't. For external exes it makes no difference as far as I can tell which one you use but & is typically used.