6

I have a file abc.txt the contents are

<classpathentry kind="src" path="Sources"/>
<classpathentry kind="con" path="WOFramework/ERExtensions"/>
<classpathentry kind="con" path="WOFramework/ERJars"/>
<classpathentry kind="con" path="WOFramework/ERPrototypes"/>
<classpathentry kind="con" path="WOFramework/JavaEOAccess"/>
<classpathentry kind="con" path="WOFramework/JavaEOControl"/>
<classpathentry kind="con" path="WOFramework/JavaFoundation"/>
<classpathentry kind="con" path="WOFramework/JavaJDBCAdaptor"/>

I want to copy all the paths into another file. That is I want my output text file to look like:

    WOFramework/ERExtensions
    WOFramework/ERJars
    WOFramework/ERPrototypes
    WOFramework/JavaEOAccess
    WOFramework/JavaEOControl
    WOFramework/JavaFoundation
    WOFramework/JavaJDBCAdaptor
4
  • you want to copy depending on kind? Commented May 27, 2014 at 18:06
  • 2
    Looks like you're trying to extract parts of an XML document. Try an XML tool such as xmlstarlet or xmllint. stackoverflow.com/questions/91791/…
    – Mikel
    Commented May 27, 2014 at 19:44
  • 5
    cut -d '"' -f4? Commented May 27, 2014 at 20:02
  • 1
    @StephaneChazelas, your answer should be the best solution :)
    – Ramesh
    Commented May 27, 2014 at 20:04

10 Answers 10

8

I assume the file follows the same pattern. If that is the case, you can have a command like below.

grep -o ' path=.*$' file.txt | cut -c8- |rev | cut -c 4- | rev

So, I open the file using cat and then I extract only the characters from path= and then I remove the unwanted characters using cut and then I use the rev technique to remove unwanted characters from the end.

Another awk approach

awk -F'path="' '{print $2}' file.txt |rev | cut -c 4- | rev

I use the path=" as delimiter and print all the information after it. And the rev basically does the same as above.

Testing

cat file.txt
<classpathentry kind="src" path="Sources"/>
<classpathentry kind="con" path="WOFramework/ERExtensions"/>
<classpathentry kind="con" path="WOFramework/ERJars"/>
<classpathentry kind="con" path="WOFramework/ERPrototypes"/>
<classpathentry kind="con" path="WOFramework/JavaEOAccess"/>
<classpathentry kind="con" path="WOFramework/JavaEOControl"/>
<classpathentry kind="con" path="WOFramework/JavaFoundation"/>
<classpathentry kind="con" path="WOFramework/JavaJDBCAdaptor"/>

After running the command,

Sources
WOFramework/ERExtensions
WOFramework/ERJars
WOFramework/ERPrototypes
WOFramework/JavaEOAccess
WOFramework/JavaEOControl
WOFramework/JavaFoundation
WOFramework/JavaJDBCAdaptor

A better approach as provided by Stephane in comments.

cut -d '"' -f4 file.txt
2
  • He doesn't want "Sources"
    – mikeserv
    Commented May 27, 2014 at 19:37
  • Actually, I guess he accepted it - so what do I know?
    – mikeserv
    Commented May 27, 2014 at 19:58
7

A simple approach with awk:

awk -F\" '/WOF/ {print $4}' abc.txt > outfile
  • -F\" changes the field separator from the default (a space) to a quote mark (escaped with \)
  • /WOF/ restricts the returned results of each record (line of the file) to those that match the pattern: WOF
  • $4 is the fourth field for each of those matching records, the path.
4

Another approach with grep and cut:

grep "kind=\"con\"" sample.txt | cut -d \" -f 4 > sample_edited.txt

This will grep all lines containing kind="con" and print the paths by setting cut's delimiter to ".

4
sed -n '/.*="con"[^"]*./{s///;s/..>//p}' <<\DATA

<classpathentry kind="src" path="Sources"/>
<classpathentry kind="con" path="WOFramework/ERExtensions"/>
<classpathentry kind="con" path="WOFramework/ERJars"/>
<classpathentry kind="con" path="WOFramework/ERPrototypes"/>
<classpathentry kind="con" path="WOFramework/JavaEOAccess"/>
<classpathentry kind="con" path="WOFramework/JavaEOControl"/>
<classpathentry kind="con" path="WOFramework/JavaFoundation"/>
<classpathentry kind="con" path="WOFramework/JavaJDBCAdaptor"/>
DATA

OUTPUT

WOFramework/ERExtensions
WOFramework/ERJars
WOFramework/ERPrototypes
WOFramework/JavaEOAccess
WOFramework/JavaEOControl
WOFramework/JavaFoundation
WOFramework/JavaJDBCAdaptor

This should get only the WO... stuff, I think. It's also fully portable.

3

Another solution if your version of grep supports PCRE-style lookarounds

grep -oP '(?<=kind="con" path=").+?(?="/>)' abc.txt
2

With sed

sed -e 's/.*path="//' -e 's:"/>$::' abc.txt > output_file
4
  • sed -e 's/.*path="//' -e 's:".*$::' abc.txt > output_file -- dropping everything after the last quote instead of specific matching at the end.
    – mdpc
    Commented May 27, 2014 at 20:01
  • it display extra lines. Commented May 28, 2014 at 5:52
  • @AvinashRaj, where are you seeing extra lines in the OP's input data? The answer above is tailored to the OP's data.
    – iruvar
    Commented May 28, 2014 at 17:38
  • it displays sources also according to the op's input. Commented May 28, 2014 at 18:05
2

If the file format is really fixed, then the approach below is not as efficient as many of the other answers you already got.

So this is in the case that the file format changes or can not be relied upon (or turns out it can't be relied upon after doing a "brute force" extraction and coming up with "paths" such as kind=). Unfortunately, my experience is that "constant and guaranteed" formats simply aren't. Or not for long.

First you convert all the tags into newlines, so that you don't have to worry about multiple tags in one line or text disposition.

tr "<>" "\n\n" < source.txt

then you select the rows containing the single word "path" followed by space(s) and an equal sign

| grep "\<path\\s*="

from these lines, you extract the "path" component; this way you do not have to worry about what happens if some tags have attributes in a slightly different format

| sed -e 's/.*path\s*=\s*"\([^"]*\)".*/\1/'
# You can modify the above to handle single quotes as well as double quotes
# using [\'"] instead of "

and finally, possibly, you may want to get each path only once

| sort | uniq

Wrapping it in a single line,

tr "<>" "\n\n" < source.txt | grep "\<path\\s*=" | sed -e 's/.*path\s*=\s*"\([^"]*\)".*/\1/' | sort | uniq > output.txt
2

Since nobody has posted one yet, here are a few Perl solutions:

perl -ne  's/.*con.*="(.+)".*/$1/ && print' file

Explanation

The -ne means "Read the input file line by line and apply the script passed by -e". The s/foo/bar/ is the substitution operator, it will replace foo with bar. In this case, the replacement will be whatever was matched in the parentheses, this is $1. The regex means "match everything up to con, then the longest string until a = and then capture everything between the quotes. The && print will print the modified line only if the replacement was successful.

perl -e  'print grep{s/.*con.*=.(.+)".*/$1/}<>' file

Explanation

This one is a bit more idiomatic. It will print the result of applying the same substitution as used above to each line of the input file (<>). Just a different way of writing the same basic approach.

perl -F'[="]' -lane 'print $F[5] if $F[2]=~/con/' file

Explanation

The -a makes perl behave like awk, it automatically splits the input line into fields (saved as the @F array) on the character(s) passed by the -F parameter. Since I tell it to split on = or ", the 5th field will be what we're after and it is only printed if the 2nd field matches con. The -l adds a newline to each print call (and other things that are not relevant).


And here's another grep one. This will print all matches of letters/letters, it works correctly on your example but might not on a more complex one:

grep -Eio '[a-z]+/[a-z]+' file

And a pure shell one (bash/zsh/ksh):

while IFS='=' read a b c; do 
    [[ "$b" =~ "con" ]] && a=${c/%?\/>/} && echo ${a/#?}; 
done <  file 

Explanation

The while read; do ... ; done < file loops through each line of the file. Setting IFS to = splits each line at = and read a b c saves each field in the variables $a through $c. Then, if $b matches con, the last three characters are removed from $c and the result is saved as $a and then printed with the first character (the quote) removed. See here for more on bash's string manipulation options.

1

And the one through GNU sed's backreferencing,

sed -nr 's/^.*kind=\"con\" path=\"([^"]*)\".*$/\1/p' file

Example:

$ cat aa
<classpathentry kind="src" path="Sources"/>
<classpathentry kind="con" path="WOFramework/ERExtensions"/>
<classpathentry kind="con" path="WOFramework/ERJars"/>
<classpathentry kind="con" path="WOFramework/ERPrototypes"/>
<classpathentry kind="con" path="WOFramework/JavaEOAccess"/>
<classpathentry kind="con" path="WOFramework/JavaEOControl"/>
<classpathentry kind="con" path="WOFramework/JavaFoundation"/>
<classpathentry kind="con" path="WOFramework/JavaJDBCAdaptor"/>
data

$ sed -nr 's/^.*kind=\"con\" path=\"([^"]*)\".*$/\1/p' aa
WOFramework/ERExtensions
WOFramework/ERJars
WOFramework/ERPrototypes
WOFramework/JavaEOAccess
WOFramework/JavaEOControl
WOFramework/JavaFoundation
WOFramework/JavaJDBCAdaptor
0
1

You could do it like this:

while IFS=\" read -r _ _ _ f4 _; do
    case $f4 in
        */*) echo "$f4";;
    esac
done < file

You must log in to answer this question.

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