1

How can I achieve this best maintaining the hierarchy on the left and aligning the size value on the right?
Also, at the same time I'd like to round the size to one or two digits.
I tried with printf but couldn't figure it out how to keep this format.

$ port rdeps mtr 2>/dev/null | sed -E "1 s/.*of (.*) @.*/\1/" | while IFS= read -r line; do echo -e "$line" \\t\\t\\t\\t $(port space --units MiB --total $line 2>/dev/null | cut -d ' ' -f 1-2); done
mtr                  0.088 MiB
  pkgconfig                  0.615 MiB
    libiconv                 6.270 MiB
      gperf                  0.0 MiB
  glib2                  46.092 MiB
    xz               1.679 MiB
      gettext                24.825 MiB
        expat                1.109 MiB
        ncurses                  15.171 MiB
    libxml2                  10.405 MiB
      zlib               0.721 MiB
    libffi               0.141 MiB
    pcre                 5.954 MiB
      bzip2                  0.647 MiB
      libedit                0.795 MiB

This should look like this

mtr                          0.08 MiB
  pkgconfig                  0.61 MiB
    libiconv                 6.27 MiB
      gperf                  0.00 MiB
  glib2                     46.09 MiB
    xz                       1.67 MiB
      gettext               24.82 MiB
        expat                1.10 MiB
        ncurses             15.17 MiB
    libxml2                 10.40 MiB
      zlib                   0.72 MiB
    libffi                   0.14 MiB
    pcre                     5.95 MiB
      bzip2                  0.64 MiB
      libedit                0.79 MiB
  • Thanks for describing what you want to do (or at least making an attempt to do so) and showing us what you tried.  Now, please, show us what the input looks like, and what you want the output to look like.  Please do not respond in comments; edit your question to make it clearer and more complete. – G-Man Mar 27 '16 at 8:27
  • @G-Man, the output you can see there already and the input (to the while loop, if you refer to this) is what you see on your left — each line is echoed plus four tabs and the output of a command that's run on this same line. On the left you have a hierarchy of dependencies, that I want to preserve — on the right you have the size value for each one separated by four tabs. Instead of four tabs, I'd like to have those numbers aligned by its floating point, say at column 40 or so. You can see this in pkgconfig and the 2 following lines. – 1.61803 Mar 27 '16 at 11:21
1

Here are a couple of approaches:

With Rounding

As you appear to realize, printf is a great tool for formatted printing.  %f is the format to output a “floating point number”, i.e., a number that is not an integer, i.e., a number that includes a fractional part.  As with all printf formats, a number (an integer!) immediately after the % specifies the overall length of the formatted output, possibly including spaces.  This number can be followed by a period (.) and another number, which specifies the number of decimal positions (digits to the right of the decimal point) to display.  So, for example, the commands

printf "%12.2f\n" 1000000
printf "%12.2f\n" 1000
printf "%12.2f\n" 1
printf "%12.2f\n" 1.2345
printf "%12.2f\n" 1.6789
          ︙

will produce the output

  1000000.00
     1000.00
        1.00
        1.23
        1.68
         ︙

Note that 1.6789 got rounded up to 1.68.

So you should be able to get the result you want with this command:

port rdeps mtr 2>/dev/null | sed -E "1 s/.*of (.*) @.*/\1/" |
     while IFS= read -r line
     do
         space="$(port space --units MiB --total $line 2>/dev/null | cut -d ' ' -f 1-2)"
         space_num=${space% *}
         space_mib=${space#* }
         printf "%-20s%12.2f %s\n" "$line" "$space_num" "$space_mib"
     done

The space=$(…) command is just the port space command substitution that we’ve been using all along, but with the result (which looks like 0.088 MiB, for example) assigned to a temporary variable, space.  Then space_num=${space% *} sets space_num to the part of that before the space character (i.e., the number; 0.088 in this example), and space_mib=${space#* } sets space_mib to the part after the space character (i.e., the units, MiB).  Finally we glue all the parts together, using printf %12.2f to display the number to two decimal places, rounding to the nearest hundredth, and lining up on the decimal points (as in the earlier example).  For your data, this looks like

mtr                         0.09 MiB
  pkgconfig                 0.62 MiB
    libiconv                6.27 MiB
      gperf                 0.00 MiB
  glib2                    46.09 MiB
    xz                      1.68 MiB
      gettext              24.83 MiB
        expat               1.11 MiB
        ncurses            15.17 MiB
    libxml2                10.40 MiB
      zlib                  0.72 MiB
    libffi                  0.14 MiB
    pcre                    5.95 MiB
      bzip2                 0.65 MiB
      libedit               0.80 MiB

Note that 0.088 MiB got rounded up to 0.09 MiB

Note also that space_mib is always set to MiB, so we don’t actually need to compute it;

port rdeps mtr 2>/dev/null | sed -E "1 s/.*of (.*) @.*/\1/" |
     while IFS= read -r line
     do
         space_num="$(port space --units MiB --total $line 2>/dev/null | cut -d ' ' -f 1)"
         printf "%-20s%12.2f %s\n" "$line" "$space_num" "MiB"
     done

does the same as the above.

With Truncation

To simply truncate numbers, it’s best to treat them as strings rather than as numbers.  This command

port rdeps mtr 2>/dev/null | sed -E "1 s/.*of (.*) @.*/\1/" |
     while IFS= read -r line
     do
         printf "%-30s%s\n" "$line" \
                  "$(port space --units MiB --total $line 2>/dev/null | cut -d ' ' -f 1-2)"
     done | sed -E -e 's/(\..*) /\100 /' -e 's/(.{25}) *(....\...).*( .*)/\1\2\3/'

starts off as pretty much the same as my previous answer.  But then it pipes everything through a sed, which has two parts:

  • s/(\..*) /\100 /
    This matches the decimal point (\.) and any number of characters after it (.*) up to, but not including, a space character.  Then it replaces the whole match string with the part before the space (\1), two zeroes, and a space.  (I could have said s/(\..*)( )/\100\2/; it would done the same thing.)  This changes 0.088 MiB to 0.08800 MiB and 0.0 MiB to 0.000 MiB.  If you had 42. MiB in your data, it would change it to 42.00 MiB.  But note that it assumes that every number has a decimal point, even if it doesn’t have any digits after it.  (It also assumes that there are no periods in the strings mtr, pkgconfig, libiconv, etc.)

    We need to do this to ensure that there are at least two digits after the decimal point in every number; that is not the case for gperf until we make this correction.

  • s/(.{25}) *(....\...).*( .*)/\1\2\3/
    .{25} is short for .........................; i.e., 25 arbitrary characters.  I assume this is long enough to capture the longest (e.g., libiconv), most deeply indented, string.  Then any number of characters (.*), which, in fact, I expect to be just a bunch of spaces.  Then ....\... matches four characters, a decimal point, and two more characters.  If you ever have a space number bigger than 9999, you’ll have to change this to match more than four digits to the left of the decimal point.  Then any number of characters (.*), which will be any digits after the first two after the decimal point.  Then a space and the rest of the line (( .*)), which I expect to be  MiB.  Then it puts the pieces back together as the string (with the appropriate leading and following spaces), the number (with enough leading spaces to make the . the fifth character in \2), and then the units.

The output from this command looks like

mtr                         0.08 MiB
  pkgconfig                 0.61 MiB
    libiconv                6.27 MiB
      gperf                 0.00 MiB
  glib2                    46.09 MiB
    xz                      1.67 MiB
      gettext              24.82 MiB
        expat               1.10 MiB
        ncurses            15.17 MiB
    libxml2                10.40 MiB
      zlib                  0.72 MiB
    libffi                  0.14 MiB
    pcre                    5.95 MiB
      bzip2                 0.64 MiB
      libedit               0.79 MiB

Note that 0.088 MiB got truncated to 0.08 MiB.

Of course you can put any of the above compound commands all on one line if you want, and you should adjust the width constants (12, 20, 25, 30, etc.) to meet your needs.

  • That was a great answer. It helped me format the output for this answer. Thanks! – 1.61803 Jun 4 '16 at 11:37
0

As you appear to realize, printf is a great tool for formatted printing.  As you probably have learned, %s is the format to output a string.  %20s outputs a 20-character long string, right justified, padded with spaces on the left (if necessary).  %-20s does the opposite — it outputs a 20-character long string, left justified, padded with spaces on the right (if necessary).  So, for example, the commands

printf "%-20s%s\n" "mtr" "0.088 MiB"
printf "%-20s%s\n" "  pkgconfig" "0.615 MiB"
printf "%-20s%s\n" "    libiconv" "6.270 MiB"
          ︙

will produce the output

mtr                 0.088 MiB
  pkgconfig         0.615 MiB
    libiconv        6.270 MiB
       ︙

So you should be able to get the result you want with this command:

port rdeps mtr 2>/dev/null | sed -E "1 s/.*of (.*) @.*/\1/" |
     while IFS= read -r line
     do
         printf "%-20s%s\n" "$line" \
                  "$(port space --units MiB --total $line 2>/dev/null | cut -d ' ' -f 1-2)"
     done

I often recommend that you should always quote $ references (e.g., variables) in shell commands and scripts unless you have a good reason not to.  Note that I put the $(…) expression into quotes.  But, since $line contains leading spaces, it is quite possible that

port space --units MiB --total "$line"

would not work.

Of course you can put the above compound command all on one line if you want, and you should adjust the 20 to your needs.

  • That's a great answer. However, it doesn't take into account justifying by a char (the floating point). So, for example, it'd output ` libiconv 6.270 MiB gperf 0.0 MiB glib2 46.092 MiB` wrongly aligned. And then also the unit would be misaligned. Although I'd probably rather have it cut (not rounded) to two digits, it'd still need zero-padding in some cases. – 1.61803 Mar 28 '16 at 18:13
  • 1
    See, this is why I said "show us what the input looks like, and what you want the output to look like." When you say, "... I'd probably rather have X ..., it'd still need Y in some cases," you're asking us to read your mind. – G-Man Mar 28 '16 at 22:38
  • Fair enough. I updated the question. – 1.61803 Mar 28 '16 at 23:53
  • Hello? I've written a new answer; do you care? – G-Man May 14 '16 at 18:40
  • Hello, G-Man. I do care and I'll check it thoroughly as soon as I can. Thanks. – 1.61803 May 15 '16 at 8:35

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.