CSCS1730 UNIX/Linux Fundamentals

PROJECT: PROCESSING WARRANTED NUMBERS (pwn0)

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):

  • bc(1)
  • comm(1)
  • diff(1)
  • grep(1)
  • join(1)
  • printf(1)
  • seq(1)
  • sort(1)
  • uniq(1)

REFERENCE

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

  • https://lab46.g7n.org/downloads/printf.pdf

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:

  • base 2 (binary)
  • base 8 (octal)
  • base 10 (decimal, specifically signed and unsigned quantities)
  • base 16 (hexadecimal)

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/SEMESTER/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/SEMESTER/unix/pwn0$ 

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

  • $1- starting value (some value greater than or equal to 0, and less than 256)
  • $2- ending value (some value greater than or equal to 0, and less than 256)

Numerical arguments are to be given in decimal.

Additional arguments your script should support:

  • 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

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

Some additional constraints/assumptions you can make:

  • the minimum lower limit your script should process for is 0. If an input goes negative, cap it to 0.
  • the maximum upper limit you can implement for is 255 (base 10). If an input goes in excess of this, cap it to 255.

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/SEMESTER/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/SEMESTER/unix/pwn0$ 

SPECIFYING STARTING AND ENDING VALUE

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

lab46:~/src/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/unix/pwn0$ 

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

lab46:~/src/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/unix/pwn0$ 

If we have a negative value of any sort:

lab46:~/src/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/unix/pwn0$ 

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

lab46:~/src/SEMESTER/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/SEMESTER/unix/pwn0$ 

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

lab46:~/src/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/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/SEMESTER/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:

  • each column exists within 10 characters of on-screen real estate:
    • 8 characters for actual value display,
    • with one space padding either side
  • columns are separated with a | symbol, appended to the right edge of the column (effectively making each column 11 characters wide).
    • note that the rightmost column is ended with this |.
  • there is a heading/header at the top of the output table, and it uses a '+' to "connect" the horizontal and vertical lines being drawn
    • The dashes and vertical bars set up an ASCII art-like table pattern that helps make the data more readable.
  • The values are right justified with each column
    • justification is essential. Everything should line up on its respective right margin.
  • base headings are right justified, with a 2 spaces being allocated for reference of the base:
    • you'll see "base 2" vs. "base16"; your script needs to accommodate this.
  • You are to literally print out no more than 2 consecutive spaces at a time in your output statements. If you need more, use one of the formatting tools to generate spaces for you (printf(1) may be able to help you with this).
  • You need to check your arguments to ensure they are present and valid. Some examples:
    • starting value is a valid (whole) number
      • if less than 0, or greater than 255, needs to be capped to whichever extreme it exceeds
        • if -4, cap to 0
        • if 300, cap to 255
    • ending value is a valid (whole) number
      • if less than 0, or greater than 255, needs to be capped to whichever extreme it exceeds
        • if -8, cap to 0
        • if 420, cap to 255
    • any invalid arguments (nonsensical value in place of starting value, invalid base specification, etc.) should be silently dropped/ignored.
  • default starting value is 0, default ending value is 15. If nothing else is validly provided, these are the values the script should run with.
  • if starting and ending values are the same, the script will display just one line of number output (in addition to the header).
  • if lacking any bases to display, silently exit
  • your script needs to commence with a proper shabang to bash; your script needs to end with an "exit 0" at the very end
  • comments and indentation are required and necessary
    • comments should explain how or why you are doing something
    • indentation should be consistent throughout the script
    • indentation is to be no less than 3 on-screen spaces (I recommend tabstops of 4).
  • continuing with our shell scripting, your script will need to employ in a core/central way:
    • variables
    • command expansions
    • regular expressions
    • if statements
    • loops
  • your logic needs to:
    • flow (one thing leads into the next, as best as possible)
    • make sense within the given context
    • avoid redundancy
    • be understood by you (no grabbing snippets that seem to "work" from the internet)
      • if you gain inspiration from some external resource, please cite it
      • comments are a great way of demonstrating understanding (if you explain the why and how effectively, and it isn't in violation of other aspects, I'll know you are in control of things)

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:

  • recognizing patterns to employ effective solutions in problem solving
  • utilizing concepts and tools covered
  • demonstrating comprehension of concepts, tools, and problems
  • employing concepts in knowledgeable and thoughtful manner
  • following instructions
  • implementing to specifications
  • utilizing creativity
  • being able to control solution via consistent, clear, and organized presentation

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.

NOTE: some have instantiated controversy in the past by utilizing AI in a manner well beyond their abilities. In such cases, submitted work is not considered your own, and thereby inviting disciplinary action. Don't fall into that trap.

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 pull 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.

printf: 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:

  • \n - newline
  • \t - tab

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

printf: 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:

  • %d - substitute integer (whole number) value
  • %s - substitute string value
  • %c - substitute single character
  • %f - substitute floating point value
  • %o - substitute integer (whole number) value and represent in octal
  • %x - substitute integer (whole number) value and represent in hexadecimal

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).

printf: 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.

SELECTION

Occasionally during the automation of a task, you will have need for the computer to make a decision based on the state of a condition to decide how to proceed. We accomplish this via the if selection structure.

In bash, an if takes the following form:

if [ condition ]; then
    statement(s)
fi

The "condition" are valid arguments posed to the [(1) command, resulting in a true or false result.

There are one or more statements within the body of the if (to be run in the event the result of the condition tests true), and the structure is terminated with the trailing fi (if spelled backwards).

We also have the ability to react in the event the if condition tests false, and that is with an optional else statement (which is used 0 or 1 times per if, and must always follow the if):

if [ condition ]; then
    statement(s)
else
    statement(s)
fi

The else has no condition: it is a reaction to the previously tested condition being false.

CONDITIONS

The conditions we pose to the computer must be such that resolve ultimately to a true or false result.

Each condition can be unary or binary in nature, meaning we can directly check the state of one thing, or compare the relationship between two things.

For example, the -z argument to [(1) tests for a NULL string. If we wanted to see if a variable as a result of some operation were NULL or populated, we could do:

check=$(/usr/bin/who | grep 'jsmith12' | wc -l | grep '^1$')
if [ -z "${check}" ]; then
    statement(s) # do this if check is NULL
fi

Note that we could also check the opposite. We can use the ! symbol to require the opposite (NOT):

check=$(/usr/bin/who | grep 'jsmith12' | wc -l | grep '^1$')
if [ ! -z "${check}" ]; then
    statement(s) # do this if check is NOT NULL
fi

Comparing two strings, the two common scenarios we tend to care about is whether or not the two strings are equal or not equal:

string1="blah"
string2="boop"

if [ "${string1}" = "${string2}" ]; then
    statement(s) # do this in the event strings are equal
fi

if [ ! "${string1}" = "${string2}" ]; then
    statement(s) # do this in the event strings are NOT equal
fi

It should be stated that instead of negating the result, we could also just use an else clause:

string1="blah"
string2="boop"

if [ "${string1}" = "${string2}" ]; then
    statement(s) # do this in the event strings are equal
else
    statement(s) # do this in the event strings are not equal
fi

As a matter of style (and to potentially avoid syntax/logic errors being committed), one should not pose a condition just for the sake of ensuring its opposite (ie no empty clauses).

If comparing numerical values, especially in numerical contexts, we have a set of arguments we can choose from:

  • -eq - is equal to
  • -ne - is not equal to
  • -lt - is less than
  • -le - is less than or equal to
  • -gt - is greater than
  • -ge - is greater than or equal to

These of course are binary conditions, meaning we compare TWO variables to see if they possess the indicated relation (which results in a true or false result produced).

LOOPS

When you have a section of code you would like to repeat some number of times, a loop can be used to achieve this.

Here we will explore the while conditional loop. If an if statement can run 0 or 1 times, a while loop can run 0 or more.

count=10
while [ "${count}" -gt 0 ]; do
    echo "count is: ${count}"

=====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:

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

Additionally:

  • Solutions not abiding by SPIRIT of project will be subject to a 25% overall deduction
  • Solutions not utilizing descriptive why and how COMMENTS will be subject to a 25% overall deduction
  • Solutions not utilizing INDENTATION to promote scope and clarity will be subject to a 25% overall deduction
  • Solutions lacking ORGANIZATION ior are not easy to read (within 90 char width) are subject to a 25% overall deduction