Embercode
✕
Variables and Data TypesLesson 15 of 16

40 min read

Command-line arguments in Python

Why it matters

Many programs need input before they start. A tool that compresses a file needs the file name. A script that checks a score needs the score. You can ask the user for these values while the program runs, which you will learn with input() in another lesson, or you can take them from the line the user typed to start the program. The second way is called command-line arguments. An argument is one word, or one quoted group of words, that follows the script name.

Arguments are common in tests and scripts. A judge or a build tool often runs your program as python3 main.py 5 12, with no prompts in between. If your program reads the arguments wrongly, it fails in a way that is hard to see. If it reads them carefully, it prints a clear message and stops.

This lesson covers three steps: reading the arguments, turning text into numbers, and checking that the numbers are usable. It uses the basic types from the variables lessons and the if and try statements from earlier in the course.

The idea: words typed after the name

Start with a program that does nothing except show what it received. Save this file as echo.py:

import sys
 
print("len(sys.argv) =", len(sys.argv))
for i, text in enumerate(sys.argv):
    print(f"sys.argv[{i}] = {text!r}")

The module sys holds the list sys.argv. Python fills it before your code runs. Each word typed after the script name becomes one entry in the list. The !r in the f-string shows each entry in quotes, so an empty entry is visible.

Run the file three ways. Here is what the program printed when it was tested:

$ python3 echo.py
len(sys.argv) = 1
sys.argv[0] = 'echo.py'
$ python3 echo.py 3 "hello world" 4.5
len(sys.argv) = 4
sys.argv[0] = 'echo.py'
sys.argv[1] = '3'
sys.argv[2] = 'hello world'
sys.argv[3] = '4.5'
$ python3 echo.py "" " 7"
len(sys.argv) = 3
sys.argv[0] = 'echo.py'
sys.argv[1] = ''
sys.argv[2] = ' 7'

Several facts are visible in this output.

  • The list always has at least one entry. sys.argv[0] is the script name, so with no arguments the length is 1. The number of real arguments is len(sys.argv) - 1.
  • The first real argument is sys.argv[1]. In Java, args[0] is the first argument, and the program name is not in the list. Python includes the script name, so the indexes are one higher.
  • The shell splits the line on spaces. The text "hello world" in quotes stays as one argument. Without quotes, hello world would be two arguments.
  • An empty pair of quotes is still an argument. The empty text "" takes a slot in the list, and so does " 7", which keeps its leading space.
  • Every argument is text, a str. The 4.5 in the output is not a number yet. It is three characters, and Python does not know it is a number until you convert it.

The shell is the program that reads what you type in the terminal. It does the splitting and the quote handling before Python sees anything. Python only receives the finished list of words.

Turning text into numbers

Arguments arrive as text, so you need a conversion step before arithmetic. For whole numbers, use int. The word parse means to read text and turn it into a value. The function takes a str and returns an int:

Python · editable
43

The text "42" became the number 42, and the addition then worked as a number.

The conversion can fail. If the text does not look like a whole number, int raises an exception. An exception is Python's signal that something went wrong during the run. It stops the normal flow of the code and jumps to a handler, or, if nothing handles it, the program ends with an error message. The exception type for a bad number is ValueError.

The course ran the same conversion on a set of inputs. Each line below shows the text and what int returned.

' 42' 42
'+5' 5
'-17' -17
'4.5' ValueError: invalid literal for int() with base 10: '4.5'
'' ValueError: invalid literal for int() with base 10: ''
'abc' ValueError: invalid literal for int() with base 10: 'abc'
'2147483648' 2147483648
'0x1F' ValueError: invalid literal for int() with base 10: '0x1F'
'1_000' 1000
'42 ' 42

Read the table for the patterns:

  • A plus or minus sign at the start is allowed.
  • Spaces at either end are allowed, so " 42" and "42 " both convert. Java's Integer.parseInt rejects them, so this is a difference to remember when you read Java code.
  • Underscores are allowed between digits, so "1_000" is 1000. This is the same rule as the digit separator in source code.
  • A decimal point is not allowed. Use float for numbers like 4.5.
  • The prefix 0x is not allowed in base 10. To read hexadecimal text, pass the base: int("1F", 16) is 31.
  • Python int has no upper limit, so "2147483648" converts. A fixed-size language would reject it.

The float function accepts a decimal point, and it also accepts spaces at the ends:

Python · editable
4.5
could not convert string to float: 'four'

The first line came from " 4.5 " with spaces at both ends. The second line is the error for "four".

Example 1: a greeting with a usage check

The first example is short. It takes one argument, a name, and prints a greeting. Its job is also to print a usage line, a short message that shows what to type, when the argument is missing. The file is saved as greet.py.

import sys
 
if len(sys.argv) < 2:
    print("usage: python3 greet.py <name>")
    sys.exit(1)
name = sys.argv[1]
print(f"Hello, {name}!")

The check len(sys.argv) < 2 must come before any use of sys.argv[1]. The reason is that sys.argv[1] on a one-entry list is an error, as the next sections show. The function sys.exit(1) ends the program right away and gives the operating system the number 1. By convention, 0 means success and any other number means an error. A script that runs the program can read that number.

The course ran it three times. Here is the output:

$ python3 greet.py
usage: python3 greet.py <name>
$ python3 greet.py Ana
Hello, Ana!
$ python3 greet.py Ana Lee
Hello, Ana!

The third run gave two arguments, Ana and Lee. The program reads only sys.argv[1] and ignores the rest. A program that expects one value does not have to reject extra values, but it should say so if the extra values matter. The next example checks the count exactly.

Example 2: adding two numbers

The second example reads numbers from the arguments and adds them. It uses a loop over the arguments after the script name, so the same code works for any count. It also prints a usage line when the count is wrong, and it reports bad entries without stopping. The file is saved as sum_args.py.

import sys
 
if len(sys.argv) != 3:
    print("usage: python3 sum_args.py <number> <number>")
    sys.exit(1)
total = 0
for text in sys.argv[1:]:
    try:
        value = int(text)
        total += value
        print(f"{text} -> {value}")
    except ValueError as error:
        print(f"not a whole number: {error}")
print(f"total = {total}")

Three parts of this program need a note.

  • The slice sys.argv[1:], a part of a list written with a colon, takes every entry from position 1 to the end, which is the list of real arguments.
  • The try block holds code that might raise. The except block handles the exception if one happens. Without the except, the bad text would end the program with a long error message.
  • The variable error holds the exception object, and the f-string prints its message.

Here are two runs. The first gives the wrong count, so it prints the usage line and exits with code 1. The second gives one valid number and one bad one.

$ python3 sum_args.py 1
usage: python3 sum_args.py <number> <number>
exit=1
$ python3 sum_args.py 10 x
10 -> 10
not a whole number: invalid literal for int() with base 10: 'x'
total = 10
exit=0

The exit code is 1 in the first run because the program called sys.exit(1). You can see it in a shell with echo $? on Linux and macOS.

The second run shows the design choice. The program reports x and keeps going, so total is the sum of the good values only. Whether that is correct depends on the task. In a real tool you might stop instead. The point of the example is that the conversion error is caught and printed, not that this is the only correct behaviour.

Example 3: checking ranges

Parsing text tells you the text is a whole number. It does not tell you the number is sensible. An age of -4 is a whole number but not a valid age. The third example adds a range check, uses strip(), and collects the good values in a list. The file is saved as ages.py.

import sys
 
if len(sys.argv) == 1:
    print("usage: python3 ages.py <age> [<age> ...]")
    sys.exit(0)
 
valid = []
for text in sys.argv[1:]:
    try:
        age = int(text.strip())
    except ValueError:
        print(f'skipped "{text}": not a whole number')
        continue
    if age < 0 or age > 150:
        print(f"skipped {age}: outside 0 to 150")
        continue
    valid.append(age)
 
print("valid ages:", valid)
if valid:
    print("average:", sum(valid) / len(valid))

This example has the most moving parts, so read it in layers.

  • text.strip() removes the spaces at each end before the conversion. Python's int already accepts those spaces, so the call only makes the intent clear.
  • The continue in the except block skips to the next argument, so the range check never runs with an age that was not set.
  • The list valid holds the good values in the order they were typed. valid.append(age) adds one value at the end.
  • sum(valid) / len(valid) divides as decimals, because / always gives a float in Python 3. The integer division // would drop the fraction, so 53 // 2 would give 26 and 53 / 2 gives 26.5.

The course ran this with a mix of inputs:

$ python3 ages.py 20 abc -4 151 " 33 "
skipped "abc": not a whole number
skipped -4: outside 0 to 150
skipped 151: outside 0 to 150
valid ages: [20, 33]
average: 26.5
$ python3 ages.py
usage: python3 ages.py <age> [<age> ...]

Each bad argument is handled at its own point, and the good ones still count. A user can see exactly which entry went wrong.

What happens without a check

Errors can also be left unhandled. Here is a short program with no checks at all, saved as crash.py:

import sys
 
n = int(sys.argv[1])
print(n)

With no arguments, the program fails before the conversion runs. Here is the output the course captured:

$ python3 crash.py
Traceback (most recent call last):
  File ".../crash.py", line 3, in <module>
    n = int(sys.argv[1])
            ~~~~~~~~^^^
IndexError: list index out of range

With a text that is not a number, the failure is at the conversion:

$ python3 crash.py twelve
Traceback (most recent call last):
  File ".../crash.py", line 3, in <module>
    n = int(sys.argv[1])
ValueError: invalid literal for int() with base 10: 'twelve'

Read the last line first. It names the exception type and the reason. The line above it, with the file name and line number, is the line in your code that failed. The ^ and ~ marks show which part of that line was involved. These messages are useful for finding bugs while you develop. They are not what a finished tool should show a user. The examples above add checks so that the user sees a short usage line instead.

Common mistakes

Treating sys.argv[0] as the first argument. sys.argv[0] is the script name. The first real argument is sys.argv[1]. A program that reads sys.argv[0] as a number fails with ValueError on the script name.

Reading an index before checking the count. Using sys.argv[1] when only the script name is there gives IndexError: list index out of range. Check len(sys.argv) first, as the examples do.

Parsing a decimal with int. The text 4.5 fails with ValueError: invalid literal for int() with base 10: '4.5'. Use float for numbers with a decimal point. The text 1,000 also fails, because the comma is not a digit. Remove the commas before the conversion.

Comparing text with the wrong operator. In Python, == compares the letters of two strings, so sys.argv[1] == "yes" is True when the argument is yes. The mistake to avoid is is. It compares objects, and it can give False for two equal strings that were built separately.

Forgetting that quotes group words. A user who types python3 greet.py Ana Lee gets two arguments. Your program sees "Ana" and "Lee", not one name. If the name should be one value, the user must type python3 greet.py "Ana Lee". The program cannot tell the difference after the shell has split the line, so document the expected form in the usage line.

Assuming a bad number is zero. Some languages turn a bad number into 0. Python does not. The conversion raises ValueError, so you must catch it or stop. Catching and then using 0 as a default is allowed, but make it a decision you write down, not a side effect.

Assuming a whole number is in range. int("-4") works and is still wrong for an age. A conversion proves the text is a number of the right kind. Your own checks prove the value makes sense.

Practice

Work each exercise yourself before you open the answer. Run each program with the arguments named in the exercise.

  1. Write a program echo_args.py that prints every argument on its own line, each prefixed with its position, starting at 1. Run it with python3 echo_args.py a "b c".
  2. Write twice_it.py that prints twice the value of its first argument. If there is no argument, print usage: python3 twice_it.py <whole number> and exit with code 1. If the argument is not a whole number, print not a whole number and exit with code 2.
  3. Write scores.py that takes any number of arguments. Each argument is a score. Count how many are valid, where valid means a whole number from 0 to 100. Print the count of valid scores and the count of invalid ones. Do not stop at the first bad value.
  4. A program is run with python3 pair.py 3 4 5. It needs exactly two numbers. Write the check that prints usage: python3 pair.py <a> <b> and exits with code 1 when the count is wrong. Then say why the check must come before any use of sys.argv[2].
  5. A user types python3 greet.py "Ana Lee" with two spaces between the names. Explain what sys.argv[1] contains, and how the program can split it into the two names.

Each exercise has an answer below a "Show the answer" line. Try each one first, then read the answer.

  1. The loop prints the position and the argument. The position starts at 1, so enumerate gets start=1.

Show the answer

import sys
 
for i, text in enumerate(sys.argv[1:], start=1):
    print(f"{i}: {text}")

The output for python3 echo_args.py a "b c" is 1: a on the first line and 2: b c on the second.

  1. The first check handles the missing argument, and the try handles the bad one. Each path ends with the right exit code.

Show the answer

import sys
 
if len(sys.argv) < 2:
    print("usage: python3 twice_it.py <whole number>")
    sys.exit(1)
try:
    value = int(sys.argv[1])
except ValueError:
    print("not a whole number")
    sys.exit(2)
print(value * 2)

Running python3 twice_it.py 21 prints 42. Running python3 twice_it.py x prints not a whole number and exits with code 2.

  1. Count the valid and invalid values in one loop. A bad value adds to the invalid count and does not stop the loop.

Show the answer

import sys
 
valid = 0
invalid = 0
for text in sys.argv[1:]:
    try:
        score = int(text.strip())
    except ValueError:
        invalid += 1
        continue
    if 0 <= score <= 100:
        valid += 1
    else:
        invalid += 1
 
print("valid:", valid)
print("invalid:", invalid)

Run with python3 scores.py 50 101 abc 0, the output is valid: 2 and invalid: 2.

  1. The check uses the length of sys.argv, which includes the script name. It must come first, because sys.argv[2] on a list with fewer than three entries raises IndexError.

Show the answer

import sys
 
if len(sys.argv) != 3:
    print("usage: python3 pair.py <a> <b>")
    sys.exit(1)
a = int(sys.argv[1])
b = int(sys.argv[2])
print(a + b)

The run python3 pair.py 3 4 5 has four entries in sys.argv, so the check fails and the program prints the usage line and exits with code 1. The check guards the reads, and it is only useful if it runs before them.

  1. sys.argv[1] holds the whole text Ana Lee with both spaces kept, because the quotes group it into one argument. The program can split it at one or more spaces with split(), which takes any run of whitespace as one separator.

Show the answer

import sys
 
first, last = sys.argv[1].split()
print(first)
print(last)

The output is Ana and then Lee. The call split() with no argument removes the extra spaces, so the two names come out clean.

What you now know

  • Command-line arguments are the words typed after the script name, and Python gives them to the program as the list sys.argv.
  • sys.argv[0] is the script name. The first real argument is sys.argv[1], and len(sys.argv) - 1 is the number of arguments.
  • Arguments are text. Convert them with int or float, and strip() the text first if spaces may be present.
  • A failed conversion raises ValueError. Catch it, or stop with a usage message, instead of letting the program crash.
  • Check len(sys.argv) before reading sys.argv[i], and check that each converted value is in the range the task allows.
  • Use sys.exit with a nonzero number for an error. Compare text with ==, not is.
Practice this topic: coding problems for Variables and Data Types →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Division by zero in Python