Table of Contents

Corning Community College

CSCS1730 UNIX/Linux Fundamentals

Project: PROCESSING WARRANTED NUMBERS (pwn0)

Errata

Toolbox

In addition to the tools you're already familiar with, it is recommended you check out the following tools for possible application in this project (you may not need to use them in your solution, but they definitely offer up functionality that some solutions can make use of):

Reference

For additional information on the printf(1) tool recommended for use in this project, please check out this paper:

NOTE that while it focuses on the C and Perl variants of printf, the conceptual underpinnings are the same, and many of the examples are likely portable to the UNIX variant of the tool.

Objective

To create a script that can, with or without information provided by the user, display useful reference data in various number bases.

Background

We've encountered many things in our UNIX journey so far, and have been readily building upon past experiences as we achieve newer and better successes. Of particular focus, we've been looking at shell scripting, and that will be the primary focus of this week's project.

There are collections of information quite useful to anyone journeying in a discipline, and computing is no different: We often encounter information represented in certain units more than others, or various quantities, or even particular number bases.

binary/octal/hex

One particular collection of information involves that of binary, octal, decimal (in signed and unsigned capacities) and hexadecimal numbers. We may encounter a tool that gives us information in octal and we need to feed it into another tool as binary or decimal; these sorts of transactions are common and crop up time and time again.

Having a strong handle on this information is vital for on-going success in the computing field.

As such, it is important to have close at hand the knowledge of the following number bases:

While we don't need to have hundreds of values memorized, it IS a good idea to have a rather quick recollection of enough values that can be used for debugging purposes. The first 16 values of each of these is such a preferred range. Not only does it give us a range of information, but for the powers-of-two bases, can actually fit in and be propagated quite nicely with little added effort.

Process

It is your task to write a script that, when run without any arguments, produces the following table of information (and displayed/formatted in this exact manner):

lab46:~/src/unix/pwn0$ ./pwn0.sh
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
        0 |        0 |        0 |        0 |
        1 |        1 |        1 |        1 |
       10 |        2 |        2 |        2 |
       11 |        3 |        3 |        3 |
      100 |        4 |        4 |        4 |
      101 |        5 |        5 |        5 |
      110 |        6 |        6 |        6 |
      111 |        7 |        7 |        7 |
     1000 |       10 |        8 |        8 |
     1001 |       11 |        9 |        9 |
     1010 |       12 |       10 |        A |
     1011 |       13 |       11 |        B |
     1100 |       14 |       12 |        C |
     1101 |       15 |       13 |        D |
     1110 |       16 |       14 |        E |
     1111 |       17 |       15 |        F |
lab46:~/src/unix/pwn0$ 

Your script should also take the following optional arguments (in this order):

Numerical arguments are to be given in decimal.

Additional arguments your script should support:

With any of these arguments validly provided, they should adjust the script's processing and output accordingly.

Some additional constraints/assumptions you can make:

Only specifying starting value

In the event only the starting value is specified, assume a count of 16 (so display 16 values, starting at the starting value).

For example:

lab46:~/src/unix/pwn0$ ./pwn0.sh 12
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
     1100 |       14 |       12 |        C |
     1101 |       15 |       13 |        D |
     1110 |       16 |       14 |        E |
     1111 |       17 |       15 |        F |
    10000 |       20 |       16 |       10 |
    10001 |       21 |       17 |       11 |
    10010 |       22 |       18 |       12 |
    10011 |       23 |       19 |       13 |
    10100 |       24 |       20 |       14 |
    10101 |       25 |       21 |       15 |
    10110 |       26 |       22 |       16 |
    10111 |       27 |       23 |       17 |
    11000 |       30 |       24 |       18 |
    11001 |       31 |       25 |       19 |
    11010 |       32 |       26 |       1A |
    11011 |       33 |       27 |       1B |
lab46:~/src/unix/pwn0$ 

Specifying starting and ending value

We can adjust the range displayed by specifying an ending value as well:

lab46:~/src/unix/pwn0$ ./pwn0.sh 37 42
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
   100101 |       45 |       37 |       25 |
   100110 |       46 |       38 |       26 |
   100111 |       47 |       39 |       27 |
   101000 |       50 |       40 |       28 |
   101001 |       51 |       41 |       29 |
   101010 |       52 |       42 |       2A |
lab46:~/src/unix/pwn0$ 

With a specified starting/ending value pair, we can display as little as 1 value and (theoretically) as many as we want.

Going backwards

It shouldn't matter the relationship of starting and ending values. In fact, if the starting value is HIGHER than the ending value, the count should go backwards:

lab46:~/src/unix/pwn0$ ./pwn0.sh 73 59
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
  1001001 |      111 |       73 |       49 |
  1001000 |      110 |       72 |       48 |
  1000111 |      107 |       71 |       47 |
  1000110 |      106 |       70 |       46 |
  1000101 |      105 |       69 |       45 |
  1000100 |      104 |       68 |       44 |
  1000011 |      103 |       67 |       43 |
  1000010 |      102 |       66 |       42 |
  1000001 |      101 |       65 |       41 |
  1000000 |      100 |       64 |       40 |
   111111 |       77 |       63 |       3F |
   111110 |       76 |       62 |       3E |
   111101 |       75 |       61 |       3D |
   111100 |       74 |       60 |       3C |
   111011 |       73 |       59 |       3B |
lab46:~/src/unix/pwn0$ 

Skipping values

With the 'by#' argument, we can cause numbers to iterate by more than 1. For example, if we wanted to go from 3 to 81 skipping 3 values each turn:

lab46:~/src/unix/pwn0$ ./pwn0.sh 3 81 by3
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
       11 |        3 |        3 |        3 |
      110 |        6 |        6 |        6 |
     1001 |       11 |        9 |        9 |
     1100 |       14 |       12 |        C |
     1111 |       17 |       15 |        F |
    10010 |       22 |       18 |       12 |
    10101 |       25 |       21 |       15 |
    11000 |       30 |       24 |       18 |
    11011 |       33 |       27 |       1B |
    11110 |       36 |       30 |       1E |
   100001 |       41 |       33 |       21 |
   100100 |       44 |       36 |       24 |
   100111 |       47 |       39 |       27 |
   101010 |       52 |       42 |       2A |
   101101 |       55 |       45 |       2D |
   110000 |       60 |       48 |       30 |
   110011 |       63 |       51 |       33 |
   110110 |       66 |       54 |       36 |
   111001 |       71 |       57 |       39 |
   111100 |       74 |       60 |       3C |
   111111 |       77 |       63 |       3F |
  1000010 |      102 |       66 |       42 |
  1000101 |      105 |       69 |       45 |
  1001000 |      110 |       72 |       48 |
  1001011 |      113 |       75 |       4B |
  1001110 |      116 |       78 |       4E |
  1010001 |      121 |       81 |       51 |
lab46:~/src/unix/pwn0$ 

This should be flexible whether going forward or backward in count:

lab46:~/src/unix/pwn0$ ./pwn0.sh 32 2 by4
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
   100000 |       40 |       32 |       20 |
    11100 |       34 |       28 |       1C |
    11000 |       30 |       24 |       18 |
    10100 |       24 |       20 |       14 |
    10000 |       20 |       16 |       10 |
     1100 |       14 |       12 |        C |
     1000 |       10 |        8 |        8 |
      100 |        4 |        4 |        4 |
lab46:~/src/unix/pwn0$ 

Exceeding bounds

Although our script does not need to iterate values outside the 0-255 range, it does need to check and cap values that exceed these bounds.

For example, if a starting or ending value falls beyond 255, cap it to 255:

lab46:~/src/unix/pwn0$ ./pwn0.sh 250
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
 11111010 |      372 |      250 |       FA |
 11111011 |      373 |      251 |       FB |
 11111100 |      374 |      252 |       FC |
 11111101 |      375 |      253 |       FD |
 11111110 |      376 |      254 |       FE |
 11111111 |      377 |      255 |       FF |
lab46:~/src/unix/pwn0$ 

If we have a negative value of any sort:

lab46:~/src/unix/pwn0$ ./pwn0.sh 4 -8
   base 2 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+
      100 |        4 |        4 |        4 |
       11 |        3 |        3 |        3 |
       10 |        2 |        2 |        2 |
        1 |        1 |        1 |        1 |
        0 |        0 |        0 |        0 |
lab46:~/src/unix/pwn0$ 

Both starting and ending values need to appropriately fall within the 0-255 bounds, however they may be situated.

Additional bases

Your script should also support the inclusion of additional bases (valid bases are any base between 2 and 16, inclusive). The default bases assumed included are 2, 8, 10, and 16, but additional ones may be specified with a with# argument.

For instance, let's say we wanted to include base 7:

lab46:~/src/unix/pwn0$ ./pwn0.sh with7
   base 2 |   base 7 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+----------+
        0 |        0 |        0 |        0 |        0 |
        1 |        1 |        1 |        1 |        1 |
       10 |        2 |        2 |        2 |        2 |
       11 |        3 |        3 |        3 |        3 |
      100 |        4 |        4 |        4 |        4 |
      101 |        5 |        5 |        5 |        5 |
      110 |        6 |        6 |        6 |        6 |
      111 |       10 |        7 |        7 |        7 |
     1000 |       11 |       10 |        8 |        8 |
     1001 |       12 |       11 |        9 |        9 |
     1010 |       13 |       12 |       10 |        A |
     1011 |       14 |       13 |       11 |        B |
     1100 |       15 |       14 |       12 |        C |
     1101 |       16 |       15 |       13 |        D |
     1110 |       20 |       16 |       14 |        E |
     1111 |       21 |       17 |       15 |        F |
lab46:~/src/unix/pwn0$ 

And this should work even with the potential inclusion of starting, ending, or a by# option (forward or backward):

lab46:~/src/unix/pwn0$ ./pwn0.sh 13 255 by13 with7
   base 2 |   base 7 |   base 8 |   base10 |   base16 |
----------+----------+----------+----------+----------+
     1101 |       16 |       15 |       13 |        D |
    11010 |       35 |       32 |       26 |       1A |
   100111 |       54 |       47 |       39 |       27 |
   110100 |      103 |       64 |       52 |       34 |
  1000001 |      122 |      101 |       65 |       41 |
  1001110 |      141 |      116 |       78 |       4E |
  1011011 |      160 |      133 |       91 |       5B |
  1101000 |      206 |      150 |      104 |       68 |
  1110101 |      225 |      165 |      117 |       75 |
 10000010 |      244 |      202 |      130 |       82 |
 10001111 |      263 |      217 |      143 |       8F |
 10011100 |      312 |      234 |      156 |       9C |
 10101001 |      331 |      251 |      169 |       A9 |
 10110110 |      350 |      266 |      182 |       B6 |
 11000011 |      366 |      303 |      195 |       C3 |
 11010000 |      415 |      320 |      208 |       D0 |
 11011101 |      434 |      335 |      221 |       DD |
 11101010 |      453 |      352 |      234 |       EA |
 11110111 |      502 |      367 |      247 |       F7 |
lab46:~/src/unix/pwn0$ 

Multiple additional bases can be included… you merely append additional with# arguments:

lab46:~/src/unix/pwn0$ ./pwn0.sh 196 2 with3 by14 with12
   base 2 |   base 3 |   base 8 |   base10 |   base12 |   base16 |
----------+----------+----------+----------+----------+----------+
 11000100 |    21021 |      304 |      196 |      144 |       C4 |
 10110110 |    20202 |      266 |      182 |      132 |       B6 |
 10101000 |    20020 |      250 |      168 |      120 |       A8 |
 10011010 |    12201 |      232 |      154 |      10A |       9A |
 10001100 |    12012 |      214 |      140 |       B8 |       8C |
  1111110 |    11200 |      176 |      126 |       A6 |       7E |
  1110000 |    11011 |      160 |      112 |       94 |       70 |
  1100010 |    10122 |      142 |       98 |       82 |       62 |
  1010100 |    10010 |      124 |       84 |       70 |       54 |
  1000110 |     2121 |      106 |       70 |       5A |       46 |
   111000 |     2002 |       70 |       56 |       48 |       38 |
   101010 |     1120 |       52 |       42 |       36 |       2A |
    11100 |     1001 |       34 |       28 |       24 |       1C |
     1110 |      112 |       16 |       14 |       12 |        E |
lab46:~/src/unix/pwn0$ 

Omitting bases

With the sans# option, we can leave out specified bases (useful for excluding any of the default 2, 8, 10, or 16).

Here we will omit base 8 from a default run:

lab46:~/src/unix/pwn0$ ./pwn0.sh sans8
   base 2 |   base10 |   base16 |
----------+----------+----------+
        0 |        0 |        0 |
        1 |        1 |        1 |
       10 |        2 |        2 |
       11 |        3 |        3 |
      100 |        4 |        4 |
      101 |        5 |        5 |
      110 |        6 |        6 |
      111 |        7 |        7 |
     1000 |        8 |        8 |
     1001 |        9 |        9 |
     1010 |       10 |        A |
     1011 |       11 |        B |
     1100 |       12 |        C |
     1101 |       13 |        D |
     1110 |       14 |        E |
     1111 |       15 |        F |
lab46:~/src/unix/pwn0$ 

Of course, this option can be validly mixed with any of the other valid options, and needs to work as appropriate (here you'll see a run we did prior, with the only difference being the omission of base 8):

lab46:~/src/unix/pwn0$ ./pwn0.sh 196 2 with3 by14 with12 sans8
   base 2 |   base 3 |   base10 |   base12 |   base16 |
----------+----------+----------+----------+----------+
 11000100 |    21021 |      196 |      144 |       C4 |
 10110110 |    20202 |      182 |      132 |       B6 |
 10101000 |    20020 |      168 |      120 |       A8 |
 10011010 |    12201 |      154 |      10A |       9A |
 10001100 |    12012 |      140 |       B8 |       8C |
  1111110 |    11200 |      126 |       A6 |       7E |
  1110000 |    11011 |      112 |       94 |       70 |
  1100010 |    10122 |       98 |       82 |       62 |
  1010100 |    10010 |       84 |       70 |       54 |
  1000110 |     2121 |       70 |       5A |       46 |
   111000 |     2002 |       56 |       48 |       38 |
   101010 |     1120 |       42 |       36 |       2A |
    11100 |     1001 |       28 |       24 |       1C |
     1110 |      112 |       14 |       12 |        E |
lab46:~/src/unix/pwn0$ 

Multiple sans# options can be given, and will cancel out any present base (including those specified with a with# option). A sans# option for a non-existent base should be silently ignored.

Online help

Your script should also, when prompted, display usage information with the help option:

lab46:~/src/unix/pwn0$ ./pwn0.sh help

      pwn0.sh - render a number table in the specified bases,
                displaying from starting value to ending value.

      behavior: by default, bases 2, 8, 10, and 16 will be
                displayed, in a range of 0 to 15 (inclusive).

          note: valid bases range from 2-16, and values should
                not go negative nor exceed 255. If a starting
                value but no ending value is given, assume an
                ending value +15 away (or capped by 255).

         usage: pwn0.sh [start#] [end#] [OPTION]...

       options:
           $1 - if a positive decimal value, override the
                starting value (0)
           $2 - if a positive decimal value, override the
                ending value (15)

 other options:
          by# - iterate through values by # (default: 1)
        with# - include base # in the list of bases to display
        sans# - remove base # in the list of bases to display
         help - display script usage and exit

lab46:~/src/unix/pwn0$ 

Specifications

Evaluation will be based on correctness of values as well as on formatting/spacing.

You'll notice that everything lines up and is positioned similarly:

To be sure, I'll be checking to make sure you solution follows the spirit of what this project is about (that you implement functional, flowing logic utilizing the tools and concepts we've learned, in an application that helps demonstrate your comprehension). Don't try to weasel your way out of this or cut corners. This is an opportunity to further solidify your proficiency with everything.

Spirit of project

The spirit of the project embodies many aspects we've been focusing on throughout the semester:

Basically: I want your solution to be the result of an honest, genuine brainstorming process where you have (on your own) figured out a path to solving the problem, you have dabbled and experimented and figured things out, and you can command the concepts and tools with a fluency enabling you to pull off such a feat. Your solution should demonstrate the real learning that took place and experience gained.

Cutting corners, avoiding work, skimping on functionality, cheating through getting others to do work for you or finding pre-packaged “answers” on the internet violates the spirit of the project, for they betray your ability to pull off the task on your own.

Identifying shortcomings

I would also like it if you provided an inventory of what functionality is lacking or out of spec when you submit the project. The better you can describe your deviations from stated requirements, the more forgiving I may be during evaluation (versus trying to hide the shortcomings and hoping I do not discover them).

The more fluent you are in describing your shortcomings on accomplishing the project (ie “I didn't know how to do this” is far from fluent), the better I will be able to gauge your understanding on a particular aspect.

This can be in the form of comments in your script, or even a separate file submitted at time of submission (if a file, be sure to make mention of it in your script so I can be sure to look for it).

Output Formatting with printf(1)

You might be wondering how you can full off some of these output formatting feats. The echo command, after all, is rather rudimentary.

Fear not! The printf(1) tool comes to your rescue!

Like echo, printf displays information to the screen (STDOUT). In fact, various programming languages (like C and C++) that want more powerful output formatting implement some form of printf into their vast libraries.

Be sure to check the manual page for options and functionality; following will be a few usage examples.

Basic operation

Like echo, printf in its simplest form takes as an argument a string to display to STDOUT:

lab46:~$ printf "Hello, World"
Hello, Worldlab46:~$ 

However, we notice one difference between the default behavior of printf vs. echo: printf does not automatically issue a newline by default, as echo does. So, we'll need to specify it manually with the \n character:

lab46:~$ printf "Hello, World\n"
Hello, World
lab46:~$ 

Those various escape characters we've learned about? Super useful here:

Be sure to check the manual page for additional escape characters.

Format specifiers

Now, we know from using echo we can utilize shell variable and command expansions to make our output more dynamic.

But, printf adds additional formatting capability that echo lacks. If the string we are displaying contains % symbols, substitutions can be made and behaviors invoked; functionality that echo lacks.

Some common format specifiers:

First up, a simple example:

lab46:~$ printf "%d\n" 175
175
lab46:~$ 

Then, adding to it:

lab46:~$ printf "The number: %d\n" 175
The number: 175
lab46:~$ 

That 175 can also be in a variable:

lab46:~$ value=175
lab46:~$ printf "The number: %d\n" ${value}
The number: 175
lab46:~$ 

You may want to wrap the variable in double quotes, to avoid cases where it might be NULL and otherwise generate an error (I generally quote all my variables to be on the safe side).

How about substitutions with strings?

lab46:~$ printf "Your username is: %s\n" "${USER}"
Your username is: username
lab46:~$ 

And in general just crafting super effective format strings:

lab46:~$ value=175
lab46:~$ printf "%d %s %X\n" "${value}" "is hexadecimal" "${value}"
175 is hexadecimal AF
lab46:~$ 

Please take note, where there are multiple format specifiers, substitution is in order of specification… %d was the first, %s was the second, and %X was the third in that particular string, so the first value following the string is grabbed by the %d, the next the %s, and the third the %X. Ordering matters (which should make sense).

Output formatting

So now, getting to where printf really excels, formatting your output.

It turns out, that between the % and whatever format option you specify, you can provide a numeric value which will impact how that number appears on the screen, commonly in the form of preallocated space to display within.

For instance:

lab46:~$ printf "Testing: >>%4d<<\n" 47
Testing: >>  47<<
lab46:~$ 

See what happened there? 47 was displayed, but WITHIN a block of 4 characters. Also of note, by default output is right justified.

To left justify, simply make it negative:

lab46:~$ printf "Testing: >>%-4d<<\n" 47
Testing: >>47  <<
lab46:~$ 

We can also pad with zeros, that is represented by decimal values:

lab46:~$ printf "Testing: >>%.3d<<\n" 49
Testing: >>049<<
lab46:~$ 

This can be combined with justify and space allocation:

lab46:~$ printf "Testing: >>%-6.3d<<\n" 49
Testing: >>049   <<
lab46:~$ 

Other neat tricks? We can variable-ize the padding by using a *:

lab46:~$ spacing=4
lab46:~$ printf "Testing: >>%*d<<\n" "${spacing}" 49
Testing: >>  49<<
lab46:~$ 

The printf tool is super-powerful and useful for output, so mastering its use adds an impressive capability to your repertoire.

Play with printf and experiment… you'll find it can accomplish some impressive output feats that previously may have been more complicated.

Verification

To assist you in verifying output compliance, I have generated a set of sample script outputs and placed them in the public directory for this project.

There you will find a file by the name of outputlist, which contains a list of filenames (output0 - outputF) and the pwn0.sh script command-line used to generate the output found in those files.

Using these files, you can check the functionality of your script in a number of operating conditions, to verify that it is indeed working as it should be. While this isn't likely to cover the full spectrum of possibilities, it should offer up some assistance in helping you broaden your view of troubleshooting scenarios.

Submission

By successfully performing this project, you should have a fully functioning script by the name of pwn0.sh, which is all you need to submit for project completion (no steps file, as your “steps” file IS the script you wrote).

To submit this project to me using the submit tool, run the following command at your lab46 prompt:

$ submit unix pwn0 pwn0.sh
Submitting unix project "pwn0":
    -> pwn0.sh(OK)

SUCCESSFULLY SUBMITTED

You should get some sort of confirmation indicating successful submission if all went according to plan. If not, check for typos and or locational mismatches.

I'll be looking for the following:

78:pwn0:final tally of results (78/78)
*:pwn0:pwn0.sh on help displays informative usage and exits [4/4]
*:pwn0:pwn0.sh effectively utilizes variables in operations [4/4]
*:pwn0:pwn0.sh effectively utilizes command expansions [4/4]
*:pwn0:pwn0.sh effectively utilizes regular expressions [4/4]
*:pwn0:pwn0.sh effectively utilizes selection statements [4/4]
*:pwn0:pwn0.sh effectively utilizes looping structures [4/4]
*:pwn0:pwn0.sh is a proper bash script with shabang and exit [4/4]
*:pwn0:pwn0.sh parses command-line arguments as appropriate [4/4]
*:pwn0:pwn0.sh accurately displays values in proper alignment [4/4]
*:pwn0:pwn0.sh accurately displays values in requested bases [4/4]
*:pwn0:pwn0.sh accurately displays values in specified range [4/4]
*:pwn0:pwn0.sh range logic flexibly works forward and reverse [4/4]
*:pwn0:pwn0.sh iterates as appropriate in given scenarios [4/4]
*:pwn0:pwn0.sh includes additional bases as requested [4/4]
*:pwn0:pwn0.sh omits specified bases as requested [4/4]
*:pwn0:pwn0.sh properly manages input violations [4/4]
*:pwn0:pwn0.sh operates according to specifications [7/7]
*:pwn0:pwn0.sh logic is organized and easy to read [7/7]

Additionally: