Processes

Let’s talk about processes from a shell point of view.

../_images/process_stdin_stdout_stderr_return-code.png

A process takes standard input (STDIN) and returns

  • standard output (STDOUT) - it gets printed on your console
  • standard error (STDERR) - you see that too if you don’t do process 2> /dev/null, but more about that later
  • return code - 0 on success, a different number otherwise

Of course there are processes that

  • do not read STDIN (but instead read files, get data from the kernel, ...)
  • do not write to STDOUT (because they don’t have anything to say)
  • do not write to STDERR (because there are no errors)

But every process returns a return code.

There are environment variables too.

In the next sections I’ll show you how to use that knowledge with Bash and Python.

Bash

I could not do better than the Advanced Bash Scripting Guide, so I will simply present a basic example. Just paste it line by line into your running shell.

#!/bin/bash

# environment variables
# =====================

var="hello world"


# STDIN, STDOUT
# =============

# echo takes a string and puts it on STDOUT
echo $var

# we can redirect STDIN to a file
echo $var 1> /tmp/some_file

# cat takes a file as first argument and writes it to STDOUT
cat /tmp/some_file

# to be brutally honest cat concatenates files and writes them to STDOUT
echo "part 2" > /tmp/some_other_file
cat /tmp/some_file /tmp/some_other_file

# cat can also take STDIN and write it to STDOUT
echo $var | cat

# to concatenate STDIN with a file:
echo $var | cat - /tmp/some_other_file


# STDERR
# ======

# let's redirect STDIN to STDERR
echo $var 1>&2

# we can read it, but if we redirect STDOUT to a file it remains empty
write_stuff_to_STDERR() {  # this is a function -- see ``man bash``
    echo "stuff" 1>&2
}
write_stuff_to_STDERR 1> /tmp/where_is_my_message
cat /tmp/where_is_my_message

# so to get to the message we redirect STDERR to a file
write_stuff_to_STDERR 2> /tmp/there_it_is
cat /tmp/there_it_is

# we can also redirect everything to a file
# that's basically everything you can read on your console
write_stuff() {
    echo "INFO" 2>&1  # to STDOUT
    echo "ERROR" 1>&2  # to STDERR
}
write_stuff > /tmp/everything
write_stuff 1> /tmp/just_stdout
write_stuff 2> /tmp/just_stderr
cat /tmp/everything
cat /tmp/just_stdout
cat /tmp/just_stderr

# if the output of a process is irrelevant
# we redirect to /dev/null (the bottomless bucket)
write_stuff 1> /dev/null  # don't care about your info
write_stuff 2> /dev/null  # don't care about your errors


# Return Codes
# ============

# on success processes return a 0
echo ''  # this will definitely succeed
echo $?

# else they return something > 0
# good tools document this in their man pages
this_command_will_probably_not_be_found 2> /dev/null
echo $?

# return codes are useful for chaining processes
this_command_will_probably_not_be_found 2> /dev/null && echo 'success'
echo 'I work!' && echo 'success'

Python

We know how to write stuff to STDIN and STDERR, how to set a return code and how to read STDOUT, STDERR and return codes. Let’s do that in Python!

Here is a process that can do all that:

#!/usr/bin/python
"""
Example about basic process manipulation
"""
import os
import sys

def read_stdin():
    res = 'Your STDIN contains: '
    for line in sys.stdin:
        res = res + line
    print res

def write_to_stdout():
    print "Writing to STDOUT is easy."
    sys.stdout.write('One way or another...\n')

def write_to_stderr():
    sys.stderr.write('Writing to STDERR is easy too.\n')

def return_code():
    sys.exit(8)

if __name__ == '__main__':
    read_stdin()
    write_to_stdout()
    write_to_stderr()
    return_code()

Use it like this:

echo 'spam' | ./processes.py
echo $?

But how do we communicate with other processes, i.e. be the one controlling others? Here’s an example:

#!/usr/bin/python
"""
Example about process communication
"""
import os
import sys

from subprocess import call, Popen, PIPE

def get_return_code():
    cmd = ['echo', '""']
    retcode = call(cmd)
    print
    print 'get_return_code'
    print '==============='
    print retcode

def get_stdout():
    cmd = ['ls', '.']
    p = Popen(cmd, stdout=PIPE)
    res = p.communicate()[0]
    print
    print 'get_stdout'
    print '=========='
    print res

def run_in_shell():
    cmd = "echo 'Shell makes life easy, but is not too powerful.' | cat -"
    p = Popen(cmd, shell=True, stdout=PIPE)
    res = p.communicate()[0]
    print
    print 'run_in_shell'
    print '============'
    print res

def get_stderr():
    cmd = ['cat', 'some_filename_that_cannot_be_found']
    p = Popen(cmd, stderr=PIPE)
    res = p.communicate()[1]
    print
    print 'get_stderr'
    print '=========='
    print res

def get_everything():
    cmd = "echo 'first part works' | cat - but_this_file_cannot_be_found"
    p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
    out, err = p.communicate()
    retcode = p.returncode
    print
    print 'get_stdout_and_stderr'
    print '============'
    print 'STDOUT: %s'%out
    print 'STDERR: %s'%err
    print 'returncode: %s'%retcode

if __name__ == '__main__':
    get_return_code()
    get_stdout()
    run_in_shell()
    get_stderr()
    get_everything()

You can find more information in the official Python Documentation.