Stranger B.
Stranger B.

Reputation: 9364

How to delete the last character of prior line with sed

I'm trying to delete a line with a the last character of the prior line with sed:

I have a json file :

{
"name":"John",
"age":"16",
"country":"Spain"
}

I would like to delete country of all entries, to do that I have to delete the comma for the json syntax of the prior line.

I'm using this pattern :

sed '/country/d' test.json
sed -n '/resolved//.$//{x;d;};1h;1!{x;p;};${x;p;}' test.json

Editor's note:
The OP later clarified the following additional requirements, which invalidated some of the existing answers:
- multiple occurrences of country properties should be removed
- across all levels of the object hierarchy
- whitespace variations should be tolerated

Upvotes: 2

Views: 586

Answers (4)

mklement0
mklement0

Reputation: 439597

Using a proper JSON parser such as jq is generally the best choice (see below), but if installing a utility is not an option, try this GNU sed command:

$ sed -zr 's/,\s*"country":[^\n]+//g' test.json
{
"name":"John",
"age":"16"
}
  • -z splits the input into records by NULs, which, in this case means that the whole file is read at once, which enables cross-line substitutions.

  • -r enables extended regular expressions for a more modern syntax with more features.

  • s/,\n"country":\s*//g replaces all occurrences of a comma followed by a (possibly empty) run of whitespace (including possibly a newline) and then "country" through the end of that line with the empty string, i.e., effectively removes the matched strings.

    • Note that this assumes that no other property or closing } follows such a country property on the same line.

To demonstrate a more robust solution based on jq.

Bertrand Martel's helpful answer contains a jq solution, which, however, does not address the requirement (added later) of replacing country attributes anywhere in the input object hierarchy.

In a not-yet-released version of jq higher than v1.5.2, a builtin walk/1 function will be available, which enables the following simple solution:

# Walk all nodes and remove a "country" property from any object.
jq 'walk(if type == "object" then del (.country) else . end)' test.json

In v1.5.2 and below, you can define a simplified variant of walk yourself:

jq '
  # Define recursive function walk_objects/1 that walks all objects in the
  # hierarchy.
  def walk_objects(f): . as $in | 
    if type == "object" then
      reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk_objects(f)) } ) | f
    elif type == "array" then map( walk_objects(f) )
    else . end;
  # Walk all objects and remove a "country" property, if present.
  walk_objects(del(.country))
' test.json

Upvotes: 2

potong
potong

Reputation: 58488

This might work for you (GNU sed):

sed 'N;s/,\s*\n\s*"country".*//;P;D' file

Read two lines into the pattern space and remove substitution string.

N.B. Allows for spaces either side of the line.

Upvotes: 2

Andreas Louv
Andreas Louv

Reputation: 47117

As pointed out before you should really consider using a JSON parser to parse JSON.

When that is said you can slurp the whole file, remove newlines and then replace accordantly:

$ sed ':a;N;$!ba;s/\n//g;s/,"country"[^}]*//' test.json
{"name":"John","age":"16"}

Breakdown:

:a;                 # Define label 'a'
   N;               # Append next line to pattern space
     $!ba;          # Goto 'a' unless it's the last line
s/\n//g;            # Replace all newlines with nothing
s/,"country"[^}]*// # Replace ',"country...' with nothing

Upvotes: 2

Bertrand Martel
Bertrand Martel

Reputation: 45433

You can use a JSON parser like jq to parse json file. The following will return the document without the country field and write the new document in result.json :

jq 'del(.country)' file.json > result.json

Upvotes: 1

Related Questions