4

I have a folder with +1000 .dat files. And each file contains many lines of the following type:

-0.0999999999999659-0.0000000006287859
-0.08999999999997500.8000000006183942
-0.0799999999999841-0.0000000007463807
-0.06999999999999320.0000000008661516
-0.06000000000000230.0000000008640644
-0.05000000000001140.0000000008807621
-0.0400000000000205-0.7000000009575896
-0.02999999999997270.0000000009476864
-0.01999999999998180.0000000009150902
-0.00999999999999090.0000000008144152
0.00000000000000000.0000000007097434
0.00999999999999090.0000000007847500
0.01999999999998180.0000000009030998
0.03000000000002960.0000000009741985

For all the files I want to convert this to

-0.0999999999999659    -0.0000000006287859
-0.0899999999999750    0.8000000006183942
-0.0799999999999841    -0.0000000007463807
-0.0699999999999932    0.0000000008661516
-0.0600000000000023    0.0000000008640644
-0.0500000000000114    0.0000000008807621
-0.0400000000000205    -0.7000000009575896
-0.0299999999999727    0.0000000009476864
-0.0199999999999818    0.0000000009150902
-0.0099999999999909    0.0000000008144152
0.0000000000000000    0.0000000007097434
0.0099999999999909    0.0000000007847500
0.0199999999999818    0.0000000009030998
0.0300000000000296    0.0000000009741985

The only thing that is consistent in all these files that the second number (corresponding to the second dot on each line) is always smaller than 1.0 and greater than -1.0. But the first number can take any real value.

I therefore thought of using "find and replace" only for the second `dot' as follows. Find:

0.

Replace with:

   0.

I don't know how to specify sed only to act on the "second dot" on each line. Does anybody have a good idea on how to get this done?

5
 sed -E s'/(.*[^-])(-?0\.)/\1    \2/' 999.dat

The * is greedy and eats up as much characters as possible so the \. matches always the last one of the line. The [^-] ensures that the optional - of the second number gets into the second group.

  • It is working with: find /home/folder -name *.dat -exec sed -i -E s'/(.*[^-])(-?0\.)/\1 \2/' {} \; Thank you so much, you saved me a lot of work! – Hunter May 13 '17 at 22:54
5

To replace the second occurrence only, use the 2 modifier. Thus:

$ sed -E 's/-?[[:digit:]][.]/    &/2' file.dat
-0.0999999999999659    -0.0000000006287859
-0.0899999999999750    0.8000000006183942
-0.0799999999999841    -0.0000000007463807
-0.0699999999999932    0.0000000008661516
-0.0600000000000023    0.0000000008640644
-0.0500000000000114    0.0000000008807621
-0.0400000000000205    -0.7000000009575896
-0.0299999999999727    0.0000000009476864
-0.0199999999999818    0.0000000009150902
-0.0099999999999909    0.0000000008144152
0.0000000000000000    0.0000000007097434
0.0099999999999909    0.0000000007847500
0.0199999999999818    0.0000000009030998
0.0300000000000296    0.0000000009741985

How it works:

  • -E

    This tells sed to used extended regex. This eliminates the need to escape the ?.

  • s/-?[[:digit:]][.]/ &/2

    This looks for an optional - followed by a digit followed by a literal .. In the replacement text, four spaces are added before whatever the matched string, denoted &, is.

    The modifier 2 at the end of the substitute command tells sed to only replace the second occurrence of the pattern.

Related examples

Some more examples showing how different substitutions can be made:

$ echo aaaa | sed 's/a/A/1'
Aaaa
$ echo aaaa | sed 's/a/A/2'
aAaa
$ echo aaaa | sed 's/a/A/3'
aaAa
$ echo aaaa | sed 's/a/A/4'
aaaA
$ echo aaaa | sed 's/a/A/g'
AAAA
  • Thank you for replying! I'm not sure why, but your solution is not working for me. I've used the following syntax: find /home/folder -name *.dat -exec sed -i -E s'/(.*[^-])(-?0\.)/\1 \2/' {} \;. Although it works for some lines, it skips many other lines. – Hunter May 13 '17 at 22:57
  • The solution that you mention, sed -i -E s'/(.*[^-])(-?0\.)/\1 \2/', is not my solution. It looks to me like you are referring to @FlorianDiesch's solution. – John1024 May 13 '17 at 23:15
  • Sorry, I meant that I used: find /home/folder -name *.dat -exec sed -i -E 's/-?0[.]/(four_spaces)&/2' {} \;, but it didn't work for many lines. Not sure why, I couldn't find any pattern in it. – Hunter May 14 '17 at 0:10
  • @Hunter If you can show me some sample lines that it didn't work on, I'd be interested. – John1024 May 14 '17 at 0:31
  • 1
    I've checked it with find /home/folder -name \*.dat -exec sed -i -E 's/-?[[:digit:]][.]/ &/2' {} \; and it is indeed working now! +1 and thanks again! – Hunter May 14 '17 at 18:21
2

find the first dot :)

sed -r 's/(.*\.[^-\.]*)(-?)0\.(.*)/\1\t\20.\3/' file

Notes

  • -r use ERE
  • s/old/new replace old with new
  • (some chars) save some chars to reference later
  • .* any number of any characters
  • \. literal .
  • [^-\.] any characters except hyphen or .
  • -? optional -
  • \1\t\20.\3 print the saved patterns, a tab, and 0. in the correct places
2

How about

$ sed -E 's/(-?0\.[0-9]+)(-?0\.[0-9]+)/\1\t\2/' file
-0.0999999999999659     -0.0000000006287859
-0.0899999999999750     0.8000000006183942
-0.0799999999999841     -0.0000000007463807
-0.0699999999999932     0.0000000008661516
-0.0600000000000023     0.0000000008640644
-0.0500000000000114     0.0000000008807621
-0.0400000000000205     -0.7000000009575896
-0.0299999999999727     0.0000000009476864
-0.0199999999999818     0.0000000009150902
-0.0099999999999909     0.0000000008144152
0.0000000000000000      0.0000000007097434
0.0099999999999909      0.0000000007847500
0.0199999999999818      0.0000000009030998
0.0300000000000296      0.0000000009741985

How it works:

  • -?0\.[0-9]+ match 0. followed by one or more other decimal digits and optionally preceded by -
  • (-?0\.[0-9]+)(-?0\.[0-9]+) capture 2 instances of the above
  • \1\t\2 substitute them back with a TAB in between

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.