7

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.
Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197
  • 1
    Hmm, escape sequence `\"` is successfully interpreted as `"` for me on Linux. According to [execute_process documentation](https://cmake.org/cmake/help/v3.0/command/execute_process.html), `CMake executes the child process using operating system APIs directly. All arguments are passed VERBATIM to the child process. No intermediate shell is used`, so this behaviour shouldn't differ on different OS. What **exact** string do your want to find, and what exact escaping do you tried? (At least, with `\"` escaping, character `\\` should be consumed by CMake). – Tsyvarev Jan 20 '16 at 20:22
  • 1
    Oh, `COMMAND find \"ell\" /c` should work. Taking string from CMake variable is another story: `set(MyArgument "ell")`, `COMMAND find "\"${MyArgument}\"" /c` (outer quotes are optional; they are required only if your string contains spaces). Storing quotes in the variable itself is a bit more complex.. but are you sure you want that? – Tsyvarev Jan 21 '16 at 10:51
  • 1
    Really weird: character "\" should be consumed by CMake interpreter and shouldn't be passed to the command, but "Access denied - \"... Looks like CMake has tried to escape things (which it shouldn't do), and does that incorrectly. What CMake version do you use? – Tsyvarev Jan 21 '16 at 11:28
  • 1
    BTW, using `COMMAND cmd /c echo ` you can see exact parameters, passed to the command. Interesting, what it would print in the last case. – Tsyvarev Jan 21 '16 at 17:39
  • Probably one has to have a look into the sources of CMake. Or file a bug report. – usr1234567 Jan 22 '16 at 13:10
  • (from bugreport you refers) `The CreateProcess API on windows does not handle individual arguments but rather requires a single continuous string for the entire command.` - this explains a lot. **But!** `find` command correctly parses quotes given in the windows command line. So there should be a way to pass them via `CreateProcess` too. Looks like a bug in CMake, but it differs from one you refer (`execute_process` calls `CreateProcess` directly, but `add_custom_command`, described in the bugreport, runs shell). – Tsyvarev Jan 25 '16 at 10:03
  • If you are familiar with Windows programming, you may try to create example on C(or similar) which calls `find` via `CreateProcess` and pass quoted arguments to it. If you succeed with it, then you can fill new bug for CMake. – Tsyvarev Jan 25 '16 at 10:10
  • @Tsyvarev I have posted an answer with a working workaround suggested by CMake developpers. – Antonio Jan 25 '16 at 15:31

1 Answers1

0

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.

Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197