12

I have a function in a bash script that looks like this (simplified):

# Usage: f URL [PARAMETER]...
f() {
    local URL="$1"; shift

    local PARAMS
    for arg in "$@"; do
        PARAMS="${PARAMS}&${arg}"
    done
    PARAMS="${PARAMS#'&'}"

    local DATA_OPTION
    [ -z "${PARAMS}" ] || DATA_OPTION='--data'

    curl -o - "${DATA_OPTION}" "${PARAMS}" "${URL}"
}

It can be called like f http://example.com/resource or f http://example.com/resource p1=v1 p2=v2. The problem is when DATA_OPTION and PARAMS are empty. In this case, Bash passes two empty arguments to curl, which are then recognised as URLs by curl and produce the following ugly message:

curl: (3) <url> malformed
curl: (3) <url> malformed

I temporarily solved the problem using an if/else so that DATA_OPTION and PARAMS are not passed at all:

    [..]

    if [ -z "${PARAMS}" ]; then
        curl -o - --data "${PARAMS}" "${URL}"
    else
        curl -o - "${URL}"
    fi
}

but this seems ugly to me. Is there a more elegant solution? Note that the quotes around PARAMS are needed because some parameter values may contain spaces.

konikos
  • 376
  • 2
  • 9

3 Answers3

21

You can actually solve this cleanly with the "use alternate value" option (:+) in a parameter expansion:

curl -o - ${PARAMS:+"--data" "$PARAMS"} "${URL}"

If PARAMS is empty or undefined, the whole ${PARAMS:+"--data" "$PARAMS"} thing evaluates to the empty string, and since it's not double-quoted, word splitting removes it entirely. On the other hand, if PARAMS is nonblank, it gets effectively replaced by "--data" "$PARAMS", which is exactly what you want.

[EDIT] This will work in most POSIX-ish shells, but not zsh, since zsh doesn't word-split expansions even if they're unquoted. If you want this to work in zsh (as well as bash, dash, ksh, etc) you need to make the option label a separate conditional item:

curl -o - ${PARAMS:+"--data"} ${PARAMS:+"$PARAMS"} "${URL}"
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • 1
    Why quote the `--data` in there, why not simply `${PARAMS:+--data "$PARAMS"}` – janos Nov 30 '13 at 23:57
  • 4
    @janos: either will work, but I prefer the quoted version for 2 reasons: First, when writing shell scripts, it's safer to habitually quote things unless there's a reason not to than to try to remember when you can get away without quotes. Second, when reading a script with something like this, it's much easier to tell where the parameter expansion option (`:+`) ends and the argument (`--data`) begins if there's a quote between them (a space would also work, but see the first reason). – Gordon Davisson Dec 01 '13 at 06:02
1

I think this is a good combination of lazy and elegant:

curl -o - --data "&$PARAMS" "$URL"

That's right, there is a useless & there. The thing is it doesn't hurt anyone, it's short, and it should work for both of your cases, whether you have anything in PARAMS or not.

janos
  • 120,954
  • 29
  • 226
  • 236
0

Using bash array for building command

I often use arrays for this!

Remark, using IFS='&' for merging arguments with & is a lot quicker and simplier!

You function will become:

f() {
    local cmdArgs=(-o -) Url="$1" IFS='&'
    shift
    [[ $* ]] && cmdArgs+=(--data "$*")
    curl "${cmdArgs[@]}" $Url
}

Note: Avoid using capitalised variables names! Prefer camel case of lower case!

F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137