CMake: How to call execute_process with a double q

2019-06-21 20:46发布

问题:

I will like to call the command find from within a execute_process.

The format of the find command is:

find [/v] [/c] [/n] [/i] [/off[line]] "<String>" [[<Drive>:][<Path>]<FileName>[...]]

So, the string has to be double quoted. However, if in cmake I do:

execute_process(COMMAND <firstCommandPipingSomethingToFind> 
                COMMAND find "<myString>" /c
                OUTPUT_VARIABLE MY_COUNT
                OUTPUT_STRIP_TRAILING_WHITESPACE)

I get an error FIND: Parameter format not correct.
If I escape the double quotes with \, I get an error Access denied - \.
Also using double escaping \\ or double double quotes "" does not help.

So my question is:
Is there a way to escape the double quotes when calling execute_process so that find receives correctly its argument?

I could use findstr, which doesn't need the double quotes; however it doesn't provide a counting feature. I could use find by calling find /v /c "", but again, I need double quotes! And I would like to count the output lines outside cmake, and store directly the result in MY_COUNT variable.

Note: I am using CMake 3.4.1


To reproduce the problem you can use the following code, in which cmake -E echo is used to feed a string to findstr and find:

#This works
execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND findstr ell OUTPUT_VARIABLE DUMMY)
message (STATUS "0) DUMMY=${DUMMY}")

#All of the following don't work
set(MyCommand "find \"ell\" /c")
execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND "${MyCommand}" OUTPUT_VARIABLE DUMMY)
message (STATUS MyCommand=${MyCommand})
message (STATUS "1) DUMMY=${DUMMY}")

execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND ${MyCommand} OUTPUT_VARIABLE DUMMY)
message (STATUS "2) DUMMY=${DUMMY}")

execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND MyCommand OUTPUT_VARIABLE DUMMY)
message (STATUS "3) DUMMY=${DUMMY}")

execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND "find \"ell\" /c" OUTPUT_VARIABLE DUMMY)
message (STATUS "4) DUMMY=${DUMMY}")

Which outputs:

0) DUMMY=hello

MyCommand=find "ell" /c
1) DUMMY=
2) DUMMY=
3) DUMMY=
4) DUMMY=

I also tried

set(MyArgument "\"ell\" /c")
message (STATUS MyArgument=${MyArgument})
execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND find "${MyArgument}" OUTPUT_VARIABLE DUMMY)
message (STATUS "1) DUMMY=${DUMMY}")

execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND find ${MyArgument} OUTPUT_VARIABLE DUMMY)
message (STATUS "2) DUMMY=${DUMMY}")

set(MyArgument "\"ell\"")
message (STATUS MyArgument=${MyArgument})
execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND find "${MyArgument}" /c OUTPUT_VARIABLE DUMMY)
message (STATUS "1) DUMMY=${DUMMY}")

execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND find ${MyArgument} /c OUTPUT_VARIABLE DUMMY)
message (STATUS "2) DUMMY=${DUMMY}")

Which outputs:

MyArgument="ell" /c
File not found - \ELL\

1) DUMMY=
File not found - \ELL\

2) DUMMY=
MyArgument="ell"
Access denied - \

1) DUMMY=
Access denied - \

2) DUMMY=
CMake Error at CMakeRules.cmake:404 (execute_process):
  execute_process given COMMAND argument with no value.
Call Stack (most recent call first):
  CMakeLists.txt:24 (include)

The problem persists also if a remove the /c option.


This:

execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello COMMAND find \"ell\" OUTPUT_VARIABLE DUMMY)
message (STATUS "DUMMY=${DUMMY}")

outputs this:

Access denied - \

DUMMY=

Following Tsyvarev suggestion to retrieve which string is effectively passed to the command line, this:

execute_process(COMMAND cmd /c echo \"ell\")
execute_process(COMMAND cmd /c echo "ell")
execute_process(COMMAND cmd /c echo ""ell"")

outputs this:

\"ell\"

ell

"" ell\"\"

With a warning for the third command:

Argument not separated from preceding token by whitespace.
This warning is for project developers.  Use -Wno-dev to suppress it.

回答1:

According to CMake developers,
Windows's find cannot be called directly by cmake from within execute_process, because of a limitation of the CreateProcess API and of the non-standard way find parses arguments.

It can be called indirectly though, by creating a temporary batch script.

My problem of counting occurrences of a string from the output of a program in Windows is solved in the following way:

file(WRITE ${CMAKE_BINARY_DIR}/countLines.bat [[@find /v /c "" %1]])
execute_process(COMMAND <firstCommandPipingSomethingToFind> COMMAND findstr <regular expression> COMMAND countLines.bat WORKING_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE N_MATCHES OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "N_MATCHES=${N_MATCHES}")
file(REMOVE ${CMAKE_BINARY_DIR}/countLines.bat)

The advantage of findstr in between is that it supports regular expressions. find is only used to count matching lines.

The @ in front of find is to prevent the command invocation within the script from ending up in the output variable.