CLI
Contents
CLI#
In this class we’ve been writing CLI programs, as opposed to a GUI. While most of the programs an average person interacts with on a day-to-day basis are most likely GUIs, text-based programs have their advantages.
They can often be faster, as they take up fewer resources needing to load and render graphics. The interface can be more precise and less error prone to interact with, as typed commands are less ambiguous than clicking on regions. And they can be powerful, making it possible to accomplish a lot by stringing together just a few commands. Plus, they’re a lot easier to write.
In this lesson we’ll learn about writing programs intended to be run in a command line environment.
Table of Contents
Part 1: Arguments#
Just like functions can take arguments, so can programs. This is done on the command line, typically separated by spaces, with multi-word arguments surrounded by single or double quotes.
In Python, those arguments can be accessed at sys.argv
which stores a list of
strings. The first list item is the name of the program being executed
followed by one argument per list item.
Note all arguments are stored as strings, so as in the following example, type conversion is often necessary.
1import sys
2print(sys.argv)
$ python program.py arg1 arg2 arg3
['cli.py', 'arg1', 'arg2', 'arg3']
$ python program.py "multi-word argument 1" arg2
['cli.py', 'multi-word argument 1', 'arg2']
This can be used to control program behavior, in major or minor ways. Here for
example is a simple program randnums.py
which prints 3
random numbers
by default, or the number specified by the first argument if one exists.
1import sys
2import random
3
4count = 3
5
6if len(sys.argv) >= 2:
7 count = int(sys.argv[1])
8
9for _ in range(count):
10 print(random.randint(0, 100))
$ python randnums.py
50
62
76
$ python randnums.py 2
15
46
Part 1.1: Exercise#
(Countdown With Args Exercise)
Write a program which counts down from 3
by default, or from the number
passed by the first argument if present. Use time.sleep()
to pause for a
second between printing each number.
Need help?
Import the
sys
andtime
modules.Assign the value
3
to thecount
variable.If there are at least
2
values in thesys.argv
list, set count to be the second item in the list, converted to anint
.Use a
for
loop with a variable name ofnum
to iterate over arange
with astart
value ofcount
, astop
value of0
, and astep
value of-1.
Hint: Use
help(range)
for more information onrange
objects.Print
num
followed by...
Call
time.sleep()
with an argument of1
Example output:
$ python countdown.py
3...
2...
1...
$ python countdown.py 5
5...
4...
3...
2...
1...
Solution to Exercise 80 (Countdown With Args Exercise)
1"""Countdown exercise for the CLI Lesson
2 https://alissa-huskey.github.io/python-class/lessons/cli.html
3"""
4import sys
5import time
6
7count = 3
8
9if len(sys.argv) >= 2:
10 count = int(sys.argv[1])
11
12for num in range(count, 0, -1):
13 print(f"{num}...")
14 time.sleep(1)
$ python countdown.py
3...
2...
1...
$ python countdown.py 5
5...
4...
3...
2...
1...
Part 2: Environment Variables#
Just like we can set variables in our programs, we can also set variables on
the command line. There are known as environment variables, and they
are typically stored as all caps. For example, most systems set the variables
SHELL
, TERM
, HOME
and TMPDIR
.
To reference a variable on the command line, prefix it with a $
.
$ echo $SHELL
/bin/zsh
$ echo $TERM
xterm-256color
$ echo $HOME
/Users/pythonclass
$ echo $TMPDIR
/var/folders/mn/qt2cdhdn5md6hrwjrz5sp60m0000gn/T/
To set an environment variable, use the typeset
or export
command as in the
following example. (Note: The variable only exists only for the duration of
your current terminal session.)
$ typeset -gx LANGUAGE=en
$ export LANGUAGE=en
In Python, we can access this using the os.environ
dictionary.
import os
print(os.environ["TERM"])
xterm-color
If you’re not sure if the environment variable exists, use the .get()
method,
with an optional second argument that will be the value returned if it is
missing.
import os
lang = os.environ.get("LANGUAGE", "en")
print(lang)
en
Part 2.1: Exercise#
(Environment Variables Exercise)
Modify your countdown program to check the environment variable VERBOSE
and the message
"Counting down from COUNT."
if it is set to a non-blank value.
Test it with the envionment variable not set, set to ""
, and set to a value like "yes"
.
Solution to Exercise 81 (Environment Variables Exercise)
1"""Countdown exercise for the CLI Lesson
2 https://alissa-huskey.github.io/python-class/lessons/cli.html
3"""
4import os
5import sys
6import time
7
8count = 3
9is_verbose = os.environ.get("VERBOSE", False)
10
11if is_verbose:
12 print(f"Counting down from {count}.")
13
14if len(sys.argv) > 2:
15 print(f"Warning: extra arguments: {sys.argv[2:]}", file=sys.stderr)
16
17if len(sys.argv) >= 2:
18 count = int(sys.argv[1])
19
20for num in range(count, 0, -1):
21 print(f"{num}...")
22 time.sleep(1)
$ python countdown.py
3...
2...
1...
$ export VERBOSE=""
$ python countdown.py
3...
2...
1...
$ export VERBOSE=yes
$ python countdown.py
Counting down from 3.
3...
2...
1...
Part 3: Input and Output#
In a command line environment, input and output are handled via three special kinds of files called that serve as data streams: stdin, stdout and stderr. Each one has an associated FD number, which we’ll learn more about later.
Descriptor |
Name |
FD |
Source / Destination |
---|---|---|---|
standard input |
|
from the keyboard |
|
standard output |
|
to the screen |
|
standard error |
|
to the screen |
In Python, these can be accessed in the sys
module.
When you use the print()
function, for example it prints to stdout
by
default. This is the same as:
import sys
print("hello", file=sys.stdout)
hello
As I mentioned, these are all special kinds of files, and in Python they are represented as file handler objects. As such, you can interact with them the same way you would with a normal file object. So yet another way to accomplish the same thing is:
import sys
sys.stdout.write("hello\n")
hello
6
The most common use of file handlers is to write to stderr
instead of stdout
.
import sys
print("Danger, Will Robinson!", file=sys.stderr)
Danger, Will Robinson!
This is useful because on those streams can be handled separately on the
command line. While a complete lesson on redirection is outside of the
scope of this lesson, the most common use case is to send a particular stream
to a different destination with the syntax: COMMAND FD> DESTINATION
.
The following for example sends the results of the ls
command to the file
files.txt
. (The file descriptor defaults to stdout
.)
$ ls > files.txt
Or we could send all error messages to errors.log
or to /dev/null
(on most
systems) to silence them completely. Here we use the file descriptor number 2
immediately before the >
to indicate that we want to redirect stderr
instead of stdout
.
$ ls 2> errors.log
file1 file2 file3
$ ls 2> /dev/null
file1 file2 file3
Take the following example, which prints some messages to stderr
and others
to stdout
.
1import sys
2print("Welcome!")
3print("Danger, Will Robinson!", file=sys.stderr)
4print("Farewell.")
If we redirect stdout
to the file messages.txt
the faux error message will
be printed to the screen and excluded from the messages.txt
file.
$ python using-stderr.py > messages.txt
Danger, Will Robinson!
$ cat messages.txt
Welcome!
Farewell.
Part 3.1: Exercise#
(Stderr Exercise)
Modify your countdown function to print a message to stderr
if there is more
than one argument passed.
Test this on the command line with more than one argument by:
redirecting stdout to
countdown.txt
redirecting stderr to
errors.log
redirecting stderr to
/dev/null
Solution to Exercise 82 (Stderr Exercise)
1"""Countdown exercise for the CLI Lesson
2 https://alissa-huskey.github.io/python-class/lessons/cli.html
3"""
4import os
5import sys
6import time
7
8count = 3
9is_verbose = os.environ.get("VERBOSE", False)
10
11if is_verbose:
12 print(f"Counting down from {count}.")
13
14if len(sys.argv) > 2:
15 print(f"Warning: extra arguments: {sys.argv[2:]}", file=sys.stderr)
16
17if len(sys.argv) >= 2:
18 count = int(sys.argv[1])
19
20for num in range(count, 0, -1):
21 print(f"{num}...")
22 time.sleep(1)
$ python countdown.py 1
1...
$ python countdown.py 1 2
Warning: extra arguments: ['2']
1...
$ python countdown.py 1 2 > countdown.txt
Warning: extra arguments: ['2']
$ python countdown.py 1 2 2> errors.log
1...
$ python countdown.py 1 2 2> /dev/null
1...
Part 4: Exiting Programs#
When a command line programs ends, it returns an exit code to indicate
success or failure. Traditionally an exit code of 0
indicates success and
anything else indicates some kind of failure. Some programs use different exit
codes to let you know why the program failed.
You can see the exit code of the last command with the special variable $?
.
Lets use the ls
command as an example.
$ ls
file1 file2 file3
$ echo $?
0
$ ls x
ls: cannot access 'x': No such file or directory
$ echo $?
2
To exit a program in Python use sys.exit()
with an optional exit code
argument. In the following example we expand the randnums.py
script to exit
with an exit code of 1
.
1import sys
2import random
3
4count = 3
5
6if len(sys.argv) > 2:
7 print(f"Warning: extra arguments: {sys.argv[2:]}", file=sys.stderr)
8
9if len(sys.argv) >= 2:
10
11 if not sys.argv[1].isnumeric():
12 print("Count must be a number.")
13 sys.exit(1)
14
15 count = int(sys.argv[1])
16
17for _ in range(count):
18 print(random.randint(0, 100))
Part 4.1: Exercise#
(Exiting Exercise)
Modify your countdown program, so that when an argument is passed that is not a
number, print an error message and exit with an exit code of 1
. Test with
both numeric and non-numeric arguments.
Need help?
If you already have an if statement checking that sys.argv
has at least two
items, do the following in it. Otherwise, make one.
In an if
statement, on second item in the sys.argv
list, check that the
results of the .isnumeric()
method is False
.
print an error message
call
sys.exit()
with the argument1
Solution to Exercise 83 (Exiting Exercise)
1"""Countdown exercise for the CLI Lesson
2 https://alissa-huskey.github.io/python-class/lessons/cli.html
3"""
4import os
5import sys
6import time
7
8count = 3
9is_verbose = os.environ.get("VERBOSE", False)
10
11if is_verbose:
12 print(f"Counting down from {count}.")
13
14if len(sys.argv) > 2:
15 print(f"Warning: extra arguments: {sys.argv[2:]}", file=sys.stderr)
16
17if len(sys.argv) >= 2:
18 if not sys.argv[1].isnumeric():
19 print(f"Error: Countdown count should be a number: {sys.argv[1]}", file=sys.stderr)
20 sys.exit(1)
21
22 count = int(sys.argv[1])
23
24for num in range(count, 0, -1):
25 print(f"{num}...")
26 time.sleep(1)
$ python countdown.py a
Error: Countdown count should be a number: a
$ echo $?
1
$ python countdown.py 2
2...
1...
$ echo $?
0
Part 5: Terminal size#
If you need to know the size of the terminal, you can use the
shutil.get_terminal_size()
function.
import shutil
shutil.get_terminal_size()
os.terminal_size(columns=80, lines=24)
It returns a terminal_size
object, which provides the
properties columns
and lines
.
import shutil
size = shutil.get_terminal_size()
print("width:", size.columns)
print("height:", size.lines)
width: 80
height: 24
It is also a special kind of tuple
, which means you can make use of multiple
assignment.
import shutil
width, height = shutil.get_terminal_size()
print("width:", width)
print("height:", height)
width: 80
height: 24
On systems where the size cannot be determined, the defaults (80, 24)
will be used. To change the defaults, send an tuple
argument with
(columns, lines)
.
import shutil
width, height = shutil.get_terminal_size((125, 33))
print("width:", width)
print("height:", height)
width: 125
height: 33
Part 5.1: Exercise#
(Terminal Size Exercise)
In the following example, we use the width from get_terminal_size()
print
the text "Hello world!"
centered on the screen.
Need help?
Import the
shutil
module.Set the variable
text
to the value"Hello world!"
Assign
width
andheight
to the value returned fromshutil.get_terminal_size()
.To get a string with centered text call the
.center()
method ontext
with a width argument ofwidth
. Assign the returned value totext
.Print
text
.
**Example output**:
Hello world!
Solution to Exercise 84 (Terminal Size Exercise)
1import shutil
2
3text = "Hello world!"
4width, height = shutil.get_terminal_size()
5text = text.center(width)
6
7print(text.center(width))
Part 6: Colors and Styles#
Many terminals use ANSI escape codes to control the font colors and style and many other things.
Fortunately, there are modules that handle all the complication of escape codes for us. The one I recommend is console, which works on Mac/Linux/Windows.
Important
Not all systems support escape codes, though one advantage to using a module like this is that it does its best to detect if colors will work with that system, and if not, does nothing.
Just be careful not to rely too heavily on colors and styles, so your program still works even if they don’t show up. Also, be aware that some colors look different or are harder to read with a different background color, so its a good idea to test with a light and back background color.
Part 6.1: Installation#
pip install console
poetry add console
Part 6.2: Usage#
To see a demo of all available features and how they work in your terminal you
can run console.demo
from the command line.
python -m console.demos
To change the text colors and styles, use the fg
, bg
and fx
objects to
change the foreground color, background color, and effects respectively.
from console import fg, bg, fx
For text effects, use the methods on the fx
object.
print(fx.bold("Attention!"))
For foreground and background colors use methods on the fg
and bg
objects
respectively, such as the 16 basic colors supported by most terminals.
print(fg.green("SUCCESS"))
print(bg.magenta("Information"))
Some terminals also provide an extended set of 256
indexed colors (shown in
the aforementioned demo). Methods for these colors are named i0
through
i255
.
print(fg.i197("Error"))
For terminals that support “True”, RGB, or 16 million colors, you can use any
6-character hex color code prefixed with t_
.
print("default=" + fg.t_f4c2c2("None"))
Or you can use X11 color names, prefixed with x_
.
print(bg.midnightblue("Home"))
print(fg.x_deepskyblue("Tutorial"))
You can combine a fg
, bg
, and/or fx
object to create a custom, callable
style.
highlight = fx.italic + fg.yellow + bg.blue
print(highlight("Great job!"))
Part 6.3: Available colors and styles#
Text Effects
Text effects methods available via the fx
object:
Basic Colors
Methods for the 16 basic colors available via the fg
and bg
objects.
X11 Colors
Methods for RGB colors associated with X11 color names available
via the fg
and bg
objects.
b
blink
bold
conceal
crossed
curly_underline
dim
double_underline
dunder
encircle
fastblink
frame
hide
i
italic
overline
reverse
s
slowblink
strike
u
underline
black
blue
cyan
green
lightblack
lightblue
lightcyan
lightgreen
lightmagenta
lightpurple
lightred
lightwhite
lightyellow
magenta
purple
red
white
yellow
snow
ghostwhite
whitesmoke
gainsboro
floralwhite
oldlace
linen
antiquewhite
papayawhip
blanchedalmond
bisque
peachpuff
navajowhite
moccasin
cornsilk
ivory
lemonchiffon
seashell
honeydew
mintcream
azure
aliceblue
lavender
lavenderblush
mistyrose
white
black
darkslategray
darkslategrey
dimgray
dimgrey
slategray
slategrey
lightslategray
lightslategrey
gray
grey
lightgrey
lightgray
midnightblue
navy
navyblue
cornflowerblue
darkslateblue
slateblue
mediumslateblue
lightslateblue
mediumblue
royalblue
blue
dodgerblue
deepskyblue
skyblue
lightskyblue
steelblue
lightsteelblue
lightblue
powderblue
paleturquoise
darkturquoise
mediumturquoise
turquoise
cyan
lightcyan
cadetblue
mediumaquamarine
aquamarine
darkgreen
darkolivegreen
darkseagreen
seagreen
mediumseagreen
lightseagreen
palegreen
springgreen
lawngreen
green
chartreuse
mediumspringgreen
greenyellow
limegreen
yellowgreen
forestgreen
olivedrab
darkkhaki
khaki
palegoldenrod
lightgoldenrodyellow
lightyellow
yellow
gold
lightgoldenrod
goldenrod
darkgoldenrod
rosybrown
indianred
saddlebrown
sienna
peru
burlywood
beige
wheat
sandybrown
tan
chocolate
firebrick
brown
darksalmon
salmon
lightsalmon
orange
darkorange
coral
lightcoral
tomato
orangered
red
hotpink
deeppink
pink
lightpink
palevioletred
maroon
mediumvioletred
violetred
magenta
violet
plum
orchid
mediumorchid
darkorchid
darkviolet
blueviolet
purple
mediumpurple
thistle
snow1
snow2
snow3
snow4
seashell1
seashell2
seashell3
seashell4
antiquewhite1
antiquewhite2
antiquewhite3
antiquewhite4
bisque1
bisque2
bisque3
bisque4
peachpuff1
peachpuff2
peachpuff3
peachpuff4
navajowhite1
navajowhite2
navajowhite3
navajowhite4
lemonchiffon1
lemonchiffon2
lemonchiffon3
lemonchiffon4
cornsilk1
cornsilk2
cornsilk3
cornsilk4
ivory1
ivory2
ivory3
ivory4
honeydew1
honeydew2
honeydew3
honeydew4
lavenderblush1
lavenderblush2
lavenderblush3
lavenderblush4
mistyrose1
mistyrose2
mistyrose3
mistyrose4
azure1
azure2
azure3
azure4
slateblue1
slateblue2
slateblue3
slateblue4
royalblue1
royalblue2
royalblue3
royalblue4
blue1
blue2
blue3
blue4
dodgerblue1
dodgerblue2
dodgerblue3
dodgerblue4
steelblue1
steelblue2
steelblue3
steelblue4
deepskyblue1
deepskyblue2
deepskyblue3
deepskyblue4
skyblue1
skyblue2
skyblue3
skyblue4
lightskyblue1
lightskyblue2
lightskyblue3
lightskyblue4
slategray1
slategray2
slategray3
slategray4
lightsteelblue1
lightsteelblue2
lightsteelblue3
lightsteelblue4
lightblue1
lightblue2
lightblue3
lightblue4
lightcyan1
lightcyan2
lightcyan3
lightcyan4
paleturquoise1
paleturquoise2
paleturquoise3
paleturquoise4
cadetblue1
cadetblue2
cadetblue3
cadetblue4
turquoise1
turquoise2
turquoise3
turquoise4
cyan1
cyan2
cyan3
cyan4
darkslategray1
darkslategray2
darkslategray3
darkslategray4
aquamarine1
aquamarine2
aquamarine3
aquamarine4
darkseagreen1
darkseagreen2
darkseagreen3
darkseagreen4
seagreen1
seagreen2
seagreen3
seagreen4
palegreen1
palegreen2
palegreen3
palegreen4
springgreen1
springgreen2
springgreen3
springgreen4
green1
green2
green3
green4
chartreuse1
chartreuse2
chartreuse3
chartreuse4
olivedrab1
olivedrab2
olivedrab3
olivedrab4
darkolivegreen1
darkolivegreen2
darkolivegreen3
darkolivegreen4
khaki1
khaki2
khaki3
khaki4
lightgoldenrod1
lightgoldenrod2
lightgoldenrod3
lightgoldenrod4
lightyellow1
lightyellow2
lightyellow3
lightyellow4
yellow1
yellow2
yellow3
yellow4
gold1
gold2
gold3
gold4
goldenrod1
goldenrod2
goldenrod3
goldenrod4
darkgoldenrod1
darkgoldenrod2
darkgoldenrod3
darkgoldenrod4
rosybrown1
rosybrown2
rosybrown3
rosybrown4
indianred1
indianred2
indianred3
indianred4
sienna1
sienna2
sienna3
sienna4
burlywood1
burlywood2
burlywood3
burlywood4
wheat1
wheat2
wheat3
wheat4
tan1
tan2
tan3
tan4
chocolate1
chocolate2
chocolate3
chocolate4
firebrick1
firebrick2
firebrick3
firebrick4
brown1
brown2
brown3
brown4
salmon1
salmon2
salmon3
salmon4
lightsalmon1
lightsalmon2
lightsalmon3
lightsalmon4
orange1
orange2
orange3
orange4
darkorange1
darkorange2
darkorange3
darkorange4
coral1
coral2
coral3
coral4
tomato1
tomato2
tomato3
tomato4
orangered1
orangered2
orangered3
orangered4
red1
red2
red3
red4
debianred
deeppink1
deeppink2
deeppink3
deeppink4
hotpink1
hotpink2
hotpink3
hotpink4
pink1
pink2
pink3
pink4
lightpink1
lightpink2
lightpink3
lightpink4
palevioletred1
palevioletred2
palevioletred3
palevioletred4
maroon1
maroon2
maroon3
maroon4
violetred1
violetred2
violetred3
violetred4
magenta1
magenta2
magenta3
magenta4
orchid1
orchid2
orchid3
orchid4
plum1
plum2
plum3
plum4
mediumorchid1
mediumorchid2
mediumorchid3
mediumorchid4
darkorchid1
darkorchid2
darkorchid3
darkorchid4
purple1
purple2
purple3
purple4
mediumpurple1
mediumpurple2
mediumpurple3
mediumpurple4
thistle1
thistle2
thistle3
thistle4
gray0
grey0
gray1
grey1
gray2
grey2
gray3
grey3
gray4
grey4
gray5
grey5
gray6
grey6
gray7
grey7
gray8
grey8
gray9
grey9
gray10
grey10
gray11
grey11
gray12
grey12
gray13
grey13
gray14
grey14
gray15
grey15
gray16
grey16
gray17
grey17
gray18
grey18
gray19
grey19
gray20
grey20
gray21
grey21
gray22
grey22
gray23
grey23
gray24
grey24
gray25
grey25
gray26
grey26
gray27
grey27
gray28
grey28
gray29
grey29
gray30
grey30
gray31
grey31
gray32
grey32
gray33
grey33
gray34
grey34
gray35
grey35
gray36
grey36
gray37
grey37
gray38
grey38
gray39
grey39
gray40
grey40
gray41
grey41
gray42
grey42
gray43
grey43
gray44
grey44
gray45
grey45
gray46
grey46
gray47
grey47
gray48
grey48
gray49
grey49
gray50
grey50
gray51
grey51
gray52
grey52
gray53
grey53
gray54
grey54
gray55
grey55
gray56
grey56
gray57
grey57
gray58
grey58
gray59
grey59
gray60
grey60
gray61
grey61
gray62
grey62
gray63
grey63
gray64
grey64
gray65
grey65
gray66
grey66
gray67
grey67
gray68
grey68
gray69
grey69
gray70
grey70
gray71
grey71
gray72
grey72
gray73
grey73
gray74
grey74
gray75
grey75
gray76
grey76
gray77
grey77
gray78
grey78
gray79
grey79
gray80
grey80
gray81
grey81
gray82
grey82
gray83
grey83
gray84
grey84
gray85
grey85
gray86
grey86
gray87
grey87
gray88
grey88
gray89
grey89
gray90
grey90
gray91
grey91
gray92
grey92
gray93
grey93
gray94
grey94
gray95
grey95
gray96
grey96
gray97
grey97
gray98
grey98
gray99
grey99
gray100
grey100
darkgrey
darkgray
darkblue
darkcyan
darkmagenta
darkred
lightgreen
See Also#
See also
colorhexa.com – a page with a list of an assortment of hex colors by name
Reference#
Glossary#
Cli#
- CLI#
- command line interface#
A program executed at the command line with a text-based user interface.
- GUI#
- graphical user interface#
A program where users provide input primarily by manipulating visual elements. Examples include ATMs, Windows, MacOS, nearly all smartphones, web browsers, and office programs.
- stdin#
The standard input file stream.
- stdout#
The standard output file stream.
- stderr#
The standard error file stream.
- FD#
- file descriptor#
A unique identifier associated with an input/output resource, most often a positive number.
- environment variables#
A variable is set on the command line and effects how programs are run or behave.
- exit code#
- status code#
A number between
0
and255
returned by command line programs to indicate success or failure.