2

I reviewed other answers but couldn't get a proper explanation of how to do this.

I have a string variable called id such that id='{"name":"john"}'. How can I get 2 variables out of this string like-

key="name"
value="john"

A detailed explanation would be appreciated as I want to understand string parsing in bash.

So far, I've removed the braces {} from the string-

id="$( echo "${id}" | tr -d {} )"

I can't include " in there as it throws an error. Also looking for something like id.split(":") in the end to get an array.

6
  • 3
    Can we warn you off of this tactic/track before it's too late? It appears like you have json or some other structured data there; perhaps a dedicated tool would be a safer way to get you where you're going?
    – Jeff Schaller
    Jul 24, 2019 at 18:41
  • I saw that jq is used by others, but is there no way I can parse this without a third party tool?
    – kev
    Jul 24, 2019 at 18:44
  • Are you bringing this data in from a file? Could there be more than one pair of key/values? What would a double-quote look like as part of the value?
    – Jeff Schaller
    Jul 24, 2019 at 18:45
  • 2
    @kev: Technically awk, sed, and grep are third party tools as well. If you are using json data there is absolutely no reason to not have the proper tools for working with it.
    – jesse_b
    Jul 24, 2019 at 18:47
  • This data is the result of a command that I executed in my script. It will always be in this format - 1 key and 1 value.
    – kev
    Jul 24, 2019 at 18:48

3 Answers 3

8

You literally asked to understand string parsing in bash so I'll write an answer with that in mind, even though it's the wrong solution for your problem. You can use bash itself to do what you want, if you have really clean data without special characters, where special is defined as anything outside [A-Za-z0-9 ]:

$ id='{"name":"john"}'
$ id="${id#*\{}"  # remove everything through the first '{'
$ echo $id
"name":"john"}
$ id="${id%\}*}"  # remove everything starting with the last '}'
$ echo $id
"name":"john"
$ name="${id%:*}" # take everything before the ':'
$ name="${name//\"/}"  # remove quotes
$ echo $name
name
$ value="${id#*:}" # take everything after the ':'
$ value="${value//\"/}" # remove quotes
$ echo $value
john

This is all described in "Parameter Expansion" in the bash manual. For example, ${parameter#word} which will Remove matching prefix pattern will remove the text word from the beginning of $parameter. Similarly, % removes the suffix. // replaces all occurences of a string with whatever comes after it (in the ${foo//\"/} example above, quotes (which have to be escaped, so appear as \") are replaced with the empty string). You have to perform each substitution by itself, though: you can't strip off the beginning and end of the string with one command.

You've also probably noticed that you need to escape special characters, like }, { and ". As long as you remember to get that right, you can write code like this pretty easily but, as simple as it is, it's trending towards being write-only code. When you come back to this code in a year or two to re-use it, you'll look at something like #*\{} and think to yourself, WTF does that even mean? and then just copy it blindly into a new project and then your code will break in a subtle way because it encounters special characters that you weren't expecting.

The examples above will break if your name-value pairs have special characters in them, like braces or escaped quotes or colons or probably other characters. So this will work fine for some quick-and-dirty scraping or the 80% use case, but you really shouldn't use it in production or any time you need to make sure it always works with any input.

Even without the echo statements in there to show you what's happening, you can see that this code is already longer than the examples in the other answer that show how to do it properly. So by not using a third-party tool you're giving yourself more code to write, which will take you longer to both write and debug, and you're also ending up with a solution that is less flexible and that will break when it encounters something unexpected.

1
  • 2
    this was quite informative - can't comprehend why people would downvote
    – kev
    Jul 26, 2019 at 13:47
3

Using jq:

id='{"name":"john"}'
key=$(jq -r keys[] <<<"$id")
value=$(jq -r .[] <<<"$id")

-r: With this option, if the filter's result is a string then it will be written directly to standard output rather than being formatted as a JSON string with quotes.

keys: The builtin function keys, when given an object, returns its keys in an array.


Using json:

id='{"name":"john"}'
key=$(json -ak <<<"$id")
value=$(json -a "$key" <<<"$id")

-a processes input as an array

-k returns key values

3
  • thank you for the explanation. works great. I reckon there's no way to parse a string without jq in bash scripts.
    – kev
    Jul 24, 2019 at 18:54
  • @kev: There are plenty of ways to parse strings but it's kind of unfair to call this a string and not json data.
    – jesse_b
    Jul 24, 2019 at 18:55
  • @kev JSON data can be encoded. To parse it, you would potentially have to decode it. Also, JSON does not care about whitespace (newlines etc.) between keys and values, so you would have to take that into account. This has already been done for you by the authors of the underlying JSON parser in jq.
    – Kusalananda
    Jul 24, 2019 at 19:34
1

Alternatively, you could use a walk-path based unix utility jtc:

id='{"name":"john"}'
bash $ key=$(jtc -w'[0]<>k' <<<"$id")
bash $ echo $key
"name"
bash $ value=$(jtc -w'[0]' <<<"$id")
bash $ echo $value
"john"
bash $ 

PS> Disclosure: I'm the creator of the jtc - shell cli tool for JSON operations

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .