3

I have a file delimited with space and random column ordering as follows:

name=Joan age=42 ip=172.20.1.80 sex=M loc=UK 
loc=IR sex=F ip=172.20.1.1 age=32 name=Sandra 

I want to extract specific fields (name, loc, and ip) only.

So the result that I'm looking for is as follows:

Joan|UK|172.20.1.80
Sandra|IR|172.20.1.1
2
  • Similar (if you just replace the space with newline), stackoverflow.com/questions/16571739/…
    – michael
    Jan 27, 2019 at 13:04
  • @michael Not quite. You need something to detect the different "blocks" then if the input has more that one line.
    – PerlDuck
    Jan 27, 2019 at 13:12

3 Answers 3

4

Luckily, your input file has a format the shell understands when it comes to assigning variables a value: var1=value1 var2=value2 etc. So we can simply read each line and use the eval command to evaluate the line.

Put the following into a file, say parse.sh, do chmod +x parse.sh and run it with your input file as a parameter.

Script parse.sh:

#!/usr/bin/env bash

while read line; do
    eval $line;
    echo "$name|$loc|$ip"
done < "$1"

exit 0;

File input.txt:

name=Joan age=42 ip=172.20.1.80 sex=M loc=UK
loc=IR sex=F ip=172.20.1.1 age=32 name=Sandra

Run:

me@ubuntu:~> ./parse.sh input.txt 
Joan|UK|172.20.1.80
Sandra|IR|172.20.1.1

Please note that the values must not have a space in them. E.g.

ip=... name=Ubai salih loc=...

would not work and give syntax errors. Also, if the input file would contain a line with a bad_command that command gets executed because that is how eval works: it just executes the given string.

4
  • 2
    Die to the risk if code injection (malicious or accidentally), I might prefer a different solution.
    – michael
    Jan 27, 2019 at 13:06
  • 1
    @michael Yes, you are right. I added a note to the answer to mention that. Another possibility would be to split at spaces and then at =. Or pick name=\S+ etc. with a regex. Feel invited to post another approach ;-)
    – PerlDuck
    Jan 27, 2019 at 13:09
  • 1
    @michael *Due to the risk *of ... Too late to edit the comment I know, but just wanted to mention the typos in case anyone else is confused.
    – wjandrea
    Jan 27, 2019 at 21:16
  • Oh goodness, sorry, I just noticed the typo (I blame autocorrect). Code injection is bad, but only on rare occasion lethal.
    – michael
    Jan 28, 2019 at 14:15
1

FWIW, here's a Python solution like PerlDuck's Bash solution, but not evaluating the input.

#!/usr/bin/env python3

import fileinput

for line in fileinput.input():
    record = line.rstrip('\n')
    d = dict(kv.split('=') for kv in record.split(' '))
    print(d['name'], d['loc'], d['ip'], sep='|')

Run:

$ ./parse.py input.txt
Joan|UK|172.20.1.80
Sandra|IR|172.20.1.1
0

Since the output order that you want is reverse lexical (name > loc > ip) you could select and then reverse sort the fields, then remove the fieldname= prefixes. For example in Perl:

$ perl -alne '
    print join "|", map { s/.*=//r } reverse sort grep { /^(name|loc|ip)=/ } @F
' file
Joan|UK|172.20.1.80
Sandra|IR|172.20.1.1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .