3

I'm writing a name-helping script to automatically set the "name": field in a package.json file so that it matches a certain regex structure, but I'm having some issues actually setting the name. The regex it must match is '\@abc\/([a-z]+-{0,1})+[a-z]*$'.

Right now I do basically this (along with some extra stuff to really assert that the naming convention is followed):

pattern='\@abc\/([a-z]+-{0,1})+[a-z]*$'
if [[ ! $name =~ $pattern ]]; then 
  read -rp "New name: " newName
  sed -ri "s/(\s.\"name\"\:\s\").*/\1$newName\",/g" $1/package.json
fi

As you might see, the problem here is that the variable $newName gets processed in sed as a command, it needs to be escape charactered (assuming that the user actually wrote in a new name with correct structure). Is there a way to do this? Preferably as dependency un-reliant as possible.

Kusalananda
  • 333,661
baal_imago
  • 37
  • 4
  • Since this concerns JSON, would you mind posting an example document along with the expected modified document. It would be much easier to do this with jq than with sed. – Kusalananda Jul 28 '21 at 14:51
  • Does it need to match @abc or \@abc? The @ isn't special in any regular expression flavor I am familiar with, so a \@ will be interpreted as a @. If you want to match a literal backslash, you need \\@. – terdon Jul 28 '21 at 14:51
  • In most programs, it doesn't hurt to backslash-escape an @, it's just treated as a literal @, but the only cases I know where it makes a difference are a) in a perl script, in order to prevent it being interpreted as the sigil for an array. e.g. s/@example.org/b/ will try to interpolate array @example into the LHS of the s// operator. s/\@a/b/ won't, it will be just a literal @. This is important to remember when trying to match email addresses; and b) there are several cases in vim regexes where @ has a different meaning depending on whether it's escaped or not. – cas Jul 29 '21 at 05:20

1 Answers1

6

On the surface, this question is a duplicate of How to ensure that string interpolated into `sed` substitution escapes all metachars. However, sed is a line-oriented text manipulation tool, and JSON is a structured document format which is not line-oriented. It would therefore be better to address the "want to work with JSON document" aspect raher than the "I want to use sed in a particular way" aspect of the question.


Since this has to do with JSON, it would be better to use a JSON-aware tool like jq.

jq --arg newname '@abc/corrected-name' \
    'select( .name | test("^@abc/([a-z]-?)+[a-z]*$") | not ).name |= $newname' file.json

This command assumes that the input consists of a JSON document that has a top-level name key. It (possibly) modifies the input document and writes the result to standard output.

In short, the jq expression selects all elements of the input set that has a top-level name key whose value does not match ^@abc/([a-z]-?)+[a-z]*$. Once an element has been selected, its name key's value is updated with the value in $newvalue.

This has the benefit that it does not necessitate an injection of a shell variable into the expression, and that the value given to the internal $newname variable will be automatically JSON-encoded by jq (if it needs to be encoded in any specific way).

An input document like

{
  "name": "@abc/abc-foo"
}

would remain unchanged by this command, while

{
  "name": "bumblebee"
}

would be changed into

{
  "name": "@abc/corrected-name"
}
Kusalananda
  • 333,661
  • Thank you. Using jq along with: https://stackoverflow.com/questions/36565295/jq-to-replace-text-directly-on-file-like-sed-i works like a charm. – baal_imago Jul 29 '21 at 05:52