Tuesday, October 8, 2013

$1 in Python

Interesting question


"Why cant I use $1 in a Python script to get the first argument to a script? What is the easiest way to do something like that in Python?"

$1 in a shell script gives us the first argument to the script, BTW.

I get a lot of questions through email, and in person. This one was part of an interesting exchange. The reasoning went like this, since the shell is processing the script, I should be able to use $1.

The problem is that it doesn't work like that. Dennis Ritchie introduced the #! (pound bang or shebang line) concept at Bell Labs and announced it publicly in 1980. In the exece function of sys1.c could be found the following:

 else if (u.u_exdata.S[0]=='#' && u.u_exdata.S[1]=='!' && indir==0) {
  cp = &u.u_exdata.S[2];
  while (*cp==' ' && cp<&u.u_exdata.S[SHSIZ])
   cp++;
  u.u_dirp = cp;
  while (cp < &u.u_exdata.S[SHSIZ-1]  && *cp != '\n')
   cp++;
  *cp = '\0';
  indir++;
  iput(ip);
  ip = namei(schar, 0);
  if (ip==NULL)
   return;
  goto again;
 
 
So, once it was established that a script started with #! and a variable number of spaces, what followed was the path to an executable that would process this script. No search, so it has to be a full path.

In that regard, if the line starts with #!/usr/bin/python or #!/usr/bin/env python, the shell is out of the loop and no $1. Python will process the script. There is an sh module that makes it appear like we are mixing Python and shell in one script, but it is still all Python code.

Simplest shell script


#!/usr/bin/bash

echo $1

Simplest Python


#!/usr/bin/env python
from sys import argv as s


print(s[1])

Fairly similar except we had to specify an import statement, and I aliased argv to s so it looked like $ in the shell script.

The "problem" (or difference) with the Python script is that it will error out if no arguments are given when run:

$ ./arg.py
Traceback (most recent call last):
  File "./arg.py", line 5, in <module>
    print(s[1])
IndexError: list index out of range

A closer equivalent



#!/usr/bin/env python
from sys import argv as s

print(s[1] if len(s)>1 else '')

Here we are simply testing to see how many items are in the argv list. The first item is the script name, so if we have >1 items, we have at a minimum one argument, so it is safe to get [1]. If not, we will return an empty string, just like a shell script would do. A try / except IndexError would have also worked in this context.

See also

I would also investigate argparse, opster and docopt. argv is really bare metal and there is no point in reinventing the wheel.


François
@f_dion

1 comment:

Unknown said...
This comment has been removed by the author.