I want to compile an OpenCV C/C (MSYS2) program on Windows using solely the CLI. The GNU Bash way to do this is (which runs fine on MSYS2 MINGW64 Shell):
g main.cc -o main `pkg-config --cflags --libs opencv4`
Bash perfectly recognizes the backticks as a subexpression, however PowerShell doesn't:
> g main.cc -o main `pkg-config --cflags --libs opencv4`
g .exe: error: unrecognized command-line option '--cflags'
g .exe: error: unrecognized command-line option '--libs'
I've done some research, and the equivalent on PowerShell would be
g main.cc -o main $(pkg-config --cflags --libs opencv4)
Despite $(pkg-config --cflags --libs opencv4)
being the right way to write a subexpression in PowerShell as an echo command shows it produces the right output (in this case compiler include and linker flags I need):
> echo $(pkg-config --cflags --libs opencv4)
-IC:/msys64/mingw64/bin/../include/opencv4 -LC:/msys64/mingw64/bin/../lib -lopencv_gapi -lopencv_stitching -lopencv_alphamat -lopencv_aruco -lopencv_barcode -lopencv_bgsegm -lopencv_ccalib -lopencv_cvv -lopencv_dnn_objdetect -lopencv_dnn_superres -lopencv_dpm (...)
It simply won't work:
> g main.cc -o main $(pkg-config --cflags --libs opencv4)
main.cc:1:10: fatal error: opencv2/imgcodecs.hpp: No such file or directory
1 | #include <opencv2/imgcodecs.hpp>
| ^~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
Therefore, I need to use a verbose workaround to compile my file:
> g main.cc -o main -IC:/msys64/mingw64/bin/../include/opencv4 -LC:/msys64/mingw64/bin/../lib -lopencv_gapi -lopencv_stitching -lopencv_alphamat -lopencv_aruco -lopencv_barcode -lopencv_bgsegm -lopencv_ccalib -lopencv_cvv -lopencv_dnn_objdetect -lopencv_dnn_superres -lopencv_dpm -lopencv_face -lopencv_freetype -lopencv_fuzzy -lopencv_hdf -lopencv_hfs -lopencv_img_hash (...)
My PowerShell version:
> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.22621.608
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.22621.608
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
My question is: how can I make this particular subexpression work in PowerShell so that I don't have to resort to the verbose fallback of copying and pasting the output of
pkg-config --cflags --libs opencv4
after g main.cc -o main
?
CodePudding user response:
The equivalent of your bash
command is:
g main.cc -o main (-split (pkg-config --cflags --libs opencv4))
That is:
(pkg-config --cflags --libs opencv4)
executes yourpkg-config
call, and, by enclosing it in(...)
, the grouping operator, its output can participate in a larger expression.Using the unary form of
-split
, the string splitting operator, then splits thepkg-config
call's output into an array of whitespace-separated tokens, which is (part of) the equivalent of whatbash
implicitly does via its shell expansion called word splitting.Passing an array as an argument to an external program in PowerShell causes its elements to be passed as individual arguments.
Note:
Using
$(...)
, PowerShell's subexpression operator does not work in this case, because its output would be passed as a whole, as a single argument to the target executable (or, in the case of an external-program call that outputs multiple lines, each line in full would be passed as an argument).In general, where
bash
requires$(...)
(or its legacy form`...`
), in PowerShell(...)
is usually sufficient (and in assignments not even it is needed, e.g.,$var = Get-Date
); the primary use case for$(...)
in PowerShell is inside"..."
, i.e. inside expandable (double-quoted) stringsMore fundamentally, PowerShell has no equivalent of
bash
's shell expansions:Notably, parameter expansions (e.g.
${var//foo/bar}
) are not supported, and require use of expressions instead (e.g.($var -replace 'foo', 'bar')
)Unquoted arguments are not generally subject to special interpretation, except if they contain metacharacters, which, if they are to be used verbatim, for syntactic reasons then must either be individually
`
-escaped (`
, the so-called backtick, is PowerShell's escape character), or require the whole argument to be enclosed in quotes.It is up to each target command to interpret special characters such as an initial
~
as referring to the home directory, or to interpret arguments such as*.txt
as wildcard patterns - whether such an argument was passed in quotes or not.Variable references (e.g.,
$var
) and simple expressions based on them (e.g,$var.Property
), (grouping) expressions (e.g. ((1 2)
), as well as subexpressions ($(...)
) and array subexpressions (@(...)
) can be used as-is as arguments, even if what they evaluate to contains spaces - in the case of calling external programs, on Windows, PowerShell will double-quote the result on demand, behind the scenes; as stated, multiple outputs are each passed as an argument.- See this answer for more information.