8

I have the following data frame that continues indefinitely horizontally and vertically with negative numbers only in the odd columns:

-1  2  3  4 -5  9
 2  3 -4  5 -6  11

And I want the 2nd, 4th and 6th complete columns (or every even column) and the minus signs only from the 1st, 3rd, and 5th (or every odd column), so I get this:

- 2   4 - 9
  3 - 5 - 11

And eventually end up with this:

-2  4 -9
 3 -5 -11

So I need the values from the even columns unchanged and of the odd columns, if there's a negative value, keep the - only and if there's a positive value, discard it.

Is there a way to do this with awk/sed?

This is about a far as I get:

awk '{ for (i=2;i<=NF;i+=2) $i="" }1' FILE.txt | sed 's/[0-9,.]*//g' 
  • When you say your dataframe continues indefinitely, do you mean horizontally or vertically? How many columns do you actually have? – terdon Jun 21 '15 at 14:36
  • Both. My test data is 3 rows by 3 columns but the actual data has varying numbers, I'd say up 40 rows and 40 columns. – Asfound Jun 21 '15 at 14:41
2

Here's one way:

$ awk '{for(i=1;i<=NF;i+=2){if($i<0){$i="-"}else{$i="";} }};1' file |
     sed 's/- */-/g; s/  */ /g'
-2 4 -9
 3 -5 -11

The awk script goes over all odd columns and sets their value to - if they are negative and empty if not. Then, the sed removes any spaces following a - and then replaces multiple consecutive spaces with a single one. Note that this means that the alignment will be broken since some fields will have two characters or more and others will have one. That won't be an issue if you're working with fields, they just don't look pretty.

4

The sed way:

sed -E '
    s/^(([ \t]*-?[ \t]*[0-9.]+[ \t]+[0-9.]+)*)[ \t]+-?[ \t]*[0-9.]+$/\1/;
    s/[0-9.]+[ \t]+([0-9.]+)/\1/g'

Output:

-2  4 -9
 3 -5 -11

The first expression kills the trailing column if there are an odd number of columns. It does that by looking for 0 or more pairs <number> <number>, where the first number can be negative.

Edit: A shorter sed solution, inspired by @mikeserv:

sed -E '
    s/[0-9.]+[ \t]*([0-9.]*)/\1/g;
    s/[- \t]*$//'

The same thing with perl:

perl -lpe 's/^((\s*-?\s*[\d.]+\s*[\d.]+)*)\s+-?\s*[\d.]+$/$1/o; s/[\d.]+\s+([\d.]+)/$1/g'

Another way with perl (probably the cleanest one):

perl -lpe '$a = 1; s/([\d.]+\s*)/$a++ % 2 ? "" : $1/eg; s/[-\s]*$//o'
  • This works fine on my actual data as long as I add the decimal points into the script. Thanks! – Asfound Jun 21 '15 at 14:38
  • @Asfound Ok, I edited my answer to also support decimal points. – lcd047 Jun 21 '15 at 14:43
  • Hang on, this will fail if there is a negative value as the last (odd) field. – terdon Jun 21 '15 at 14:47
  • @terdon It fails if there are an odd number of columns, yes. But there are either exactly 6 columns, or "inifinitely many", and "infinitely many" is not an odd number. :) – lcd047 Jun 21 '15 at 14:50
  • The OP said that there can be "up to 40 columns" :( – terdon Jun 21 '15 at 14:51
3

A perl one:

$ perl -anle 'BEGIN{$,=" "}
  print map{$_=$F[$_]=~/^-/?"-$F[$_+1]":" $F[$_+1]"}grep{!($_%2)}0..$#F' file
-2  4 -9
 3 -5 -11
  • -an split input to @F array
  • BEGIN{$,=" "} set output field separator to a space
  • grep{!($_%2)}0..$#F get all even indexes in @F array, which are indexes of odd elements
  • map{$_=$F[$_]=~/^-/?"-$F[$_+1]":" $F[$_+1]"} check if odd element start with -, then append - to next even element, else append a space
3

As @terdon's answer but without the sed:

awk '{ for(i=1;i<=NF;i+=2){
         if ($i<0) $(i+1)*=-1;
         $i = "";
       }
       print
     }'
3

A python solution

python -c 'from __future__ import print_function; 
import sys, math;
for line in sys.stdin:
  x = [int(y) for y in line.split()]
  print(*[int(math.copysign(b, a)) for a, b in zip(x[::2], x[1::2])], sep=" ")
' <file
2

A simple mathematics-based awk solution:

$ cat <<M | awk '{for(i=2;i<=NF;i+=2){printf "%4s",($(i-1)<0?-1:1)*$i}print ""}'
-1  2  3  4 -5  9
2  3.2 -4  5 -6
M

  -2   4  -9
 3.2  -5
  • Loop from the second (i=2) to the last field (i<=NF).
  • Multiply the previous field ($(i-1)) with either -1 or 1.
  • Format the output nicely (printf "%4s"), and print a trailing newline (print "").

The only caveat to this is that if you have an odd number of columns, the last field will not display anything at all. I hope this is what you expect. Apparently this is what you expect. :)

(edited to work with decimal values, and to make the loop conditions more aligned with the question while saving 2 characters.)

1

You need to forget the negative entirely - leave it out. You want to consolidate two fields - from left to right. That's very easy.

sed '   s/ *\(.*\)/\1 /
        s/\([0-9]*  *\)\{2\}/\1/g
        s/[ -]*$//
' <<\IN
-1  2  3  4 -5  9
 2  3 -4  5 -6  11
IN
-2  4 -9
3 -5 -11

Notice how I avoid any reference to the sign at all - when the input is processed the automaton will accept only spaces or numbers because it understands nothing else - all else is ignored completely and will remain in place.

When you specify a \{numeric repetition interval\} for a \(subexpression\), only the last occurrence of that expression is \1 back-referenced. So you can just squeeze - or truncate - a repeat interval that easily. And because we squeeze the repeat behind the sign - if there is one - the second occurrence of that pattern will follow any sign that used to precede the first.

The behavior described above is specified by POSIX for all BRE compliant applications, but very few seds get it right. GNU sed does.

Last, the spaces are just to make the pattern occurrence regular.

Of course, this will never work for you. Or, probably more correctly, it will always work for you, but never return any results. How could it if the pattern is indefinite?

  • This will only work if there is an even number of fields. – terdon Jun 22 '15 at 12:26
  • @terdon - nope - it works for whatever. – mikeserv Jun 22 '15 at 12:27
  • No, try it with an odd number of fields. The last one is printed and it shouldn't be. – terdon Jun 22 '15 at 12:31
  • @terdon - why shouldn't it be? There is no following field to cancel it out? The asker states they want to remove odd columns followed by an even column. The last column is not followed by an even column - it does exactly what it should, and removes as little as possible. Assuming some data should go is bad practice in my opinion. – mikeserv Jun 22 '15 at 12:32
  • No they don't: "So I need the values from the even columns unchanged and of the odd columns, if there's a negative value, keep the - only and if there's a positive value, discard it." Odd fields should never be printed, the only information they should impart is whether they were negative. Yours prints positive odd fields. – terdon Jun 22 '15 at 12:33

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.