Idealmente, o que eu gostaria de poder fazer é:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
Idealmente, o que eu gostaria de poder fazer é:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
Respostas:
Esta é realmente apenas uma explicação da resposta de Yuzem , mas não achei que essa edição devesse ser feita para outra pessoa e os comentários não permitem a formatação, então ...
rdom () { local IFS=\> ; read -d \< E C ;}
Vamos chamar de "read_dom" em vez de "rdom", espaçá-lo um pouco e usar variáveis mais longas:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
}
Ok, então define uma função chamada read_dom. A primeira linha torna o IFS (o separador de campos de entrada) local para essa função e o altera para>. Isso significa que, quando você lê dados, em vez de serem automaticamente divididos no espaço, tabulação ou novas linhas, eles são divididos em '>'. A próxima linha diz para ler a entrada de stdin e, em vez de parar em uma nova linha, pare quando vir um caractere '<' (o sinalizador -d para deliminador). O que é lido é então dividido usando o IFS e atribuído à variável ENTITY e CONTENT. Então, faça o seguinte:
<tag>value</tag>
A primeira chamada para read_dom
obter uma sequência vazia (já que o '<' é o primeiro caractere). Isso é dividido pelo IFS em apenas '', pois não há um caractere '>'. O Read então atribui uma string vazia às duas variáveis. A segunda chamada obtém a string 'tag> value'. Isso é dividido pelo IFS nos dois campos 'tag' e 'value'. O Read então atribui as variáveis como: ENTITY=tag
e CONTENT=value
. A terceira chamada obtém a string '/ tag>'. Isso é dividido pelo IFS nos dois campos '/ tag' e ''. O Read então atribui as variáveis como: ENTITY=/tag
e CONTENT=
. A quarta chamada retornará um status diferente de zero, porque chegamos ao final do arquivo.
Agora, o loop while se limpou um pouco para coincidir com o acima:
while read_dom; do
if [[ $ENTITY = "title" ]]; then
echo $CONTENT
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
A primeira linha diz apenas "enquanto a função read_dom retorna um status zero, faça o seguinte." A segunda linha verifica se a entidade que acabamos de ver é "title". A próxima linha faz eco do conteúdo da tag. A linha quatro sai. Se não fosse a entidade do título, o loop será repetido na sexta linha. Nós redirecionamos "xhtmlfile.xhtml" para a entrada padrão (para a read_dom
função) e redirecionamos a saída padrão para "titleOfXHTMLPage.txt" (o eco do início do loop).
Agora, dê o seguinte (semelhante ao que você obtém ao listar um bucket no S3) para 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>
e o seguinte loop:
while read_dom; do
echo "$ENTITY => $CONTENT"
done < input.xml
Voce deveria pegar:
=>
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 =>
Então, se escrevemos um while
loop como o de Yuzem:
while read_dom; do
if [[ $ENTITY = "Key" ]] ; then
echo $CONTENT
fi
done < input.xml
Obteríamos uma lista de todos os arquivos no bucket S3.
EDIT
Se, por algum motivo local IFS=\>
, não funcionar para você e você configurá-lo globalmente, você deve redefini-lo no final da função, como:
read_dom () {
ORIGINAL_IFS=$IFS
IFS=\>
read -d \< ENTITY CONTENT
IFS=$ORIGINAL_IFS
}
Caso contrário, qualquer linha dividida posteriormente no script será confusa.
EDIT 2
Para dividir os pares de nome / valor de atributo, você pode aumentar da seguinte read_dom()
maneira:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local ret=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $ret
}
Em seguida, escreva sua função para analisar e obter os dados desejados assim:
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
}
Então, enquanto você read_dom
liga para parse_dom
:
while read_dom; do
parse_dom
done
Depois, dê o seguinte exemplo de marcação:
<example>
<bar size="bar_size" type="metal">bars content</bar>
<foo size="1789" type="unknown">foos content</foo>
</example>
Você deve obter esta saída:
$ cat example.xml | ./bash_xml.sh
bar type is: metal
foo size is: 1789
EDIT 3 outro usuário disse que estava tendo problemas com ele no FreeBSD e sugeriu salvar o status de saída da leitura e devolvê-lo no final do read_dom como:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local RET=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $RET
}
Não vejo nenhuma razão para que isso não funcione
IFS=\< read ...
que definirá apenas o IFS para a chamada de leitura. (Note que eu estou em nenhuma maneira endossando a prática do uso read
de xml de análise, e eu acredito que isso é muito perigoso e deve ser evitado.)
Você pode fazer isso muito facilmente usando apenas o bash. Você só precisa adicionar esta função:
rdom () { local IFS=\> ; read -d \< E C ;}
Agora você pode usar o rdom como read, mas para documentos html. Quando chamado, rdom atribuirá o elemento à variável E e o conteúdo à var C.
Por exemplo, para fazer o que você queria:
while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
As ferramentas de linha de comando que podem ser chamadas a partir de scripts de shell incluem:
Também uso xmllint e xsltproc com pequenos scripts de transformação XSL para processar XML a partir da linha de comando ou em scripts de shell.
Você pode usar o utilitário xpath. Está instalado com o pacote Perl XML-XPath.
Uso:
/usr/bin/xpath [filename] query
ou XMLStarlet . Para instalá-lo no opensuse, use:
sudo zypper install xmlstarlet
ou tente cnf xml
em outras plataformas.
xpath
que vem pré-instalado não é adequado para uso como componente em scripts. Veja, por exemplo, stackoverflow.com/questions/15461737/… para uma elaboração.
apt-get install xmlstarlet
Isso é suficiente ...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
apt-get install libxml-xpath-perl
.
Confira o XML2 em http://www.ofb.net/~egnor/xml2/, que converte XML em um formato orientado a linhas.
a partir da resposta do chad, aqui está a solução de trabalho COMPLETA para analisar UML, com manipulação apropriada de comentários, com apenas duas pequenas funções (mais de 2 bu, você pode misturar todas). Não digo que o chad não funcionou, mas houve muitos problemas com arquivos XML mal formatados: então você precisa ser um pouco mais complicado para lidar com comentários e espaços extraviados / CR / TAB / etc.
O objetivo desta resposta é fornecer funções bash prontas para o uso, prontas para uso, para qualquer pessoa que precise analisar a UML sem ferramentas complexas usando perl, python ou qualquer outra coisa. Quanto a mim, não consigo instalar o cpan, nem os módulos perl para o antigo sistema operacional de produção em que estou trabalhando, e o python não está disponível.
Primeiro, uma definição das palavras UML usadas neste post:
<!-- comment... -->
<tag attribute="value">content...</tag>
EDIT: funções atualizadas, com alça de:
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
}
e o segundo:
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
}
e, finalmente, as funções rtrim, trim e echo2 (to 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; }
ah, e você precisará de algumas variáveis dinâmicas de cores limpas para serem definidas primeiro e exportadas também:
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"'
Você sabe como criar funções e carregá-las via FPATH (ksh) ou uma emulação de FPATH (bash)
Caso contrário, basta copiar / colar tudo na linha de comando.
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
Com o modo Debug (-d), comentários e atributos analisados são impressos no stderr
./read_xml.sh: line 22: (-1): substring expression < 0
:?
[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
Não conheço nenhuma ferramenta de análise XML de shell puro. Então você provavelmente precisará de uma ferramenta escrita em outro idioma.
Meu módulo XML :: Twig Perl vem com uma ferramenta: xml_grep
onde você provavelmente escreveria o que deseja xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt
(a -t
opção fornece o resultado como texto em vez de xml)
Outra ferramenta de linha de comando é o meu novo Xidel . Ele também suporta XPath 2 e XQuery, ao contrário do xpath / xmlstarlet já mencionado.
O título pode ser lido como:
xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt
E também possui um recurso interessante para exportar várias variáveis para o bash. Por exemplo
eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )
define $title
o título e $imgcount
o número de imagens no arquivo, que deve ser tão flexível quanto analisá-lo diretamente no bash.
Após alguma pesquisa para tradução entre os formatos Linux e Windows dos caminhos dos arquivos XML, encontrei tutoriais e soluções interessantes sobre:
Embora existam alguns utilitários de console prontos que podem fazer o que você deseja, provavelmente levará menos tempo para escrever algumas linhas de código em uma linguagem de programação de uso geral, como o Python, na qual você pode estender e adaptar facilmente suas necessidades.
Aqui está um script python usado lxml
para análise - ele pega o nome de um arquivo ou uma URL como o primeiro parâmetro, uma expressão XPath como o segundo parâmetro e imprime as strings / nós correspondentes à expressão especificada.
#!/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
pode ser instalado com pip install lxml
. No ubuntu você pode usar sudo apt install python-lxml
.
python xpath.py myfile.xml "//mynode"
lxml
também aceita um URL como entrada:
python xpath.py http://www.feedforall.com/sample.xml "//link"
Nota : Se o seu XML tiver um espaço para nome padrão sem prefixo (por exemplo
xmlns=http://abc...
), você deverá usar op
prefixo (fornecido pelo 'hack') em suas expressões, por exemplo,//p:module
para obter os módulos de umpom.xml
arquivo. Caso op
prefixo já esteja mapeado em seu XML, será necessário modificar o script para usar outro prefixo.
Um script único que serve ao propósito restrito de extrair nomes de módulos de um arquivo apache maven. Observe como o nome do nó ( module
) é prefixado com o espaço para nome padrão {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
excesso extra . Obrigado! apt-get
yum
O método de Yuzem pode ser aprimorado invertendo a ordem dos sinais <
e >
na rdom
função e nas atribuições de variáveis, de modo que:
rdom () { local IFS=\> ; read -d \< E C ;}
torna-se:
rdom () { local IFS=\< ; read -d \> C E ;}
Se a análise não for feita assim, a última tag no arquivo XML nunca será alcançada. Isso pode ser problemático se você pretende gerar outro arquivo XML no final do while
loop.
Isso funciona se você estiver querendo atributos 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
Embora pareça que "nunca analise XML, JSON ... do bash sem uma ferramenta adequada" é um bom conselho, discordo. Se esse é um trabalho paralelo, é essencial procurar a ferramenta adequada e aprender ... O Awk pode fazê-lo em minutos. Meus programas precisam trabalhar com todos os tipos de dados mencionados acima e mais. Inferno, eu não quero testar 30 ferramentas para analisar 5-7-10 formatos diferentes que eu preciso se puder resolver o problema em questão de minutos. Eu não ligo para XML, JSON ou o que quer! Eu preciso de uma solução única para todos eles.
Como exemplo: meu programa SmartHome administra nossas casas. Ao fazê-lo, ele lê uma infinidade de dados em muitos formatos diferentes que não consigo controlar. Eu nunca uso ferramentas dedicadas e adequadas, pois não quero gastar mais do que minutos lendo os dados necessários. Com os ajustes FS e RS, essa solução awk funciona perfeitamente para qualquer formato de texto. Mas, pode não ser a resposta correta quando sua tarefa principal é trabalhar principalmente com cargas de dados nesse formato!
O problema de analisar XML do bash que eu enfrentei ontem. Aqui está como eu faço isso para qualquer formato de dados hierárquico. Como um bônus - eu atribuo dados diretamente às variáveis em um script bash.
Para facilitar a leitura, apresentarei a solução em etapas. A partir dos dados de teste do OP, criei um arquivo: test.xml
Analisando o referido XML no bash e extraindo os dados em 90 caracteres:
awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml
Normalmente, uso uma versão mais legível, pois é mais fácil modificar na vida real, pois geralmente preciso testar de forma diferente:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml
Eu não me importo como é chamado o formato. Eu busco apenas a solução mais simples. Nesse caso em particular, posso ver pelos dados que newline é o separador de registros (RS) e <> delimit fields (FS). No meu caso original, eu tinha uma indexação complicada de 6 valores em dois registros, relacionando-os, descobrindo quando os dados existem mais campos (registros) podem ou não existir. Foram necessárias 4 linhas de awk para resolver o problema perfeitamente. Então, adapte a idéia a cada necessidade antes de usá-la!
A segunda parte simplesmente parece que há uma sequência desejada em uma linha (RS) e, se houver, imprime os campos necessários (FS). O processo acima levou cerca de 30 segundos para copiar e adaptar do último comando que usei dessa maneira (quatro vezes mais). E é isso aí! Feito em 90 caracteres.
Mas sempre preciso inserir os dados ordenadamente em variáveis no meu script. Primeiro testei as construções da seguinte forma:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml
Em alguns casos, uso printf em vez de print. Quando vejo que tudo parece bem, simplesmente termino de atribuir valores a variáveis. Eu sei que muitos pensam que "eval" é "mau", não há necessidade de comentar :) Trick funciona perfeitamente em todas as minhas quatro redes há anos. Mas continue aprendendo se você não entende por que isso pode ser uma prática ruim! Incluindo atribuições de variáveis bash e amplo espaçamento, minha solução precisa de 120 caracteres para fazer tudo.
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"