Computer Science: Linux: The Terminal

For those of you that are familiar with the Linux operating system family, you know that the terminal is the most important part of it. What is the “terminal”? To answer this question, we first explore some fundamental truths related to the Linux kernel and the OS.

Device Files in Linux

The Linux operating system hosts these mysterious little items known as device files. These files contain all the bytes read from input devices like mice, keyboards, and joysticks. Kernel modules read from these devices and write to their respective device file. Remember how I sad the terminal is a virtual input device? Well it has a device file as well; one that you are no-doubt familiar with:

/dev/stdin

The file “stdin” holds the bytes gathered from the terminal. We can access this file in Python to see what the user is typing into the terminal:

f = open('/dev/stdin', 'r')
f.read(1024)

Run the above program. As you type into the terminal, you will notice it is vomiting what you type. This is because we are reading from the terminal’s device file. You can expect the same thing if you read from the mouses device file. There is also a much faster way of accessing the terminal’s device file:

import sys
sys.stdin.read()

Awesome! Did you know that the terminal was that simple?

File Descriptors in Linux

A file descriptor is often over complicated in the Computer Science community. All it is a unique ID, in the form of an integer, that represents a certain open file object. For example:

f = open('test.doc', 'r')

The file object “f” has a file descriptor:

fd = f.fileno()

The “fileno” method can be used to grab the descriptor of any open file, including device files! Let us get the file descriptor of the terminal’s device file:

sys.stdin.fileno()

This should return 0 or 1, because the terminal device file is the main open file on the Linux system. Every file you open after that will increment by one.

Controlling the Linux Terminal

We can use the terminal’s file descriptor to change attributes and adjust colors! We can even make secret password prompts and change the position of the cursor!

For a simple example, let us grab the current attributes or settings of the terminal using a handy std-lib module called “termios”:

import sys
import termios
# retrieve terminal file descriptor
fd = sys.stdin.fileno()
# grab the current settings
settings = termios.tcgetattr(fd)

If we return those settings, we should see something like this:

[17664, 5, 191, 35387, 15, 15, ['\x03', '\x1c', '\x7f', '\x15', '\x04', '\x00', '\x01', '\x00', '\x11', '\x13', '\x1a', '\x00', '\x12', '\x0f', '\x17', '\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00']]

Wow! That is a lot! These are all UTF-8 encoded raw bytes. We can easily change these settings later on, but for now, lets use “termios” to update the settings! This is as easy as setting the old settings now, which will refresh the settings buffer above.

import sys
import termios
# retrieve terminal file descriptor
fd = sys.stdin.fileno()
# grab the current settings
settings = termios.tcgetattr(fd)
# update settings
termios.tcsetattr(fd, termios.TCSANOW, settings)

Now we have a fresh input buffer! Lets use the above method to edit the settings and do some cool stuff!

import sys
import termios
# retrieve terminal file descriptor
fd = sys.stdin.fileno()
# grab the current settings
settings = termios.tcgetattr(fd)
# change one buffer in the settings
settings[3] &= ~termios.ECHO
# update settings
termios.tcsetattr(fd, termios.TCSANOW, settings)

Bam! Now when you call “sys.stdin.read” you will not see what you type! This is useful for password prompts. To undo the above terminal control operation, run this function:

def enable_echo ():
fd = sys.stdin.fileno()
setts = termios.tcgetattr(fd)
setts[3] |= termios.ECHO
termios.tcsetattr(fd,
termios.TCSANOW, setts)

There are endless possibilities for controlling terminals in UNIX based systems. The module “termios” has many constants and methods that do various terminal control operations. Here is a quick reference:

termios.tcsetattr(FD, WHEN, ATTRIBUTES)
Set FD's ATTRIBUTES according to WHEN
termios.tcflush(FD, QUEUE)
Flush FD's i/o queue according to QUEUE
termios.tcsendbreak(DURATION)
Send break command for DURATION
termios.tcgetattr(FD)
Return FD's standard attributes
termios.tcflow(FD, ACTION)
Suspends transmission or reception according to ACTION
termios.cfgetosspeed()
Retrieve line buffer rate (output BAUD).
termios.cfsetosspeed(B_RATE)
Set line buffer rate (output BAUD).
Possible WHEN arguments:
TCSANOW-----set attributes now
TCSAFLUSH-----set attributes after draining i/o
TCSADRAIN-----set attributes after draining o
Possible QUEUE arguments:
TCIFLUSH-----flush terminal's input queue
TCOFLUSH-----flush terminal's output queue
TCIOFLUSH-----flush terminal's i/o queue
Possible ACTION arguments:
TCOOFF-----suspends terminal's output
TCOON-----restarts suspended terminal output
TCIOFF-----transmits STOP character to terminal's input
TCION-----transmits START character to terminal's input

Possible B_RATE (o_BAUD) arguments:
B0 B50 B75 B110 B134 B150
B200 B300 B600 B1200 B1800
B2400 B4800 B9600 B19200
B38400 B57600 B115200
B230400

Additionally, there are non-argument constansts that can be used to edit the settings buffer from “tcgetattr”. They are many:

ECHO-----echo terminal's input queue
NOFLUSH-----no flushing terminal's i/o queues
ICANON-----enable canonical mode
ECHOE-----enable erase character's if ICANON is set
IEXTEN-----enable implementation-defined input processing
IGNBRK-----ignore break condition on input
BRKINT-----if not IGNBRK, flush i/o queues on BRK
IGNPAR-----ignore parity and framing errors
INPCK-----exact opposite of IGNPAR
IGNCR-----ignore carriage return on input
ICRNL-----translate carriage return to newline unless IGNCR
IXON-----enable flow control in input queue XON/XOFF
IXOFF-----disable flow control in input queue XON/XOFF
ECHOK-----if ICANON, kill character erases current line

NOTE: This guide only applies to Linux.