I have a bunch of .tcx files (essentially a GPS device outputted XML file) in a folder. They follow the naming convention DATE_LOCATION_SPORT.tcx. Each file has a bunch of track-point nodes noting key GPS data as recorded each second, within which there is a child distance-so-far node called "DistanceMeters". I want to grep through each file, and find the last "DistanceMeters" node, and then output that list of distances. Here's a snippet from a file. (I am using OS X Sierra terminal)

  <HeartRateBpm xsi:type="HeartRateInBeatsPerMinute_t">

I started off by trying to do this with just one file (note, some of the filenames have spaces in, not sure if this is tripping me up);

grep '<DistanceMeters>.*<\/DistanceMeters>' '2017-03-23_Somewhere_Running.tcx' | tail -1 | grep -o '[0-9]\+\.[0-9]\+'

Which very nicely gets the last "DistanceMeters" node, and then uses grep to pull out the numerical distance rather than the whole line of xml.

find . -iname '2017*_Running.tcx'

The above gives me a list of all the files I am interested in for this year.

But when I try to combine the commands, it all falls apart. I either get an error about not ending in a "\;" or, I get an answer, but only the last distance value for the last file in the file list, rather than a distance for each.

So this gives me the final distance of the last file in the list;

find . -iname '*_Running.tcx' -print0 | xargs -0 grep '<DistanceMeters>.*<\/DistanceMeters>' | tail -1 | grep -o '[0-9]\+\.[0-9]\+'

As does this;

find . -iname '*_Running.tcx' -exec grep '<DistanceMeters>.*<\/DistanceMeters>' {} \; | tail -1 | grep -o '[0-9]\+\.[0-9]\+'

I've tried various combinations, but I am not sure how to combine them the way I need. I thought maybe putting the "\;" at the end would do it, but this doesn't work - complains about not ending in ";" or "+".

find . -iname '*_Running.tcx' -exec grep '<DistanceMeters>.*<\/DistanceMeters>' {} | tail -1 | grep -o '[0-9]\+\.[0-9]\+' \;


grep: ;: No such file or directory
find: -exec: no terminating ";" or "+"

Any ideas?

Thanks for reading this far!


The problem here is with quoting... I don't know if there's a way to quote this correctly so that the 'exec' option to find(1) will accept a pipeline of commands.

Here's one way to get around that problem:

$ for rfile in $(find . -iname '2017*_Running.tcx'); do grep '<DistanceMeters>.*<\/DistanceMeters>' $rfile | tail -1 | grep -o '[0-9]\+\.[0-9]\+'; done
  • 2
    find -exec can indeed only receive one command, and that command can't directly involve shell constructs like pipes... unless that command is a shell. find . iname '2017*_Running.tcx' -exec sh -c 'for f; do grep '<DistanceMeters>.*</DistanceMeters>’ "$f" | tail -n 1 | grep -oE '[0-9]+\.[0-9]+'; done' _ + (notice also how / doesn't need a backslash, and the grep -E at the end, which allows for simplifying the regex) though I would use an XML parser for this, or at least refactor this into an Awk script. – tripleee Apr 15 '17 at 8:03
  • 2
    And for file in $(find) cannot work correctly in the general case; if the output from find contains whitespace or other shell metacharacters in the filenames, the shell won't parse them correctly when initializing the for loop. This is one of those pesky corner cases which is hard to solve completely correctly in shell scripts. – tripleee Apr 15 '17 at 8:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.