bash+xmlstarlet: как индексировать список или заполнять массив?

Я пытаюсь выбрать один узел с помощью xmlstarlet из следующего примера XML:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="key.xsl" ?>
<tables>
  <tableset>
    <table name="table1">
      <row>
        <fld name="fileName">
          <strval><![CDATA[/my/XYZ/file1]]></strval>
        </fld>
        <fld name="fileName">
          <strval><![CDATA[/my/XYZ/file2]]></strval>
        </fld>
        <fld name="fileName">
          <strval><![CDATA[/my/other/XYZ/file3]]></strval>
        </fld>
        <fld name="worksBecauseUnique">
          <strval><![CDATA[/XYZ/unique]]></strval>
        </fld>
      </row>
    </table>
  </tableset>
</tables>

Я пытаюсь создать ассоциативный массив в bash... Как выбрать один узел или выполнить итерацию по нескольким узлам с помощью xmlstarlet?

Я пытаюсь что-то вроде следующего до сих пор, который не работает:

xmlstarlet sel -t -v "//tables/tableset/table/row/fld[@name=\"fileName\"]/strval[0]" xmlfile.xml

Надеясь получить "/my/XYZ/file1", однако это не работает.


person sconicelli    schedule 05.08.2015    source источник
comment
Можете ли вы предположить, что в содержании никогда не будет новых строк?   -  person Charles Duffy    schedule 05.08.2015
comment
Тоже... ассоциативный? С чем в качестве ключа?   -  person Charles Duffy    schedule 05.08.2015
comment
Как вы, наверное, заметили, это связано с другим моим постом. В значениях CDATA не должно быть новых строк. Я надеюсь, что смогу изменить каждое значение CDATA по отдельности.   -  person sconicelli    schedule 05.08.2015
comment
CDATA — это просто синтаксический сахар: текстовый узел — это текстовый узел — это текстовый узел, независимо от того, задается ли его содержимое с помощью CDATA или с посимвольным экранированием объекта.   -  person Charles Duffy    schedule 05.08.2015
comment
Ассоциативный в том, что ключ=//fld[@name] и значение=//fld[@name]/strval   -  person sconicelli    schedule 05.08.2015
comment
Но как тогда быть с именем, имеющим несколько значений?   -  person Charles Duffy    schedule 05.08.2015
comment
Вы хотите разделить их (с чем-то вроде новой строки) в содержимом массива оболочки?   -  person Charles Duffy    schedule 05.08.2015
comment
Обратите внимание, что это опасно - пути UNIX могут содержать буквально любой символ, отличный от NUL, включая символы новой строки. Разрешить считывать созданный со злым умыслом путь как два пути, последний из которых привязан к корню вашей файловой системы, не обязательно безопасно.   -  person Charles Duffy    schedule 05.08.2015
comment
Точно, и здесь я застрял. Я в порядке, если атрибут уникален, однако, когда есть несколько атрибутов с одинаковым точным именем, которые дают несколько значений, у меня возникают проблемы. Например, fileName доставляет мне проблемы, потому что у него более одного значения, и каждое значение должно быть уникальным.   -  person sconicelli    schedule 05.08.2015
comment
Вам нужно выбрать представление данных или использовать более мощный язык (например, тот, который позволяет использовать списки или массивы в качестве значений словаря). На самом деле это вопрос не столько о языке bash, сколько о том, как выглядит ваша модель данных.   -  person Charles Duffy    schedule 05.08.2015
comment
Я изменил свой ответ, чтобы показать один шаблон, но по указанным выше причинам это не хороший шаблон.   -  person Charles Duffy    schedule 05.08.2015
comment
... хотя, на самом деле, если ваша цель - извлечь контент для модификации, то я думаю, что проще всего будет индексированный массив, а не ассоциативный; таким образом, несколько элементов с похожими ключами по-прежнему остаются полностью отдельными, и вы можете использовать значения индекса в своем более позднем запросе, чтобы фактически применить изменения.   -  person Charles Duffy    schedule 05.08.2015
comment
Я бы также серьезно подумал о том, чтобы переписать то, что вы здесь делаете, чтобы использовать xmlstarlet pyx, а не xmlstarlet ed; это гораздо лучший инструмент для кругового обхода.   -  person Charles Duffy    schedule 05.08.2015
comment
Спасибо. это дает мне много пищи для размышлений   -  person sconicelli    schedule 05.08.2015
comment
Кстати, если мой ответ на ваш другой вопрос настолько неадекватен, что вам нужен дополнительный вопрос... не могли бы вы объяснить, почему? Вон там, как комментарий к ответу? :)   -  person Charles Duffy    schedule 05.08.2015
comment
(Кроме того, сочли бы вы точным, если бы я отредактировал заголовок этого вопроса на bash+xmlstarlet: создание массива bash из результатов запроса? Прямо сейчас заголовок, похоже, не очень хорошо отражает вашу фактическую цель).   -  person Charles Duffy    schedule 05.08.2015
comment
Конечно... это может быть изменено на это. Я также хотел бы знать, как выбрать один узел в диапазоне. Я могу подсчитать имя файла и получить обратно 3, но надеялся также получить strval с помощью индекса... если это имеет смысл?   -  person sconicelli    schedule 05.08.2015
comment
Если вы что-то здесь упускаете, так это то, что массивы XPath имеют индекс 1, а не 0. Таким образом, получение первой записи — это foo[1], а не foo[0].   -  person Charles Duffy    schedule 05.08.2015


Ответы (1)


Отвечая на первую часть вашего вопроса, вы делаете простую ошибку:

strval[0]

должно быть

strval[1]

... для выбора первого экземпляра, поскольку массивы XPath индексируются 1, а не 0.


Теперь, когда вы хотите выбрать второе совпадение внутри всего документа, а не внутри родительского элемента fld, это выглядит немного иначе:

(//tables/tableset/table/row/fld[@name="fileName"]/strval)[2]

Теперь о заполнении массива оболочки. Поскольку ваш контент здесь не содержит новых строк:

query='//tables/tableset/table/row/fld[@name="fileName"]/strval'

fileNames=( )
while IFS= read -r entry; do
  fileNames+=( "$entry" )
done < <(xmlstarlet sel -t -v "$query" -n xmlfile.xml)

# print results
printf 'Extracted filename: %q\n' "${fileNames[@]}"

Вы не даете достаточно подробностей для настройки ассоциативного массива (как вы хотите установить ключи?), поэтому я делаю это как простой индексированный массив.


С другой стороны, если бы мы сделали некоторые предположения - что вы хотите настроить свой ассоциативный массив так, чтобы он соответствовал ключу @name значению strval, и что вы хотите использовать символы новой строки для разделения нескольких значений, заданных для одного и того же key -- тогда это может выглядеть так:

match='//tables/tableset/table/row/fld[@name][strval]'
key_query='./@name'
value_query='./strval'

declare -A content=( )
while IFS= read -r key && IFS= read -r value; do
  if [[ $content[$key] ]]; then
    # appending to existing value
    content[$key]+=$'\n'"$value"
  else
    # first value for this key
    content[$key]="$value"
  fi
  fileNames+=( "$entry" )
done < <(xmlstarlet sel \
           -t -m "$query" \
           -v "$key_query" -n \
           -v "$value_query" -n xmlfile.xml)
person Charles Duffy    schedule 05.08.2015
comment
Спасибо, это определенно помогает ... Я могу застрять, переходя к чему-то другому, кроме bash, чтобы выполнить работу. - person sconicelli; 05.08.2015
comment
Если бы вы более подробно описали, в чем заключалась работа, это могло бы быть более полезным. Если ваша цель состоит в том, чтобы внести изменения на стороне оболочки, а затем применить их, это, безусловно, очень и очень выполнимо, хотя и не с очень хорошей эффективностью, без существенных компромиссов в правильности или лаконичности. Если вам нужен язык, оптимизированный для выполнения сложных операций с XML-документами, могу ли я предложить XQuery? - person Charles Duffy; 05.08.2015
comment
Хорошо ... последний вопрос, который я задал, вы сказали, что я использовал слишком много деталей. Спасибо за ваши предложения... У меня очень сложная задача по изменению очень сложного XML-файла. Я могу описать, что я пытаюсь сделать, но это потребует много объяснений. - person sconicelli; 05.08.2015
comment
Детали — это действительно неправильное слово. Контекст, наверное? Одна из полезных особенностей контекста заключается в том, что он может предоставляться отдельно от основной постановки задачи, т.е. четкое и краткое изложение проблемы здесь, контекстная информация здесь, контекст не требуется для ответа и четко разделен. - person Charles Duffy; 05.08.2015
comment
Что касается контекста и деталей, применяется другая часть руководства «Как задавать умные вопросы»: catb.org/esr/faqs/smart-questions.html#goal (опишите цель, а не шаг). - person Charles Duffy; 05.08.2015
comment
Хорошо, постараюсь быть кратким. Цель состоит в том, чтобы иметь возможность создать плоский файл пар ключ=значение из in.xml. Затем мне нужно иметь возможность изменить плоский файл на key=new_value. Затем плоский файл используется для переименования значений, содержащихся в файле in.xml, и генерирует файл out.xml. У меня весь этот процесс работает, за исключением случаев, когда я сталкиваюсь с несколькими значениями с одинаковым именем атрибута. - person sconicelli; 05.08.2015
comment
Я бы предложил поставить сигилы на ключи, т.е. клавиша [1], клавиша [2] и т. д.; до тех пор, пока это не перекрывается с буквальными значениями ключа. - person Charles Duffy; 05.08.2015
comment
Думал о том же... однако все еще не может вытащить отдельные значения с помощью xmlstarlet sel -t -v "//tables/tableset/table/row/fld[@name=\"fileName\"]/strval[2]" file.xml - это не работает - person sconicelli; 05.08.2015
comment
//tables, кстати, ищет элементы с именем tables по всему документу. Это крайне неэффективно по сравнению с простым рутированием в /tables. - person Charles Duffy; 05.08.2015
comment
Кстати, почему вы настаиваете на использовании двойных кавычек вне запроса? Усложняет вашу жизнь, потому что это означает, что вам нужно избегать буквальных двойных кавычек внутри запроса. - person Charles Duffy; 05.08.2015
comment
В любом случае -- это не работает, потому что вы ставите [2] не в том месте; он ищет второй strval внутри этого единственного fld. - person Charles Duffy; 05.08.2015
comment
Отредактировал ответ для демонстрации. - person Charles Duffy; 05.08.2015
comment
Удалены внешние двойные кавычки и используется /tables вместо //tables, однако по-прежнему невозможно получить значение при использовании `xmlstarlet sel -t -v /tables/tableset/table/row/fld[@name=fileName]/strval[2] file.xml - person sconicelli; 05.08.2015
comment
Это сработало: xmlstarlet sel -t -v "(//tables/tableset/table/row/fld[@name=\"fileName\"]/strval)[2]" file.xml - мне нужно было заключить XPath в кавычки, иначе я получаю синтаксическую ошибку. Спасибо! - person sconicelli; 05.08.2015
comment
Посмотрите, как мой код использует кавычки — одинарные кавычки снаружи и двойные кавычки внутри, а не двойные кавычки в обоих местах. - person Charles Duffy; 05.08.2015