0

(This is a more specific version of the problem discussed in bash - expand arguments from array containing double quotes .)

I want bash to call cmake with arguments from an array with double quotes which itself contain items from another array. Here is an example for clarification:

cxx_flags=()
cxx_flags+=(-fdiagnostics-color)
cxx_flags+=(-O3)

cmake_arguments=()
cmake_arguments+=(-DCMAKE_BUILD_TYPE=Release)
cmake_arguments+=("-DCMAKE_CXX_FLAGS=\"${cxx_flags[@]}\"")

The arguments shall be printed pretty like this:

$ echo "CMake arguments: ${cmake_arguments[@]}"
CMake arguments: -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fdiagnostics-color -O3"

Problem

And finally cmake should be called (this does not work!):

cmake .. "${cmake_arguments[@]}"

It expands to (as set -x produces):

cmake .. -DCMAKE_BUILD_TYPE=Release '-DCMAKE_CXX_FLAGS="-fdiagnostics-color' '-O3"'

Workaround

echo "cmake .. ${cmake_arguments[@]}" | source /dev/stdin

Expands to:

cmake .. -DCMAKE_BUILD_TYPE=Release '-DCMAKE_CXX_FLAGS=-fdiagnostics-color -O3'

That's okay but it seems like a hack. Is there a better solution?

Update

If you want to iterate over the array you should use one more variable (as randomir and Jeff Breadner suggested):

cxx_flags=()
cxx_flags+=(-fdiagnostics-color)
cxx_flags+=(-O3)
cxx_flags_string="${cxx_flags[@]}"

cmake_arguments=()
cmake_arguments+=(-DCMAKE_BUILD_TYPE=Release)
cmake_arguments+=("-DCMAKE_CXX_FLAGS=\"$cxx_flags_string\"")

The core problem remains (and the workaround still works) but you could iterate over cmake_arguments and see two items (as intended) instead of three (-DCMAKE_BUILD_TYPE=Release, -DCMAKE_CXX_FLAGS="-fdiagnostics-color and -O3"):

echo "cmake .. \\"
size=${#cmake_arguments[@]}
for ((i = 0; i < $size; ++i)); do
    if [[ $(($i + 1)) -eq $size ]]; then
        echo "    ${cmake_arguments[$i]}"
    else
        echo "    ${cmake_arguments[$i]} \\"
    fi
done

Prints:

cmake .. \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_CXX_FLAGS="-fdiagnostics-color -O3"
bibermann
  • 77
  • 1
  • 7

2 Answers2

1

You basically want cxx_flags array expanded into a single word.

This:

cxx_flags=()
cxx_flags+=(-fdiagnostics-color)
cxx_flags+=(-O3)

flags="${cxx_flags[@]}"

cmake_arguments=()
cmake_arguments+=(-DCMAKE_BUILD_TYPE=Release)
cmake_arguments+=(-DCMAKE_CXX_FLAGS="$flags")

will produce the output you want:

$ set -x
$ echo "${cmake_arguments[@]}"
+ echo -DCMAKE_BUILD_TYPE=Release '-DCMAKE_CXX_FLAGS=-fdiagnostics-color -O3'

So, to summarize, running:

cmake .. "${cmake_arguments[@]}"

with array expansion quoted, ensures each array element (cmake argument) is expanded as only one word (if it contains spaces, the shell won't print quotes around it, but the command executed will receive the whole string as a single argument). You can verify that with set -x.

If you need to print the complete command with arguments in a way that can be reused by copy/pasting, you can consider using printf with %q format specifier, which will quote the argument in a way that can be reused as shell input:

$ printf "cmake .. "; printf "%q " "${cmake_arguments[@]}"; printf "\n"
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS=-fdiagnostics-color\ -O3

Note the backslash which escapes the space.

randomir
  • 17,989
  • 1
  • 40
  • 55
  • This works, but then it's printed wrong: `-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS=-fdiagnostics-color -O3` (it seems like -O3 is an argument from cmake and it's not copy-pastable) – bibermann Jul 27 '17 at 05:44
  • I'm sorry, but I don't understand what's bothering you. Does it compile when written like this? You want it in addition, to print a copy-pastable output? To be used for what purpose? If you provide enough details, we'll solve this. :) – randomir Jul 27 '17 at 10:36
  • 1
    It compiles yes, but in my question I wrote it shall be printed pretty and I gave an example :) The output is used as a build instruction for humans. – bibermann Jul 27 '17 at 12:13
  • Ok, so, you want it compiling and printed for reuse in shell? Have a look at my update, does that help? – randomir Jul 27 '17 at 13:25
  • That's technically interesting but not as pretty as `-DCMAKE_CXX_FLAGS="-fdiagnostics-color -O3"`, one for example doesn't perceive -O3 belonging to the flags. Besides the esthetic downside it's unpractical if someone wants to copy or paste some arguments in or out from the cmd line. It's just not the way you write such sort of thing :/ but thanks anyway! – bibermann Jul 29 '17 at 09:04
1

It seems that there's another layer of parsing that has to happen before cmake is happy; the | source /dev/stdin handles this, but you could also just move your CXX flags through an additional variable:

#!/bin/bash -x 

cxx_flags=()
cxx_flags+=(-fdiagnostics-color)
cxx_flags+=(-O3)
CXX_FLAGS="${cxx_flags[@]}"

cmake_arguments=()
cmake_arguments+=(-DCMAKE_BUILD_TYPE=Release)
cmake_arguments+=("'-DCMAKE_CXX_FLAGS=${CXX_FLAGS}'")
CMAKE_ARGUMENTS="${cmake_arguments[@]}"

echo "CMake arguments: ${CMAKE_ARGUMENTS}"

returns:

+ cxx_flags=()
+ cxx_flags+=(-fdiagnostics-color)
+ cxx_flags+=(-O3)
+ CXX_FLAGS='-fdiagnostics-color -O3'
+ cmake_arguments=()
+ cmake_arguments+=(-DCMAKE_BUILD_TYPE=Release)
+ cmake_arguments+=("'-DCMAKE_CXX_FLAGS=${CXX_FLAGS}'")
+ CMAKE_ARGUMENTS='-DCMAKE_BUILD_TYPE=Release '\''-DCMAKE_CXX_FLAGS=-fdiagnostics-color -O3'\'''
+ echo 'CMake arguments: -DCMAKE_BUILD_TYPE=Release '\''-DCMAKE_CXX_FLAGS=-fdiagnostics-color -O3'\'''
CMake arguments: -DCMAKE_BUILD_TYPE=Release '-DCMAKE_CXX_FLAGS=-fdiagnostics-color -O3'

There is probably a cleaner solution still, but this is better than the | source /dev/stdin thing, I think.

Jeff Breadner
  • 1,366
  • 9
  • 19
  • It seems okay, but then g++ complains: `g++: error: unrecognized command line option ‘-fdiagnostics-color -O3’` – bibermann Jul 27 '17 at 05:38
  • Maybe use the same trick again, see my edit (add the CMAKE_ARGUMENTS variable to resolve the array) – Jeff Breadner Jul 27 '17 at 05:43
  • Then CMake sees `-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fdiagnostics-color -O3"` as one argument, but this are two. If I omit the double quotes in the call the flags are parsed as `'-DCMAKE_CXX_FLAGS="-fdiagnostics-color' '-O3"' ` – bibermann Jul 27 '17 at 06:01
  • I moved some quotes around, but this looks very similar to the answer that @randomir provided, which was rejected. – Jeff Breadner Jul 27 '17 at 06:10
  • The problem remains (a single argument for CMake). – bibermann Jul 27 '17 at 06:19
  • I'm somewhat hindered by my inability to test the solution directly, sorry. I'm afraid I'm out of ideas for this evening, at least you have the source /dev/stdin workaround. – Jeff Breadner Jul 27 '17 at 06:24
  • A comment in another thread suggests this kind of structure should work, but I've got no experience with maintaining build environments myself. -DCMAKE_CXX_FLAGS:="-D_GLIBCXX_USE_CXX11_ABI=0 -DTSI_OPENSSL_ALPN_SUPPORT=0" Note the := assignment style operator. I'm really gasping at straws here :) https://stackoverflow.com/questions/8564337/how-to-define-a-c-preprocessor-macro-through-the-command-line-with-cmake/10364240 – Jeff Breadner Jul 27 '17 at 06:39
  • Unfortunately this does not seem to help :( – bibermann Jul 27 '17 at 07:29