Skip to main content

Py1

·3 mins

Py1, a Python AWK #

One should use the right tool for the right task. But Learning 300 tools is counterproductive, so one needs a fallback. To be generic enough that fallback must be scriptable. So we have AWK, Perl, Sed, TCL… and their read-only languages.

Enters py1, my new project. It’s a “Python AWK”.

You can use it to write Python one-liners. Just use {{ and }} instead of identation, ; instead of line feed:

py1 "for x in range(4): {{ print x; print x*2 }}"

You promised AWK #

In most case you want to start with something, do something for each line, then conclude. py1 provides a template for AWK-like usage.

py1 --begin 'count=0' --each-line "if 'cow' in L: count += 1" --end 'P(count)'

To ease hacking, py1 provides you a set of 1&2-letters variables and functions. In the example above we have L, the current line. We also have P(), an alias for print(). They can be overriden, for examples lines are read from F:

py1 -b 'F=zip(open("file1"), open("file2")' -b 'count=0' -l "if 'cow' in L: count += 1" -e 'P(count)'

Nothing magic, they’re just Python variables.

Sustainable scripting #

If you find yourself writing a longer than readable snippet, you can transform it in regular Python code than can easily be refactored for later reuse.

If your 1 liner becomes 500 character long, turn it into a Python module and keep the code flowing!

py1 -b 'count=0' -l "if 'cow' in L: count += 1" -e 'P(count)' --dump-code

Gives you:

## Program conveniency variables.
ENV=os.environ   # ENV: dict of Environment strings
# Input
F = sys.stdin    # F: input File, defaults to stdin
WS = None        # WS: Word Separator, defaults to whitespace
WRE = None       # WS: Word RegExp separator, overrides WS if set
# Output
OF = sys.stdout  # OF: Output File, defaults to stdin
OWS = None       # OWS: Output Word Separator, defaults to whitespace
OLS = None       # OLS: Output Line Separator, defaults to \n

def P(*args, **kwargs):
  """Like print() but honors OWS, OLS & OF."""
  kwargs.setdefault("sep", OWS)
  kwargs.setdefault("end", OLS)
  kwargs.setdefault("file", OF)
  print(*args, **kwargs)

def M(pattern, string=None, flags=0):
  """Returns all capture groups starting with the full match."""
  string = R if string is None else string
  matched = re.search(pattern, string, flags)
  if matched is None:
    return None
  return (matched.group(0), ) + matched.groups()

def S(pattern, repl, string=None, count=0, flags=0):
  """Substitute pattern with repl in string (or R if string is None)."""
  string = R if string is None else string
  return re.sub(pattern, repl, string, count, flags)
## --begin
count =0

## Main loop
# LN: Line Number; R: Raw Line
for LN, R in enumerate(F):
  ## Line conveniency variables.
  L = R.strip()           # L: Line without whitespace arround.
  if WRE is None:
    W = L.split(WS)       # W: Words split on WS
  else:
    W = re.split(WRE, L)  # W: Words split on WRE
  NW = len(W)             # Number of words

  ## --each-line
  if 'cow'in L :count +=1

## --end
print count

I want it! #

pip install py1

The documentation is here, the code on github.