В ідеалі я хотів би зробити це:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
В ідеалі я хотів би зробити це:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
Відповіді:
Це справді лише пояснення відповіді Юзема , але я не відчував, що таке редагування потрібно робити комусь іншому, а коментарі не дозволяють форматувати, так що ...
rdom () { local IFS=\> ; read -d \< E C ;}
Назвемо це "read_dom" замість "rdom", виділіть його трохи і використовуйте довші змінні:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
}
Гаразд, це визначає функцію, яку називають read_dom. Перший рядок робить IFS (роздільник поля введення) локальним для цієї функції та змінює її на>. Це означає, що коли ви читаєте дані, а не автоматично розділяєте їх на пробіл, вкладку чи нові рядки, вони розбиваються на '>'. У наступному рядку сказано, щоб прочитати введення з stdin, і замість того, щоб зупинятися на новому рядку, зупиніться, коли побачите символ <<(прапор -d для роздільника). Прочитане потім розділяється за допомогою IFS і присвоюється змінній ENTITY і CONTENT. Тому візьміть наступне:
<tag>value</tag>
Перший виклик, щоб read_dom
отримати порожню рядок (оскільки '<' є першим символом). Це розділяється IFS на просто "", оскільки немає ">" символу. Прочитати потім призначає порожній рядок обом змінним. Другий виклик отримує рядок "тег> значення". Це розділяється IFS потім на два поля "тег" та "значення". Прочитати потім присвоює змінні типу: ENTITY=tag
і CONTENT=value
. Третій виклик отримує рядок '/ tag>'. Це розділяється IFS на два поля '/ tag' та ''. Прочитати потім присвоює змінні типу: ENTITY=/tag
і CONTENT=
. Четвертий виклик поверне ненульовий статус, оскільки ми дійшли до кінця файлу.
Тепер його цикл while прибрав трохи, щоб відповідати вищевказаному:
while read_dom; do
if [[ $ENTITY = "title" ]]; then
echo $CONTENT
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Перший рядок просто говорить, "поки функція read_domповертає нульовий статус, зробіть наступне". Другий рядок перевіряє, чи сутність, яку ми щойно бачили, - "назва". Наступний рядок перегукується з вмістом тегу. Чотири лінії виходять. Якщо це не сутність заголовка, то цикл повторюється в шостому рядку. Ми перенаправляємо "xhtmlfile.xhtml" на стандартний вхід (для read_dom
функції) і перенаправляємо стандартний висновок на "titleOfXHTMLPage.txt" (відлуння від попереднього циклу).
Тепер надано наступне (подібне до того, що ви отримуєте з перерахування відра на S3) для input.xml
:
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>sth-items</Name>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>item-apple-iso@2x.png</Key>
<LastModified>2011-07-25T22:23:04.000Z</LastModified>
<ETag>"0032a28286680abee71aed5d059c6a09"</ETag>
<Size>1785</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
і наступний цикл:
while read_dom; do
echo "$ENTITY => $CONTENT"
done < input.xml
Ви повинні отримати:
=>
ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/" =>
Name => sth-items
/Name =>
IsTruncated => false
/IsTruncated =>
Contents =>
Key => item-apple-iso@2x.png
/Key =>
LastModified => 2011-07-25T22:23:04.000Z
/LastModified =>
ETag => "0032a28286680abee71aed5d059c6a09"
/ETag =>
Size => 1785
/Size =>
StorageClass => STANDARD
/StorageClass =>
/Contents =>
Отже, якщо ми написали while
цикл, як Юзем:
while read_dom; do
if [[ $ENTITY = "Key" ]] ; then
echo $CONTENT
fi
done < input.xml
Ми отримаємо список усіх файлів у відрі S3.
EDIT
Якщо з якихось причин local IFS=\>
не працює для вас, і ви встановили його в усьому світі, вам слід скинути його в кінці функції, наприклад:
read_dom () {
ORIGINAL_IFS=$IFS
IFS=\>
read -d \< ENTITY CONTENT
IFS=$ORIGINAL_IFS
}
В іншому випадку будь-який розбиття рядка, який ви робите пізніше в сценарії, буде зіпсований.
EDIT 2
Для розділення пар імен атрибутів / значень можна read_dom()
додати подібне:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local ret=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $ret
}
Потім напишіть свою функцію для розбору та отримання потрібних даних:
parse_dom () {
if [[ $TAG_NAME = "foo" ]] ; then
eval local $ATTRIBUTES
echo "foo size is: $size"
elif [[ $TAG_NAME = "bar" ]] ; then
eval local $ATTRIBUTES
echo "bar type is: $type"
fi
}
Потім, поки ви read_dom
телефонуєте parse_dom
:
while read_dom; do
parse_dom
done
Тоді наведено наступний приклад розмітки:
<example>
<bar size="bar_size" type="metal">bars content</bar>
<foo size="1789" type="unknown">foos content</foo>
</example>
Ви повинні отримати цей вихід:
$ cat example.xml | ./bash_xml.sh
bar type is: metal
foo size is: 1789
Інший користувач EDIT 3 заявив, що у FreeBSD виникають проблеми з ним, і запропонував зберегти статус виходу з прочитаного та повернути його в кінці read_dom, наприклад:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local RET=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $RET
}
Я не бачу жодної причини, чому це не повинно працювати
IFS=\< read ...
який встановить IFS лише для дзвінка зчитування. (Зауважте, що я жодним чином не схвалюю практику використання read
для розбору xml, і я вважаю, що це загрожує небезпекою і цього слід уникати.)
Це можна зробити дуже легко, використовуючи лише баш. Вам потрібно лише додати цю функцію:
rdom () { local IFS=\> ; read -d \< E C ;}
Тепер ви можете використовувати rdom як для читання, але для html-документів. При виклику rdom призначить елемент змінній E, а вміст - var C.
Наприклад, щоб зробити те, що ви хотіли зробити:
while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Інструменти командного рядка, які можна викликати із скриптів оболонки, включають:
Я також використовую xmllint і xsltproc з невеликими сценаріями перетворення XSL для обробки XML з командного рядка або в скриптах оболонки.
Ви можете використовувати утиліту xpath. Він встановлений разом з пакетом Perl XML-XPath.
Використання:
/usr/bin/xpath [filename] query
або XMLStarlet . Щоб встановити його на opensuse, використовуйте:
sudo zypper install xmlstarlet
або спробуйте cnf xml
на інших платформах.
xpath
які попередньо встановлені, непридатні для використання в якості компонента в сценаріях. Дивіться, наприклад, stackoverflow.com/questions/15461737/… для розробки.
apt-get install xmlstarlet
Цього достатньо ...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
apt-get install libxml-xpath-perl
.
Перегляньте XML2 з http://www.ofb.net/~egnor/xml2/, який перетворює XML у формат, орієнтований на лінію.
Починаючи з відповіді чаду, ось ПОСЛУГ робоче рішення для розбору UML, з пропперною обробкою коментарів, лише з двома маленькими функціями (більше 2 бут ви можете їх змішати всі). Я не можу сказати, що один з Chad взагалі не працював, але у нього виникло занадто багато проблем із неправильно сформованими XML-файлами: тому вам потрібно бути трохи складніше, щоб обробляти коментарі та неправильно розміщені пробіли / CR / TAB / тощо.
Метою цієї відповіді є надання готових 2-х функцій для використання програм, що не потребують аналізу, для тих, хто потребує розбору UML без складних інструментів, використовуючи perl, python або щось інше. Щодо мене, я не можу встановити ні cpan, ні perl модулі для старої виробничої ОС, над якою працюю, і python недоступний.
По-перше, визначення слів UML, використаних у цій публікації:
<!-- comment... -->
<tag attribute="value">content...</tag>
EDIT: оновлені функції, з ручкою:
xml_read_dom() {
# /programming/893585/how-to-parse-xml-in-bash
local ENTITY IFS=\>
if $ITSACOMMENT; then
read -d \< COMMENTS
COMMENTS="$(rtrim "${COMMENTS}")"
return 0
else
read -d \< ENTITY CONTENT
CR=$?
[ "x${ENTITY:0:1}x" == "x/x" ] && return 0
TAG_NAME=${ENTITY%%[[:space:]]*}
[ "x${TAG_NAME}x" == "x?xmlx" ] && TAG_NAME=xml
TAG_NAME=${TAG_NAME%%:*}
ATTRIBUTES=${ENTITY#*[[:space:]]}
ATTRIBUTES="${ATTRIBUTES//xmi:/}"
ATTRIBUTES="${ATTRIBUTES//xmlns:/}"
fi
# when comments sticks to !-- :
[ "x${TAG_NAME:0:3}x" == "x!--x" ] && COMMENTS="${TAG_NAME:3} ${ATTRIBUTES}" && ITSACOMMENT=true && return 0
# http://tldp.org/LDP/abs/html/string-manipulation.html
# INFO: oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# [ "x${ATTRIBUTES:(-1):1}x" == "x/x" -o "x${ATTRIBUTES:(-1):1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:(-1)}"
[ "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x/x" -o "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:${#ATTRIBUTES} -1}"
return $CR
}
і другий:
xml_read() {
# /programming/893585/how-to-parse-xml-in-bash
ITSACOMMENT=false
local MULTIPLE_ATTR LIGHT FORCE_PRINT XAPPLY XCOMMAND XATTRIBUTE GETCONTENT fileXml tag attributes attribute tag2print TAGPRINTED attribute2print XAPPLIED_COLOR PROSTPROCESS USAGE
local TMP LOG LOGG
LIGHT=false
FORCE_PRINT=false
XAPPLY=false
MULTIPLE_ATTR=false
XAPPLIED_COLOR=g
TAGPRINTED=false
GETCONTENT=false
PROSTPROCESS=cat
Debug=${Debug:-false}
TMP=/tmp/xml_read.$RANDOM
USAGE="${C}${FUNCNAME}${c} [-cdlp] [-x command <-a attribute>] <file.xml> [tag | \"any\"] [attributes .. | \"content\"]
${nn[2]} -c = NOCOLOR${END}
${nn[2]} -d = Debug${END}
${nn[2]} -l = LIGHT (no \"attribute=\" printed)${END}
${nn[2]} -p = FORCE PRINT (when no attributes given)${END}
${nn[2]} -x = apply a command on an attribute and print the result instead of the former value, in green color${END}
${nn[1]} (no attribute given will load their values into your shell; use '-p' to print them as well)${END}"
! (($#)) && echo2 "$USAGE" && return 99
(( $# < 2 )) && ERROR nbaram 2 0 && return 99
# getopts:
while getopts :cdlpx:a: _OPT 2>/dev/null
do
{
case ${_OPT} in
c) PROSTPROCESS="${DECOLORIZE}" ;;
d) local Debug=true ;;
l) LIGHT=true; XAPPLIED_COLOR=END ;;
p) FORCE_PRINT=true ;;
x) XAPPLY=true; XCOMMAND="${OPTARG}" ;;
a) XATTRIBUTE="${OPTARG}" ;;
*) _NOARGS="${_NOARGS}${_NOARGS+, }-${OPTARG}" ;;
esac
}
done
shift $((OPTIND - 1))
unset _OPT OPTARG OPTIND
[ "X${_NOARGS}" != "X" ] && ERROR param "${_NOARGS}" 0
fileXml=$1
tag=$2
(( $# > 2 )) && shift 2 && attributes=$*
(( $# > 1 )) && MULTIPLE_ATTR=true
[ -d "${fileXml}" -o ! -s "${fileXml}" ] && ERROR empty "${fileXml}" 0 && return 1
$XAPPLY && $MULTIPLE_ATTR && [ -z "${XATTRIBUTE}" ] && ERROR param "-x command " 0 && return 2
# nb attributes == 1 because $MULTIPLE_ATTR is false
[ "${attributes}" == "content" ] && GETCONTENT=true
while xml_read_dom; do
# (( CR != 0 )) && break
(( PIPESTATUS[1] != 0 )) && break
if $ITSACOMMENT; then
# oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# if [ "x${COMMENTS:(-2):2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:(-2)}" && ITSACOMMENT=false
# elif [ "x${COMMENTS:(-3):3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:(-3)}" && ITSACOMMENT=false
if [ "x${COMMENTS:${#COMMENTS} - 2:2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 2}" && ITSACOMMENT=false
elif [ "x${COMMENTS:${#COMMENTS} - 3:3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 3}" && ITSACOMMENT=false
fi
$Debug && echo2 "${N}${COMMENTS}${END}"
elif test "${TAG_NAME}"; then
if [ "x${TAG_NAME}x" == "x${tag}x" -o "x${tag}x" == "xanyx" ]; then
if $GETCONTENT; then
CONTENT="$(trim "${CONTENT}")"
test ${CONTENT} && echo "${CONTENT}"
else
# eval local $ATTRIBUTES => eval test "\"\$${attribute}\"" will be true for matching attributes
eval local $ATTRIBUTES
$Debug && (echo2 "${m}${TAG_NAME}: ${M}$ATTRIBUTES${END}"; test ${CONTENT} && echo2 "${m}CONTENT=${M}$CONTENT${END}")
if test "${attributes}"; then
if $MULTIPLE_ATTR; then
# we don't print "tag: attr=x ..." for a tag passed as argument: it's usefull only for "any" tags so then we print the matching tags found
! $LIGHT && [ "x${tag}x" == "xanyx" ] && tag2print="${g6}${TAG_NAME}: "
for attribute in ${attributes}; do
! $LIGHT && attribute2print="${g10}${attribute}${g6}=${g14}"
if eval test "\"\$${attribute}\""; then
test "${tag2print}" && ${print} "${tag2print}"
TAGPRINTED=true; unset tag2print
if [ "$XAPPLY" == "true" -a "${attribute}" == "${XATTRIBUTE}" ]; then
eval ${print} "%s%s\ " "\${attribute2print}" "\${${XAPPLIED_COLOR}}\"\$(\$XCOMMAND \$${attribute})\"\${END}" && eval unset ${attribute}
else
eval ${print} "%s%s\ " "\${attribute2print}" "\"\$${attribute}\"" && eval unset ${attribute}
fi
fi
done
# this trick prints a CR only if attributes have been printed durint the loop:
$TAGPRINTED && ${print} "\n" && TAGPRINTED=false
else
if eval test "\"\$${attributes}\""; then
if $XAPPLY; then
eval echo "\${g}\$(\$XCOMMAND \$${attributes})" && eval unset ${attributes}
else
eval echo "\$${attributes}" && eval unset ${attributes}
fi
fi
fi
else
echo eval $ATTRIBUTES >>$TMP
fi
fi
fi
fi
unset CR TAG_NAME ATTRIBUTES CONTENT COMMENTS
done < "${fileXml}" | ${PROSTPROCESS}
# http://mywiki.wooledge.org/BashFAQ/024
# INFO: I set variables in a "while loop" that's in a pipeline. Why do they disappear? workaround:
if [ -s "$TMP" ]; then
$FORCE_PRINT && ! $LIGHT && cat $TMP
# $FORCE_PRINT && $LIGHT && perl -pe 's/[[:space:]].*?=/ /g' $TMP
$FORCE_PRINT && $LIGHT && sed -r 's/[^\"]*([\"][^\"]*[\"][,]?)[^\"]*/\1 /g' $TMP
. $TMP
rm -f $TMP
fi
unset ITSACOMMENT
}
і нарешті, функції rtrim, trim та echo2 (до stderr):
rtrim() {
local var=$@
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
trim() {
local var=$@
var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
echo2() { echo -e "$@" 1>&2; }
о, і вам знадобиться спочатку визначити акуратні колоризуючі динамічні змінні, які також експортуються:
set -a
TERM=xterm-256color
case ${UNAME} in
AIX|SunOS)
M=$(${print} '\033[1;35m')
m=$(${print} '\033[0;35m')
END=$(${print} '\033[0m')
;;
*)
m=$(tput setaf 5)
M=$(tput setaf 13)
# END=$(tput sgr0) # issue on Linux: it can produces ^[(B instead of ^[[0m, more likely when using screenrc
END=$(${print} '\033[0m')
;;
esac
# 24 shades of grey:
for i in $(seq 0 23); do eval g$i="$(${print} \"\\033\[38\;5\;$((232 + i))m\")" ; done
# another way of having an array of 5 shades of grey:
declare -a colorNums=(238 240 243 248 254)
for num in 0 1 2 3 4; do nn[$num]=$(${print} "\033[38;5;${colorNums[$num]}m"); NN[$num]=$(${print} "\033[48;5;${colorNums[$num]}m"); done
# piped decolorization:
DECOLORIZE='eval sed "s,${END}\[[0-9;]*[m|K],,g"'
Або ви знаєте, як створити функції та завантажити їх через FPATH (ksh) або емуляцію FPATH (bash)
Якщо ні, просто скопіюйте / вставте все в командному рядку.
xml_read [-cdlp] [-x command <-a attribute>] <file.xml> [tag | "any"] [attributes .. | "content"]
-c = NOCOLOR
-d = Debug
-l = LIGHT (no \"attribute=\" printed)
-p = FORCE PRINT (when no attributes given)
-x = apply a command on an attribute and print the result instead of the former value, in green color
(no attribute given will load their values into your shell as $ATTRIBUTE=value; use '-p' to print them as well)
xml_read server.xml title content # print content between <title></title>
xml_read server.xml Connector port # print all port values from Connector tags
xml_read server.xml any port # print all port values from any tags
За допомогою режиму налагодження (-d) коментарі та проаналізовані атрибути друкуються на stderr
./read_xml.sh: line 22: (-1): substring expression < 0
?
[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
Я не знаю жодного інструмента розбору XML чистих оболонок XML. Тож вам, швидше за все, знадобиться інструмент, написаний іншою мовою.
Мій модуль XML :: Twig Perl поставляється з таким інструментом:, xml_grep
де ви, мабуть, напишете те, що хочете як xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt
( -t
опція дає результат як текст, а не xml)
Іншим інструментом командного рядка є мій новий Xidel . Він також підтримує XPath 2 та XQuery, всупереч уже згаданому xpath / xmlstarlet.
Заголовок можна прочитати так:
xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt
І він також має цікаву функцію експорту декількох змінних в bash. Наприклад
eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )
встановлює $title
заголовок та $imgcount
кількість зображень у файлі, які повинні бути настільки ж гнучкими, як і їх аналіз безпосередньо в bash.
Після деяких досліджень щодо перекладу між форматами Linux та Windows шляхів до файлів у файлах XML я знайшов цікаві підручники та рішення щодо:
Хоча існує досить багато готових утиліт консолі, які можуть робити те, що ви хочете, можливо, знадобиться менше часу, щоб написати пару рядків коду на загальній мові програмування, наприклад, Python, до якого ви можете легко розширити і адаптувати ваші потреби.
Ось сценарій python, який використовується lxml
для синтаксичного аналізу - він приймає ім'я файлу чи URL-адреси в якості першого параметра, вираз XPath як другий параметр, і друкує рядки / вузли, що відповідають заданому виразу.
#!/usr/bin/env python
import sys
from lxml import etree
tree = etree.parse(sys.argv[1])
xpath_expression = sys.argv[2]
# a hack allowing to access the
# default namespace (if defined) via the 'p:' prefix
# E.g. given a default namespaces such as 'xmlns="http://maven.apache.org/POM/4.0.0"'
# an XPath of '//p:module' will return all the 'module' nodes
ns = tree.getroot().nsmap
if ns.keys() and None in ns:
ns['p'] = ns.pop(None)
# end of hack
for e in tree.xpath(xpath_expression, namespaces=ns):
if isinstance(e, str):
print(e)
else:
print(e.text and e.text.strip() or etree.tostring(e, pretty_print=True))
lxml
можна встановити за допомогою pip install lxml
. На ubuntu можна використовувати sudo apt install python-lxml
.
python xpath.py myfile.xml "//mynode"
lxml
також приймає URL як вхідний:
python xpath.py http://www.feedforall.com/sample.xml "//link"
Примітка : Якщо у вашому XML є простір імен за замовчуванням без префікса (наприклад
xmlns=http://abc...
), тоді ви повинні використовуватиp
в своїх виразах префікс (наданий 'hack'), наприклад//p:module
для отримання модулів зpom.xml
файлу. Якщоp
префікс вже відображено у вашому XML, вам потрібно буде змінити сценарій, щоб використовувати інший префікс.
Одноразовий сценарій, який виконує вузьку мету вилучення імен модулів з файлу apache maven. Зауважте, як ім'я вузла ( module
) встановлено з простором імен за замовчуванням {http://maven.apache.org/POM/4.0.0}
:
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modules>
<module>cherries</module>
<module>bananas</module>
<module>pears</module>
</modules>
</project>
module_extractor.py :
from lxml import etree
for _, e in etree.iterparse(open("pom.xml"), tag="{http://maven.apache.org/POM/4.0.0}module"):
print(e.text)
pip install
кадром apt-get
або yum
виклику. Дякую!
Метод Юзема можна вдосконалити, перевернувши порядок знаків <
та >
знаків у rdom
функції та призначення змінних, щоб:
rdom () { local IFS=\> ; read -d \< E C ;}
стає:
rdom () { local IFS=\< ; read -d \> C E ;}
Якщо розбір не виконаний так, останній тег у XML-файлі ніколи не досягається. Це може бути проблематично, якщо ви плануєте вивести ще один XML-файл у кінці while
циклу.
Це працює, якщо ви бажаєте атрибутів XML:
$ cat alfa.xml
<video server="asdf.com" stream="H264_400.mp4" cdn="limelight"/>
$ sed 's.[^ ]*..;s./>..' alfa.xml > alfa.sh
$ . ./alfa.sh
$ echo "$stream"
H264_400.mp4
Хоча здається, що "ніколи не розбирайте XML, JSON ... з баш без належного інструменту" - це слушна порада, я не згоден. Якщо це побічна робота, слід шукати належний інструмент, а потім навчитися ... Awk може це зробити за лічені хвилини. Мої програми повинні працювати над усіма згаданими вище та іншими видами даних. Чорт, я не хочу перевіряти 30 інструментів для розбору 5-7-10 різних форматів, які мені потрібні, якщо я можу порушити проблему за лічені хвилини. Мене не хвилює XML, JSON чи інше! Мені потрібно єдине рішення для всіх них.
Як приклад: моя програма SmartHome працює за нашими будинками. Роблячи це, він читає безліч даних у занадто багато різних форматів, які я не можу контролювати. Я ніколи не використовую спеціальні інструменти, оскільки не хочу витрачати більше ніж хвилини на читання потрібних мені даних. Завдяки налаштуванням FS та RS це awk рішення ідеально підходить для будь-якого текстового формату. Але це може бути не правильною відповіддю, коли ваше основне завдання - це працювати в основному з завантаженням даних у такому форматі!
З проблемою розбору XML від bash я зіткнувся вчора. Ось як я це роблю для будь-якого ієрархічного формату даних. Як бонус - я призначаю дані безпосередньо змінним у скрипті bash.
Щоб полегшити читання тонких, я представлятиму рішення поетапно. З даних тестування OP я створив файл: test.xml
Парсинг сказав XML у bash та витяг даних у 90 символів:
awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml
Я зазвичай використовую більш читану версію, оскільки її в реальному житті простіше змінити, оскільки мені часто потрібно тестувати інакше:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml
Мені все одно, як називається формат. Я шукаю лише найпростішого рішення. У цьому конкретному випадку я бачу з даних, що новий рядок - це роздільник записів (RS) та <> розділення полів (FS). У моєму первісному випадку у мене складно було індексувати 6 значень у двох записах, пов’язаних з ними, виявити, коли дані існують плюс поля (записи) можуть або не існують. Щоб вирішити проблему, було потрібно 4 рядки awk. Отже, адаптуйте ідею до кожної потреби перед її використанням!
Друга частина просто виглядає, що є потрібна рядок у рядку (RS), і якщо так, виводиться необхідні поля (FS). Вищезазначене зайняло у мене близько 30 секунд, щоб скопіювати та адаптувати з останньої команди, яку я використав цим способом (у 4 рази довше). І це все! Вчинено в 90 символів.
Але мені завжди потрібно акуратно ввести дані в змінні в моєму сценарії. Я спочатку тестую такі конструкції:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml
У деяких випадках я використовую printf замість print. Коли я бачу, що все виглядає добре, я просто закінчую присвоєння значень змінним. Я знаю, що багато хто думає, що "eval" - це "зло", не потрібно коментувати :) Трюк роками чудово працює у всіх чотирьох моїх мережах. Але продовжуйте вчитися, якщо ви не розумієте, чому це може бути поганою практикою! Включаючи задані зміни bash і достатній пробіл, для мого рішення потрібно 120 символів, щоб зробити все.
eval $( awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml ); echo "host: $host, username: $username, password: $password dbname: $dbname"