Вопрос или проблема
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE POWERMART SYSTEM "powrmart.dtd">
<POWERMART CREATION_DATE="12/01/2016 17:43:15" REPOSITORY_VERSION="184.93">
<REPOSITORY NAME="PCREPO_BIDEV" VERSION="184" CODEPAGE="UTF-8"
DATABASETYPE="Oracle">
<FOLDER NAME="ABC" GROUP="" OWNER="Administrator" SHARED="SHARED"
DESCRIPTION="" PERMISSIONS="rwx---r--" UUID="3b13d2c9-39dc-426f-8320-
def2bb8424ef">
У меня есть вышеуказанные образцы данных. В каком файле будет находиться FOLDER NAME с некоторым значением. Мне нужно заменить его на другое значение, которое представляет собой переменную. Я хочу заменить FOLDER NAME=”ABC” на DEF.
sed -i "s/<FOLDER NAME=\"\*\"/<FOLDER NAME=\"$FLDR\"/g" Gather.XML
Вышеуказанная команда sed не возвращает ошибок, но она не производит замену.
Предполагая, что это корректный XML-документ, используя XMLStarlet:
xmlstarlet ed -u '/POWERMART/REPOSITORY/FOLDER[@NAME="ABC"]/@NAME' -v "$FLDR" file.xml
Это найдет узел FOLDER
под /POWERMART/REPOSITORY
, у которого атрибут NAME
равен ABC
, и изменит его значение на значение переменной оболочки FLDR
.
Это также будет работать, если между именем узла FOLDER
и атрибутом NAME
будет перенос строки, так как XML нечувствителен к такому пробелу.
Пример:
$ cat file.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE POWERMART SYSTEM "powrmart.dtd">
<POWERMART CREATION_DATE="12/01/2016 17:43:15" REPOSITORY_VERSION="184.93">
<REPOSITORY NAME="PCREPO_BIDEV" VERSION="184" CODEPAGE="UTF-8" DATABASETYPE="Oracle">
<FOLDER NAME="ABC" GROUP="" OWNER="Administrator" SHARED="SHARED" DESCRIPTION="" PERMISSIONS="rwx---r--" UUID="3b13d2c9-39dc-426f-8320-def2bb8424ef"/>
</REPOSITORY>
</POWERMART>
$ FLDR='DEF'
$ xmlstarlet ed -u '/POWERMART/REPOSITORY/FOLDER[@NAME="ABC"]/@NAME' -v "$FLDR" file.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE POWERMART SYSTEM "powrmart.dtd">
<POWERMART CREATION_DATE="12/01/2016 17:43:15" REPOSITORY_VERSION="184.93">
<REPOSITORY NAME="PCREPO_BIDEV" VERSION="184" CODEPAGE="UTF-8" DATABASETYPE="Oracle">
<FOLDER NAME="DEF" GROUP="" OWNER="Administrator" SHARED="SHARED" DESCRIPTION="" PERMISSIONS="rwx---r--" UUID="3b13d2c9-39dc-426f-8320-def2bb8424ef"/>
</REPOSITORY>
</POWERMART>
Если вам нужно сопоставить определенное NAME
REPOSITORY
, то, например:
xmlstarlet ed -u '/POWERMART/REPOSITORY[@NAME="PCREPO_BIDEV"]/FOLDER[@NAME="ABC"]/@NAME' -v "$FLDR" file.xml
Попробуйте это,
sed -i 's/<FOLDER NAME="[A-Z]*"/<FOLDER NAME="'$FLDR'"/g' Gather.XM
В вашем выражении sed не хватает “.
“, чтобы сопоставить все символы
sed -i "s/<FOLDER NAME=\"\.*\"/<FOLDER NAME=\"$FLDR\"/g" Gather.XML
Похоже, вам следует использовать [^\"]*
вместо \*
:
sed -i "s/<FOLDER NAME=\"[\"]*\"/<FOLDER NAME=\"$FLDR\"/g" Gather.XML
Поскольку вы хотите сопоставить весь текст до первого "
. \*
будет сопоставлять символ *
в буквальном смысле, но его там нет, так что содержимое файла никогда не будет соответствовать.
Я не рекомендую sed
в общем случае, но в этом случае это, возможно, достаточно просто для его обработки, нам просто нужно быть осторожными с некоторыми опасностями:
-
если
$FLDR
содержит"
, то это сломает XML. -
Если
$FLDR
содержит/
(что кажется правдоподобным для папки), то это сломает командуsed
. -
Если
$FLDR
содержит\
или&
, это может привести к добавлениюsed
неожиданных символов или строк в XML.
(Не говоря уже о том, что если $FLDR
не обернуто в двойные кавычки, могут возникнуть дополнительные проблемы.)
Каждый из этих элементов может быть использован злонамеренно для изменения XML неожиданным образом.
Если вы экранируете символы \
, /
и &
, а также экранируете "
с помощью "
(\"
для sed
), вы сможете обойти эти проблемы.
Вы можете выполнять замены в bash с помощью "${FLDR//a/b}"
, хотя для обратных и прямых косых черт и амперсандов потребуется дополнительное экранирование. И обратите внимание, что нужно быть осторожным с порядком, в котором вы выполняете замены, и очень осторожным с экранированием в самих подстановках bash.
Другой проблемой в вашем коде является то, что поиск "…NAME=\"\*\"…"
будет соответствовать только буквальной строке …"*"…
.
Вы можете заменить это на "…\".*\"…"
(любые символы между кавычками).
Или на "…\"[^"]*\"…"
(любые символы, не являющиеся кавычками, между кавычками), если вы не хотите беспокоиться о том, насколько “жадным” будет совпадение.
Это должно защитить от всех вышеперечисленных случаев:
FLDR="${FLDR//\\/\\\\}" # экранировать обратные слеши (прежде всего)
FLDR="${FLDR//\"/\"}" # экранировать кавычки (прежде чем экранировать амперсанд)
FLDR="${FLDR//&/\\&}" # экранировать амперсанды
FLDR="${FLDR//\//\\/}" # экранировать прямые косые черты
sed -i "s/<FOLDER NAME=\"[^\"]*\"/<FOLDER NAME=\"$FLDR\"/g" Gather.XML
Ответ или решение
Для замены значения атрибута XML с помощью переменной оболочки вы можете использовать утилиты, такие как sed
или xmlstarlet
. Рассмотрим оба метода и их особенности, а также потенциальные проблемы, которые могут возникнуть при их использовании.
Метод 1: Использование sed
sed
— это мощный инструмент для текстовой обработки, однако его использование для изменения XML может быть рискованным по ряду причин, включая возможность несовместимости с неправильными данными или структурой XML. Вот как вы можете это сделать:
-
Объявление переменной:
Убедитесь, что ваша переменнаяFLDR
инициализирована, например:FLDR='DEF'
-
Замена с использованием
sed
:
Чтобы заменить значение атрибутаNAME
элементаFOLDER
, вы можете использовать следующий синтаксис:sed -i "s/<FOLDER NAME=\"[^\"]*\"/<FOLDER NAME=\"$FLDR\"/g" Gather.XML
Здесь регулярное выражение
<FOLDER NAME=\"[^\"]*\"
позволяет найти атрибутNAME
с любым значением, заключенным в кавычки. Ключевое важно — это использование[^"]
, которое означает "любые символы, кроме кавычки". -
Проблемы с
sed
:- Если переменная
FLDR
содержит специальные символы, такие как&
,/
,\
, или"
, они могут сломать команду. Рекомендуется предварительно экранировать эти символы:
FLDR=$(printf '%s\n' "$FLDR" | sed -e 's/[\/&]/\\&/g' -e 's/"/\\"/g')
- Если переменная
Метод 2: Использование xmlstarlet
xmlstarlet
— это инструмент, специально разработанный для обработки XML и может быть более надежным в этом контексте.
-
Команда для замены:
Замена значения атрибутаNAME
для элементаFOLDER
может выглядеть следующим образом:xmlstarlet ed -u '/POWERMART/REPOSITORY/FOLDER[@NAME="ABC"]/@NAME' -v "$FLDR" Gather.XML
Эта команда изменяет значение атрибута
NAME
, если текущее значение равноABC
. -
Преимущества
xmlstarlet
:- Учитывает структуру XML и не зависит от текстового формата, как
sed
. - Минимизирует риск повреждения документа из-за неправильного экранирования символов.
- Учитывает структуру XML и не зависит от текстового формата, как
Заключение
Хотя sed
может выполнять простые задачи, использование xmlstarlet
рекомендуется для работы с XML-документами, чтобы избежать возможных ошибок, связанных с неформатированием. В любом случае, вы должны быть осторожны с содержимым переменной FLDR
, особенно при наличии специальных символов, которые могут нарушить форматирование документа.
Подходите к обработке XML аккуратно, особенно в производственной среде, чтобы избежать повреждения ваших данных.