How to parse xml sibling element value with SED [duplicate] - linux

This question already has answers here:
How to parse XML using shellscript? [duplicate]
(10 answers)
Closed 7 years ago.
I have a xml file which contains following text:
<Cluster>
<Name>CLS_20</Name>
<JMXUserName>admin</JMXUserName>
<JMXPassword>mypwd</JMXPassword>
</Cluster>
<Server>
<Name>Server_20</Name>
<IpAddress>a.b.c.d</IpAddress>
<Port>1234</Port>
</Server>
<Server>
<Name>Server_21</Name>
<IpAddress>e.f.g.h</IpAddress>
<Port>1234</Port>
</Server>
I have the IP address of the server (a.b.c.d)
I want to retrieve name of the server (Server_20)
How can this be achieved with SED
Or with any other linux command.
Please help.

Sed is to use with caution on xml due to lot of variation in structure but based on this sample
sed -n '
/<Server>/ h
/<Server>/,\#</Server># {
H
\#</Server># {
x
s#.*<Name>\([^<]*\)<.*IpAddress>a.b.c.d<.*#\1#p
}
}' YourFile
Principle:
don't print unless explicitly asked
load section Server in buffer
when reaching end of section
change the whole by keeping only name attribut if the ipadresse value is the same as a.b.c and in this case print the result

Do not do this with sed; it will break horribly when benign formatting changes happen to the XML.
Use a proper XML-parsing tool. For example with xmlstarlet:
xmlstarlet sel -t -c '//Server[IpAddress="a.b.c.d"]/Name/node()' -n filename.xml
or with xmllint:
xmllint --xpath '//Server[IpAddress="a.b.c.d"]/Name/node()' filename.xml
or with old versions of xmllint that don't yet understand --xpath (if you are tempted to use this I encourage you to look at other tools):
echo 'cat //Server[IpAddress="a.b.c.d"]/Name/node()' | xmllint --shell filename.xml | sed '1d;$d'
or with the xpath utility from the XML::XPath Perl library:
xpath -q -e '//Server[IpAddress="a.b.c.d"]/Name/node()' filename.xml
...or with any of three dozen (dozen) other XML tools.
The heart of this is the XPath expression //Server[IpAddress="a.b.c.d"]/Name/node(). This consists of:
//Server refers to a Server node anywhere in the document
//Server/Name refers to a Name node that is the child of such a Server node
//Server/Name/node() refers to the contents of such a Name node
//Server[IpAddress="a.b.c.d"] refers to a server node that satisfies the condition IpAddress="a.b.c.d", which means that it has a child IpAddress node that contains a.b.c.d
Putting all that together, //Server[IpAddress="a.b.c.d"]/Name/node() refers to the contents of a Name node that is the child of a Server node anywhere in the document that has an IpAddress child node that contains a.b.c.d.

Related

parsing the JSON string with only mandatory POSIX tools [duplicate]

This question already has answers here:
Parsing JSON with Unix tools
(45 answers)
Closed 24 days ago.
I have json string like this
{"state":{"stateId":0,"nextPollingTime":null,"updateState":null},"config":{"privateIPs":null,"64Bit":false,"silent":false,"iid":14,"selfp":null,"sevlfp":null,"av":14,"aid":null,"aty":2,"sev":0,"seci":0,"sti":false,"scto":60000,"sci":5000,"stkd":5000,"sud":5000,"mpfa":3}}
I need to get "state" and "config" keys content to a shell custom variable or to a file.
tried with a command:
$RESPONSE is the API response, the above json string
echo $RESPONSE | sed 's/{"$state":"*\([0-9a-zA-Z]*\)"*,*.*}/\1/'
this prints nothing but suppose to get this output:
{"stateId":0,"nextPollingTime":null,"updateState":null}
Tried with saving the response to tmp file and executed this command
cat /tmp/a.json | grep -o -e "{"state":.*}"
this also print empty string but expected result:
{"stateId":0,"nextPollingTime":null,"updateState":null}
Am new to shell script and trying with various options available in the internet, please help me to write the command for the same.
This uses any sed:
$ sed 's/.*"state":\({[^}]*}\).*/\1/' file
{"stateId":0,"nextPollingTime":null,"updateState":null}
That will work for your posted input but fail given other json input (e.g. with "state" as a value or with } inside a value), just like any other solution that uses mandatory POSIX tools as none of them have a JSON parser (unless you write one with awk, but I expect that's more work than you or anyone here would put into this).

"Cat" into multiple files using brace expansion

I am quite new to bash and trying to type some text into multiple files with a single command using brace expansion.
I tried: cat > file_{1..100} to write into 100 files some text that I will type in the terminal. I get the following error:
bash: file_{1..100}: ambiguous redirect
I also tried: cat > "file_{1..100}" but that creates a singe file named: file_{1..100}.
I tried: cat > `file_{1..100}` but that gives the error:
file_1: command not found
How can I achieve this using brace expansion? Maybe there are other ways using other utilities and/or pipelines. But I want to know if that is possible using only simple brace expansion or not.
You can't do this with cat alone. It only writes its output to its standard output, and that single file descriptor can only be associated with a single file.
You can however do it with tee file_{1..100}.
You may wish to consider using tee file_{01..100} instead, so that the filenames are zero-padded to all have the same width: file_001, file_002, ... This has the advantage that lexicographic order will agree with numerical order, and so ls, *, etc, will process them in numerical order. Without this, you have the situation that file_2 comes after file_10 in lexicographic order.
target could be only a pipe, not a multiple files.
If you want redirect output to multiple files, use tee
cat | tee file_{1..100}
Don't forget to check man tee, for example if you want to append to the files, you should add -a option (tee -a file_{1..100})
This types the string or text into file{1..4}
echo "hello you just knew me by kruz" > file{1..4}
Use to remove them
rm file*

Use $HOSTNAME in sed substitution [duplicate]

This question already has answers here:
Replace a string in shell script using a variable
(12 answers)
Closed 8 years ago.
I want to use my Centos VM's $HOSTNAME inside a call to sed like so:
sed -i 's/Apache 2 Test Page/$HOSTNAME Test Page/g' /var/www/error/noindex.html
But this just replaces Apache 2 Test Page with $HOSTNAME Test Page... I know I can do it manually, but I've got a good reason for wishing to do it this way.
FWIW, I'm actually doing this inside a bash script that gets called from a Vagrantfile, which in turn provisions multiple VM's (each with Apache2 installed), so that I can test-out the load-balancing capabilities of HAProxy. so I just want a simple way to differentiate between my 3 web-servers, and II figure that modifying the default Apache page is the easiest way to do that.
Enclose sed's s/// with double quotes instead of single quotes for variable expansion.
sed -i "s/Apache 2 Test Page/$HOSTNAME Test Page/g" /var/www/error/noindex.html
Example:
$ echo 'foo Apache 2 Test Page bar' | sed "s/Apache 2 Test Page/$HOSTNAME Test Page/g"
foo avinash-Lenovo-IdeaPad-Z500 Test Page bar

egrep command with piped variable in ssh throwing No Such File or Directory error

Ok, here I'm again, struggling with ssh. I'm trying to retrieve some data from remote log file based on tokens. I'm trying to pass multiple tokens in egrep command via ssh:
IFS=$'\n'
commentsArray=($(ssh $sourceUser#$sourceHost "$(egrep "$v" /$INSTALL_DIR/$PROP_BUNDLE.log)"))
echo ${commentsArray[0]}
echo ${commentsArray[1]}
commax=${#commentsArray[#]}
echo $commax
where $v is something like below but it's length is dynamic. Meaning it can have many file names seperated by pipe.
UserComments/propagateBundle-2013-10-22--07:05:37.jar|UserComments/propagateBundle-2013-10-22--07:03:57.jar
The output which I get is:
oracle#172.18.12.42's password:
bash: UserComments/propagateBundle-2013-10-22--07:03:57.jar/New: No such file or directory
bash: line 1: UserComments/propagateBundle-2013-10-22--07:05:37.jar/nouserinput: No such file or directory
0
Thing worth noting is that my log file data has spaces in it. So, in the code piece I've given, the actual comments which I want to extract start after the jar file name like : UserComments/propagateBundle-2013-10-22--07:03:57.jar/
The actual comments are 'New Life Starts here' but the logs show that we are actually getting it till 'New' and then it breaks at space. I tried giving IFS but of no use. Probably I need to give it on remote but I don't know how should I do that.
Any help?
Your command is trying to run the egrep "$v" /$INSTALL_DIR/$PROP_BUNDLE.log on the local machine, and pass the result of that as the command to run via SSH.
I suspect that you meant for that command to be run on the remote machine. Remove the inner $() to get that to happen (and fix the quoting):
commentsArray=($(ssh $sourceUser#$sourceHost "egrep '$v' '/$INSTALL_DIR/$PROP_BUNDLE.log'"))
You should use fgrep to avoid regex special interpretation from your input:
commentsArray=($(ssh $sourceUser#$sourceHost "$(fgrep "$v" /$INSTALL_DIR/$PROP_BUNDLE.log)"))

Need help coloring/replacing arbitrary strings using Bash and sed

I'm using a bash script based on the technique used here: Get color output in bash to color the output of my builds and other scripts to make things easier to read. One of the steps in my build executes a "git pull" and the git server spits out a "welcome" string like this amidst a bunch of other output:
** WARNING: THIS IS A PRIVATE NETWORK. UNAUTHORIZED ACCESS IS PROHIBITED. **
Use of this system constitutes your consent to interception, monitoring,
and recording for official purposes of information related to such use,
including criminal investigations.
I'd like to color this specific message yellow or possibly delete it from the output while leaving the rest of the output alone. I've tried to replace a simple string like this:
WelcomeMessage="WARNING"
pathpat=".*"
ccred=$(echo -e "\033[0;31m")
ccyellow=$(echo -e "\033[0;33m")
ccend=$(echo -e "\033[0m")
git pull 2>&1 | sed -r -e "/$WelcomeMessage/ s%$pathpat%$ccyellow&$ccend%g"
The first line of the welcome string is colored yellow as expected but the rest of the lines are not. I'd really like to color the exact welcome string and only that string but for many reasons, this doesn't work:
WelcomeMessage="** WARNING: THIS IS A PRIVATE NETWORK. UNAUTHORIZED ACCESS IS PROHIBITED. **
Use of this system constitutes your consent to interception, monitoring,
and recording for official purposes of information related to such use,
including criminal investigations."
pathpat=".*"
ccred=$(echo -e "\033[0;31m")
ccyellow=$(echo -e "\033[0;33m")
ccend=$(echo -e "\033[0m")
git pull 2>&1 | sed -r -e "/$WelcomeMessage/ s%$pathpat%$ccyellow&$ccend%g"
This fails with the error: sed: -e expression #1, char 78: unterminated address regex
I've looked at a couple other questions and I was able to get the asterisks escaped (by preceding them with backslashes) but I'm baffled by the periods and multiple lines. I'd like to continue using sed to solve this problem since it integrates nicely with the colorizing solution.
Any help is appreciated. Thanks!
The following will colorize in yellow every line from the first instance of ** to the first instance of a period . that's not on the same line. This will match the entire warning message as written.
NORMAL=$(tput sgr0)
YELLOW=$(tput setaf 3)
git pull 2>&1 | sed "/\*\*/,/\./s/.*/$YELLOW&$NORMAL/"
Note: If you want to delete the message you can use this:
git pull 2>&1 | sed '/\*\*/,/\./d'

Resources