Steve Amerige
Steve Amerige

Reputation: 1499

jq: how do I update a value based on a substring match?

I've got a jq question. Given a file file.json containing:

[
  {
    "type": "A",
    "name": "name 1",
    "url": "http://domain.com/path/to/filenameA.zip"
  },
  {
    "type": "B",
    "name": "name 2",
    "url": "http://domain.com/otherpath/to/filenameB.zip"
  },
  {
    "type": "C",
    "name": "name 3",
    "url": "http://otherdomain.com/otherpath/to/filenameB.zip"
  }
]

I'm looking to create another file using jq with url modified only if the url's value matches some pattern. For example, I'd want to update any url matching the pattern:

http://otherdomain.com.*filenameB.*

to some fixed string such as:

http://yetanotherdomain.com/new/path/to/filenameC.tar.gz

with the resulting json:

[
  {
    "type": "A",
    "name": "name 1",
    "url": "http://domain.com/path/to/filenameA.zip"
  },
  {
    "type": "B",
    "name": "name 2",
    "url": "http://domain.com/otherpath/to/filenameB.zip"
  },
  {
    "type": "C",
    "name": "name 3",
    "url": "http://yetanotherdomain.com/new/path/to/filenameB.tar.gz"
  }
]

I haven't gotten far even on being able to find the url, let alone update it. This is as far as I've gotten (wrong results and doesn't help me with the update issue):

% cat file.json | jq -r '.[] | select(.url | index("filenameB")).url'
http://domain.com/otherpath/to/filenameB.zip
http://otherdomain.com/otherpath/to/filenameB.zip
%

Any ideas on how to get the path of the key that has a value matching a regex? And after that, how to update the key with some new string value? If there are multiple matches, all should be updated with the same new value.

Upvotes: 4

Views: 2321

Answers (2)

jq170727
jq170727

Reputation: 14695

Here is a solution which identifies paths to url members with tostream and select and then updates the values using reduce and setpath

  "http://otherdomain.com.*filenameB.*"                      as $from
| "http://yetanotherdomain.com/new/path/to/filenameC.tar.gz" as $to

| reduce (tostream | select(length == 2 and .[0][-1] == "url")) as $p (
      .
    ; setpath($p[0]; $p[1] | sub($from; $to))
  )

Upvotes: 3

peak
peak

Reputation: 116870

The good news is that there's a simple solution to the problem:

map( if .url | test("http://otherdomain.com.*filenameB.*")
     then .url |= sub(  "http://otherdomain.com.*filenameB.*"; 
           "http://yetanotherdomain.com/new/path/to/filenameC.tar.gz")
     else .
     end)

The not-so-good news is that it's not so easy to explain unless you understand the key cleverness here - the "|=" filter. There is plenty of jq documentation about it, so I'll just point out that it is similar to the += family of operators in the C family of programming languages.

Specifically, .url |= sub(A;B) is like .url = (.url|sub(A;B)). That is how the update is done "in-place".

Upvotes: 5

Related Questions