Цели

Замените текст "scripts: {" следующей строкой

"scripts": {
    "watch": "tsc -w",

В файле json.

Попытки

Я создал две переменные для строк источника и назначения:

Первая попытка

SRC='"scripts": {'
DST='"scripts": {
    "watch": "tsc -w",'

И выполнил следующую команду:

sed "s/$SRC/$DST/" foo.json

Это не удалось.

Вторая попытка

На этот раз я избегал двойных кавычек для исходной и целевой строк:

SRC="\"scripts\": {"
DST="\"scripts\": {
    \"watch\": \"tsc -w\",
    \"dev\": \"nodemon dist/index.js\","

И выполнил ту же команду, что и выше, которая не удалась.

Третья и четвертая попытки

Я попробовал переменные, определенные, как указано выше, с помощью следующей команды:

sed 's/'"$SRC"'/'"$DST"'/' foo.json

Это не удалось.

Все эти попытки привели к ошибке

unterminated 's' command

Что пошло не так?

5
Kusalananda 23 Дек 2020 в 12:06
4
Не могли бы вы показать больше вашего документа JSON? sed — неправильный инструмент для работы с JSON. Что-то вроде jq было бы лучше. Похоже, вы хотите вставить пару ключ-значение в какой-то объект JSON. Где находится ключ scripts в документе JSON?
 – 
Kusalananda
23 Дек 2020 в 11:55
Ключ scripts находится на первом уровне файла json. Он package.json поставляется с npm init -y. использование sed в этом случае невозможно?
 – 
PHD
23 Дек 2020 в 11:58
Ответ о переполнении стека.
 – 
Toby Speight
23 Дек 2020 в 20:07

5 ответов

Лучший ответ

Предполагая, что ваш документ JSON выглядит примерно так

{
  "scripts": {
    "other-key": "some value"
  }
}

... и вы хотите вставить другую пару ключ-значение в объект .scripts. Затем вы можете использовать jq для этого:

$ jq '.scripts.watch |= "tsc -w"' file.json
{
  "scripts": {
    "other-key": "some value",
    "watch": "tsc -w"
  }
}

Или же,

$ jq '.scripts += { watch: "tsc -w" }' file.json
{
  "scripts": {
    "other-key": "some value",
    "watch": "tsc -w"
  }
}

Оба они заменят уже существующую запись .scripts.watch.

Обратите внимание, что порядок пар ключ-значение в .scripts не важен (поскольку это не массив).

Перенаправьте вывод в новый файл, если хотите его сохранить.

Чтобы добавить несколько пар ключ-значение к одному и тому же объекту:

$ jq '.scripts += { watch: "tsc -w", dev: "nodemon dist/index.js" }' file.json
{
  "scripts": {
    "other-key": "some value",
    "watch": "tsc -w",
    "dev": "nodemon dist/index.js"
  }
}

В сочетании с jo для создания JSON, который необходимо добавить к объекту .scripts:

$ jq --argjson new "$( jo watch='tsc -w' dev='nodemon dist/index.js' )" '.scripts += $new' file.json
{
  "scripts": {
    "other-key": "some value",
    "watch": "tsc -w",
    "dev": "nodemon dist/index.js"
  }
}

sed подходит для разбора строкового текста. JSON не входит в записи с разделителями-новострочными, и sed не знает о правилах цитирования, кодирования символов и т. д. JSON. Чтобы правильно проанализировать и изменить набор структурированных данных, подобный этому (или XML, или YAML, или даже CSV при некоторых обстоятельствах), вы должны использовать правильный анализатор.

В качестве дополнительного преимущества использования jq в данном случае вы получаете небольшой фрагмент кода, который легко модифицируется в соответствии с вашими потребностями, и который так же легко модифицируется для поддержки изменений в структура входных данных.

8
Kusalananda 23 Дек 2020 в 13:32

Кусалананда абсолютно прав, говоря, что выделенный анализатор — правильный инструмент для работы. Однако это можно легко сделать и в sed (по крайней мере, в GNU sed, который понимает \n). Вы все усложняли, пытаясь заменить весь двухстрочный шаблон. Вместо этого просто сопоставьте целевую строку и вставьте замену после нее:

"scripts": {

А потом:

$ sed '/"scripts": {/s/$/\n    "watch": "tsc -w",/' file
"scripts": {
    "watch": "tsc -w",

Это означает, что «если эта строка соответствует строке "scripts": {, то замените конец строки новой строкой (\n), за которой следует "watch": "tsc -w",. Кроме того, вы можете использовать a команда sed для добавления текста:

$ sed '/"scripts": {/a\    "watch": "tsc -w",' file 
"scripts": {
    "watch": "tsc -w",
6
terdon 23 Дек 2020 в 12:50

Здесь мы создаем команду Sed, но добавляем в нее новую строку:

sed "s/$SRC/$DST/" foo.json

Это неверно, так как новая строка завершает команду. Нам нужно написать \n вместо буквального перехода на новую строку. В некоторых оболочках (Bash, zsh, других..., но не в простой оболочке POSIX) мы можем сделать это, используя подстановку параметров:

DST=${DST//$'\n'/\\n}
#        //             replace every
#          $'\n'        newline
#               /       with
#                \\n    \ and n 

Для других строк замены нам также может понадобиться заключить / в кавычки:

DST=${DST//\//\\/}

Кроме того, \ (сначала сделайте это) и & имеют особое значение в замещающем тексте и поэтому нуждаются в замене.

2
Toby Speight 23 Дек 2020 в 20:09

Хотя я согласен с тем, что jq является оптимальным инструментом для использования, эту конкретную задачу можно легко выполнить с помощью sed. По сути, задача состоит в том, чтобы добавить строку текста после совпадения шаблона в предыдущей строке.

$ cat example.json
{
   "scripts": {  
      "access_token": "asadasd",
      "expires_in": "3600"
   }
}

$ cat demo.sh
#!/bin/bash

SRC='"scripts": {'
DST='\ \ \ \ \ \ "watch": "tsc -w",'

sed '/'"$SRC"'/a '"$DST"'' example.json

$ ./demo.sh
{
   "scripts": {  
      "watch": "tsc -w",
      "access_token": "asadasd",
      "expires_in": "3600"
   }
}
$
0
fpmurphy 23 Дек 2020 в 13:10

Поскольку sed рассматривает весь текст как regex, поэтому мы должны экранировать строки, прежде чем вставлять их в код sed. Кроме того, нам нужно позаботиться о том, находится ли входящая строка слева или справа от команды s///, потому что список символов BRE различен с обеих сторон.

s='[[:blank:]]'
srch_str='"scripts": {'
repl_str='"watch": "tsc -w",'

esc_srch_str=$(
  printf '%s\n' "$srch_str" |
  sed -e 's:[][\/.^$*]:\\&:g'
)

esc_repl_str=$(
  printf '%s\n' "$repl_str" |
  sed -e 's:[\/&]:\\&:g'
)

sed -e "
  /^$s*$esc_srch_str\$/G
  s/^\($s*\).*\n/&\1  $esc_repl_str/
" file

Вывод:

{
  "scripts": {
    "watch": "tsc -w",
    "other-key": "some value"
  }
}
0
guest_7 23 Дек 2020 в 17:42
 – 
Kusalananda
23 Дек 2020 в 18:30
Sed не "рассматривает весь текст как регулярное выражение" - в частности, текст подстановки команды s является текстом подстановки.
 – 
Toby Speight
23 Дек 2020 в 20:03