Number Systems and Base Conversions on the Command Line
I practiced converting between binary, decimal, and hexadecimal using bc, printf, and Python, the math that underpins subnetting, file permissions, and byte-level analysis. Fluency in base conversion makes CIDR masks and octal modes intuitive rather than memorized.
Objective & Context
Networking and permissions are base-2 and base-8 at heart. This lab builds quick conversion skills so subnet masks, chmod octals, and hex dumps read naturally, directly supporting the subnetting and permissions labs.
Learning Objectives
- Convert between binary, octal, decimal, and hexadecimal with bc.
- Format numbers in any base with printf.
- Convert bases quickly in Python one-liners.
- Read IPv4 octets as binary to understand subnet masks.
- Translate chmod octal modes to binary permission triples.
Environment & Prerequisites
Prerequisites
- Linux shell with bc, printf (shell builtin), and python3.
- Sample values to convert: IP octets, permission bits, byte values.
- No networking or root needed; this lab is pure computation.
- Curiosity about why 255, 8, and 16 appear everywhere in computing.
Step-by-Step Execution
1. Decimal to binary with bc
echo "obase=2; 192" | bcExpected output:
11000000
What this teaches: obase=2 switches bc's output base; 192 decimal is 11000000 binary. bc handles arbitrary precision, so it converts values far beyond 64-bit.
2. Decimal to hex with printf
printf '%X\n' 255Expected output:
FF
What this teaches: printf %X formats as uppercase hex; %x gives lowercase and %o gives octal. Note that printf has no binary format specifier (%b expands backslash escapes inside a string argument, it does not print binary); for binary output use echo "obase=2; 255" | bc or Python's bin(255). printf is the fastest formatter for scripts because it is a shell builtin.
3. Hex to decimal in Python
python3 -c "print(int('FF',16))"Expected output:
255
What this teaches: int('FF', 16) parses a base-16 string to decimal; the second argument sets the base (2, 8, 16), and bin()/hex() go the other way. Python is the pocket calculator for bases.
4. Decode a chmod octal to binary permission triples
echo "obase=2; 755" | bcExpected output:
111101101
What this teaches: 755 in binary is 111 101 101, read as three permission triples: rwx, r-x, r-x. Each octal digit is exactly three bits, which is why octal is the natural base for Unix permissions; grouping the output in threes makes the mapping visual.
How It Works
Positional notation is one idea in many bases: each digit multiplies a power of the base. Binary (base 2) mirrors hardware bits, octal (base 8) groups bits in threes which maps perfectly onto Unix permission triples, decimal (base 10) is human convention, and hex (base 16) groups bits in fours, matching bytes as two digits. Converting is just re-expressing the same value.
Networking leans on binary because subnet masks are contiguous bit runs: 255.255.255.0 is 24 ones then 8 zeros, i.e. a /24. Permissions lean on octal because rwx is three bits, so 7 is full and 5 is read+execute. Hex dominates dumps and hashes because two hex digits are exactly one byte, keeping binary data compact and readable.
The tools each cover a niche: bc is the arbitrary-precision calculator for long conversions, printf formats numbers inside scripts without spawning Python, and Python one-liners handle parsing plus formatting in a single expression. Knowing all three means never reaching for a web converter.
Validation & Testing
Convert an IP octet (192) to binary and a permission value (755) reasoning, then verify round-trips back to decimal. Pass criteria: conversions agree across bc, printf, and Python and match manual calculation.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| bc prints 0 for obase=16 conversions | ibase was set after obase, and obase is interpreted in the new ibase | Set obase before ibase, or use obase=16 with ibase untouched |
| printf %x shows unexpected ffffffff | The value was treated as signed 32-bit negative | Mask first: printf '%x' $(( value & 0xffffffff )) |
| Python int() raises ValueError | The string contains characters invalid for the base | Strip prefixes like 0x first, or pass base 0 to auto-detect |
| Binary string misaligns octets | Leading zeros were dropped | Pad with zfill(8) or printf %08d for fixed-width octets |
| bc -l changed integer results | -l loads the math library and sets scale=20 | Use plain bc for integer base conversion; reserve -l for floating point |
| Confusion between 1K=1000 and 1KiB=1024 | Storage vendors use decimal, OS tools use binary | Check units: ls -h uses powers of 1024, du --si uses 1000 |
Advanced: Troubleshooting
- bc base confusion: set
ibasebefore the value andobasefor output; order matters. - Wrong hex case: use
%xfor lowercase,%Xfor uppercase in printf. - Leading zeros lost: pad with printf width specifiers when needed.
Key Results
- Converted fluently across base-2, base-10, and base-16.
- Applied binary math directly to subnet mask reasoning.
- Verified conversions round-trip across 3 independent tools.
- Made octal permission values intuitive rather than memorized.
Key Takeaways
- Binary maps to bits (subnets), octal to permission triples, hex to bytes.
- bc converts with obase/ibase; set obase first.
- printf %x/%o formats inside scripts with zero overhead.
- Python int(s, base) parses; bin()/hex() format back.
- Pad binary octets to 8 digits when comparing masks.
Practice Challenges
Challenge 1: Mask in binary
Goal: print 255.255.240.0 as four 8-digit binary octets and state the CIDR prefix length.
Hint: convert each octet with printf or Python, then count the 1 bits.
Challenge 2: Permission decoder
Goal: write a one-liner that takes 750 and prints rwxr-x---.
Hint: map each octal digit to its 3-bit binary form, then 1 to r/w/x by position.