2

I was working on a bash function to help me to compile packages:

make_package() {
        local PACKAGE_NAME=$1
        local PACKAGE_VERSION=$2
        local PACKAGE_INSTALL_DIR=$3
        local PACKAGE_CONFIGURE_PARAMETERS=$4

        echo "Make ${PACKAGE_NAME}-${PACKAGE_VERSION}"

        local BUILD_DIRECTORY=build/${PACKAGE_NAME}-${PACKAGE_VERSION}_build
        if [ -d ${BUILD_DIRECTORY} ]; then
                rm -rf ${BUILD_DIRECTORY}/*
        else
                mkdir -p ${BUILD_DIRECTORY}
        fi

        cd ${BUILD_DIRECTORY} && \
        ../../${PACKAGE_NAME}-${PACKAGE_VERSION}/configure $4 --prefix=`pwd`/../$3 && \
        make && \
        make install && \
        cd ../../
        if [ $? -ne 0 ]; then
                echo "Make ${PACKAGE_NAME} error!"
                exit 1
        fi
}

It was working fine for things like

make_package "popt" "1.16" "arm_sdk" "--host=arm-linux-gnueabihf"
make_package "ncurses" "5.6" "arm_sdk" "--host=arm-linux-gnueabihf --without-ada"

But I'm stuck at:

make_package "OpenIPMI" "2.0.28" "--host=arm-linux-gnueabihf LDFLAGS=\"-L`pwd`/build/arm_sdk/lib\" CPPFLAGS=\"-I`pwd`/build/arm_sdk/include -I`pwd`/build/arm_sdk/include/ncurses\""

The problem comes from the:

CPPFLAGS=\"-I`pwd`/build/arm_sdk/include -I`pwd`/build/arm_sdk/include/ncurses\"

Because of this I get an error:

configure: error: unrecognized option: `-I/home/me/build/arm_sdk/include/ncurses"'

So I'm stuck here... Is there a way to pass such configure arguments as a parameter to a function in bash?

kostr22
  • 153
  • 6

1 Answers1

1

The shell parses quotes (and escapes etc) before expanding parameters (and variables etc). As a result, putting quotes in the value of a parameter, as you're doing in your fourth argument, doesn't do anything useful. See BashFAQ #50: "I'm trying to put a command in a variable, but the complex cases always fail!", and many previous questions.

Usually, people are trying to store multiple arguments in a variable, and the answer is to use an array; but you can't pass an array as an argument, so that won't work for you. What you can do instead is pass multiple arguments, so $4 is the first package configuration argument, $5 is the second, etc. So you'd run it something like this:

make_package "OpenIPMI" "2.0.28" --host=arm-linux-gnueabihf LDFLAGS="-L$PWD/build/arm_sdk/lib" CPPFLAGS="-I$PWD/build/arm_sdk/include -I$PWD/build/arm_sdk/include/ncurses"

Note that I replaced use of the pwd command with references to the variable $PWD -- this doesn't require creating a new process each time, so it's more efficient. If you do want to use the pwd command, I recommend using $(pwd) instead of backticks, because it's generally more readable and doesn't have the same syntactic weirdnesses that backticks have.

To extract and use the arguments in your function, use something like this:

# Store arguments starting at $4 in an array:
local package_configure_parameters=("${@:4}")
...

...
../../"${package_name}-${package_version}/configure" "${package_configure_parameters[@]}" --prefix="$PWD/../$package_install_dir" &&
...

See all those parentheses, brackets, quotes etc I used in defining and referencing the array? Those are all strictly required for it to work right. I also added quotes around the other variable references, which probably isn't necessary, but is a good idea. shellcheck.net is good at pointing out things like this. And I lowercased the variable names, which is generally a good practice to avoid tripping over any of the all-caps names with special functions (like $PWD). Lower- or mixed-case names are safer unless you want some special meaning.

Arrays are a bash (and zsh and...) feature, but not available in all shells. For completeness, if you needed to do this in a portable or non-bash script, you could capture $1 through $3 as variables (as you already do), then use shift to remove them from the function's argument list and use "$@" to get the config parameters:

make_package() {
    local package_name="$1"
    local package_version="$2"
    local package_install_dir="$3"
    shift 3    # Remove everything but the package config params from the arg list

    ...
    ../../"${package_name}-${package_version}/configure" "$@" --prefix="$PWD/../$package_install_dir" &&
    ...
Gordon Davisson
  • 34,084
  • 5
  • 66
  • 70
  • Thanks for a great answer! But can you explain for me one more thing. In my problem I had only one complex parameter that was causing troubles for me. And you suggest to handle all function arguments as usual and this complex parameter as "the rest of arguments". But what should I do in a situation where I have more than one such complex parameter? For example "package_configure_parameters" and "package_make_parameters"? – kostr22 Apr 16 '20 at 21:39
  • @kostr22 That'd be a lot more complicated. One way would be to require each of one (or both) type of argument to be marked with a certain `-`option prefix to distinguish it. See the [grep](https://linux.die.net/man/1/grep) command for example -- it can take both multiple search patterns and multiple filenames to search, but if you want to supply multiple patterns you need to prefix each one with `-e` (e.g. `grep -e pattern1 -e pattern2 -e pattern3 file1 file2 file3`). – Gordon Davisson Apr 16 '20 at 23:03