Expanding an argument within single quotes

I’m trying to create a function that executes the following

composer config repositories.foo ‘{"type": "path", "url": "/b/foo-bundle"}’

I’m starting with

lb() { composer config repositories.$1 '{"type": "path", "url": "/b/$1-bundle" }' ; }
lb foo

But because the argument (a json string) is inside single quotes, it’s generate $1-bundle instead of foo-bundle.

I’m sure there’s a way to escape this, but I can’t seem to hack my way through it.

Thanks.

Asked By: Tac Tacelosky

||

There’s nothing you can do to make the shell expand $1 inside single-quotes, single-quoting is that strong. You need $1 to be outside of single-quotes. You can close the single-quoting, double-quote $1 and open single-quoting again:

… '{"type": "path", "url": "/b/'"$1"'-bundle" }'
#  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^      ^^^^^^^^^^  single-quoted, will be passed as-is
#                                ^^              double-quoted, will be expanded
# ^                            ^^  ^^          ^ quote removal will remove these

You should also double-quote $1 that appears elsewhere (repositories.$1).

Answered By: Kamil Maciorowski

Rather than literally embed that contents the $1 into the json string which you could do with:

lb() {
  composer config "repositories.$1" '
    {
      "type": "path",
      "url": "/b/'"$1"'-bundle"
    }'
}

You could also get jq to generate the json using the proper encoding with:

lb() {
  composer config "repositories.$1" "$(
    URL="/b/$1-bundle" jq -cn '
      {
        "type": "path",
        "url": $ENV.URL
      }')"
}

That way, it works correctly even if $1 contains characters that are special in the JSON syntax (not that it should happen for URLs).

Beware that jq transforms bytes that can’t be part of UTF-8 characters to (U+FFFD the replacement character). JSON strings can’t hold arbitrary sequence of bytes, only UTF-8 encoded characters.

In a URL anyway, those bytes should be encoded as %XX. jq does have a @uri operator to URI-encode a string so you could do:

lb() {
  composer config "repositories.$1" "$(
    URL="/b/$1-bundle" jq -cn '
      {
        "type": "path",
        "url": ($ENV.URL | @uri)
      }')"
}

However, unfortunately, that would apply after the translation to , so wouldn’t let you properly URI-encode any byte sequence (like the ones that can’t be decoded as UTF-8).

You’d need the URI encoding to be done before jq. If using ksh93 instead of bash, that could be done with its builtin printf for instance:

function lb {
  typeset -x URL=${
    LC_ALL=C printf '%(url)q' "/b/$1-bundle"
  }
  composer config "repositories.$1" "${
    jq -cn '
      {
        "type": "path",
        "url": $ENV.URL
      }'
  }"
}

Or you could use proper programming languages like perl or ruby that come with proper URI / JSON manipulation modules.

Beware ksh93‘s printf, like jq‘s @uri encode / as %2F which may or may not be desirable.

Answered By: Stéphane Chazelas
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.