Shell Script (!) For Cyclically Editing/Running Python Modules

Tim Cargile tecargile at hotmail.com
Thu Jan 9 09:01:07 CET 2003


I called it 'pyer' (PYthon Edit Run), but I'm sure you folks
can find some other four-letter name for it.  The name is not
hard-coded anywhere in the script   :-)

If nothing else it would save a lot of typing when working
at the command line ... or at least provide an example of structured
shell. I would have written it in Python, but couldn't cost-justify
that at my Python skill level.

The first line might have to be edited to specify the
valid path for whichever is your favorite shell.  Tested with Cygwin
bash (which I've linked to ksh), but should run under ksh and
might even run under sh! If emailing me, the keyword 'techalert'
must be in the subject somewhere or it will be filtered as spam.

For performance degradation a builtin 'man' page is the first function
so you can read it immediately.

#---------------------------- Cut Here ------------------------------
#!/usr/bin/ksh

#------------------------------------------------------------
cat_man_page() {
#------------------------------------------------------------

cat <<-EOF | $PAGER
  ------------------------------------------------------------------
  $PN(1)                                                   $PN(1)


  NAME
        $PN - Interactive command line Python language interface

  SYNOPSIS

        $PN [python_interpreter_options]
              [python_file_name] [python file arguments]

        $PN -h

        $PN -help

  DESCRIPTION

        $PN provides an interactive interface to editing and
        running Python language modules.  It is based upon
        an 'edit/run' cycle model that is commonly used during
        focused unit module development.  The desired editor can
        be configured with the PYTHONEDITOR environment variable.

        Following the 'edit' phase, the edited module is run with
        the 'python' interpreter with arguments being supplied
        to both the interpreter and to the module being run.
        The 'run' 'stdout' is directed to 'filename.py.out'.
        and the 'run' 'stderr' is directed to 'filename.py.err'.

        The desired Python interpreter can be configured
        with the PYTHONEDITOR environment variable.  Options
        passed to the Python interpreter can be permanently set
        by using the PYTHONOPTIONS environment variable or passed
        on the command line to $PN.  Options passed on the command
        line override those set by PYTHONOPTIONS.

        Following the run, the output files are 'paged' with the
        if they have content.  The specific pager is identifed
        by the PAGER environment variable or the default 'more'
        if PAGER is null.

        Following each run, the opportunity is given with a 'Continue'
        prompt to either:

           1) Perform another 'edit/run' cycle
           2) Change the current Python source file and file arguments
              to be passed
           3) Run a single shell command
           4) Escape to a subshell

        The current directory is the default for finding current
        working Python files.  If the file cannot be found in the CWD,
        then $PN will search for the file using the PYTHONPATH
        environment variable if it is not null.

        If it is desired to create a temporary working file,
        the file name will be assigned automatically or by
        the PYTHONTEMPFILE environment variable.

        Upon exit, $PN removes empty *.out and *.err files.

  OPTIONS
        $PN accepts the '-h' and '-help' options to display
        a 'usage' and 'man' page respectively.  These options
        must not be unaccompanied by other options or arguments.

        All other options are assumed to be for the Python interpreter
        or are arguments for the Python modules to be edited
        and run.

        See SNYOPSIS and ENVIRONMENT VARIABLES sections.

  EXAMPLES

        $PN test1.py

             Will edit/run the 'test1.py' module and make it the
             'current' file.


        $PN -v test2

             Will edit/run the 'test2.py' module and make it the
             'current' file.  The '-v' argument will be passed
             to the Python interpreter.  A '.py' extension/suffix
             is not required.


        $PN test3.py one_word_argument "multi-word argument1"

             Will edit/run the 'test3.py' module and make it the
             'current' file. Note that 'multi-word arguments must
             be enclosed in quotes.


        $PN -help | -h

             Will display a manual page or short 'usage' output,
             respectively.

  NOTES
        It assumes that modules to be edited/run will have the '.py'
        suffix. File names entered without the '.py' will automatically
        have '.py' appended.

        All prompts to enter a file name accept arguments to be passed
        to the file at run time.  Multi-word arguments should be
        delimited by quotes to ensure correct argument assignment by
        the Python module.

        $PN, is written in Korn/Bash shell compatible and would probably
        run under the Bourne shell.  It was tested under the 'Cygwin'
        'bash' shell.


        It would be a challenge (to someone) to convert it to Python.


  ENVIRONMENT VARIABLES

        PAGER - Used to determine the page to be used for
                for viewing Python run output files.
                The default is 'more'.

        PYTHONEDITOR - Used to determine the editor to be used for
                       for editing Python files. The default is 'vi'.

        PYTHONEXEC -   Used to determine the Python Interpreter
                       to use for the 'run' phase.  The default
                       interpreter is 'python'.

        PYTHONOPTIONS - Used to set options passed to the Python
                        interpreter.

        PYTHONPATH - Searched for files to edit if not found in
                     the current directory.

        PYTHONTEMPFILE - Used to determine the name of a
                         temporary file when a specific
        Python source file is not passed on the command line.
        The default is a by the name '$PNnnnn' and are
        within the CWD.

  AUTHOR

        Tim Cargile (510) 914-4809 / 1/8/2002 / www.tecargile.com

  SEE ALSO

        Python(1), bash(1), ksh(1), vi()


  $PN(1)                                                   $PN(1)
  ------------------------------------------------------------------
EOF
}

#------------------------------------------------------------
main() {
#------------------------------------------------------------

    if process_args "$@"; then
        while [ true ]; do
            if [ -n "$do_edit_run" ];then
                if edit_py_file $curr_py_file;then
                    run_py_file $curr_py_file
                fi
            fi

            #-- Bottom of Edit/Run Loop Ops.
            if ! do_continue; then
                break
            fi
        done
    fi

    do_cleanup
}

#------------------------------------------------------------
find_py_file() {
#------------------------------------------------------------
    trace "find_py_file(): entry - arg1 = $1"

    find_py_file_bn=`basename $1`

    case $1 in
    ./*)
        file_str=$find_py_file_bn
        ;;
    *)
        file_str=$1
        ;;
    esac

    echo_stderr "Finding file: $file_str ..."

    if [ -r "$1" ];then
        echo $1
    else
        if [ -n "$PYTHONPATH" ];then
            trace "find_py_file(): searching = $PYTHONPATH"
            IFS_SAVE=$IFS
            IFS=:
            for dir in $PYTHONPATH;do
                if [ -r ${dir}/$find_py_file_bn ];then
                    found_py_file=${dir}/$find_py_file_bn
                    trace "find_py_file(): found."
                    echo $found_py_file
                    break
                else
                    trace "find_py_file(): NOT found."
                fi
            done
            IFS=$IFS_SAVE
        fi
    fi
}

#------------------------------------------------------------
edit_py_file() {
#------------------------------------------------------------
    if ! word_in_string $1 $edited_file_list; then
        if [ -n "$edited_file_list" ];then
            edited_file_list="$edited_file_list $1"
        else
            edited_file_list="$1"
        fi
    fi
    eval $py_editor $1 #-- Edit the Python File
    return $?
}

#------------------------------------------------------------
run_py_file() {
#------------------------------------------------------------
    if [ -n "$1" -a -r "$1"  ];then
        exec_str="$python_exec $python_opts $1 $py_file_args\
 >${1}.out 2> ${1}.err"
    
        echo "Running: $exec_str ..."

        eval $exec_str

        if [ -s ${1}.err ];then
            eval $PAGER ${1}.err
        fi

        if [ -s ${1}.out ];then
            eval $PAGER ${1}.out
        fi
    else
        if [ ! -n "$1" ];then
            echo_stderr "$PN: run_py_file(): null file name (internal error)"
        elif [ ! -r "$1" ];then
            echo "$PN: file: $1 not found - did not run."
        elif [ ! -s "$1" ];then
            echo "$PN: file: $1 is empty - did not run."
        fi
    fi
}

#------------------------------------------------------------
do_continue() {
#------------------------------------------------------------
    echo "---------------------------------------------------"
    echo "Current File: $curr_py_file"
    echo "Current Args: $py_file_args"
    echo "Edited Files: $edited_file_list"
    echo "Python Opts:  $python_opts"
    echo "---------------------------------------------------"

    echo_no_lf "Continue [Y|n] | ![cmd] | new_file [args] ?: "
    read continue

    if [ -z "$continue" ];then
        do_edit_run=t
        return 0
    fi

    unset do_edit_run

    case $continue in
    N|n)
        return 1
        ;;
    Y|y)
        do_edit_run=t
        ;;
    !)
        eval $shell_exec
        ;;
    !*)
        shell_cmd=`echo $continue | sed -e 's/^!//'`
        trace "do_continue(): shell_exec = $shell_exec shell_cmd = $shell_cmd"
        exec_str="$shell_exec -c \"$shell_cmd\""
        eval $exec_str
        #eval $shell_exec -c "$shell_cmd"
        ;;
    *)
        #---
        #--- New Current File Implied.  Parse filename and arguments
        #---
        new_py_file=`echo $continue | sed -e 's/^ .*//'|cut -d' ' -f1`
        new_py_file_args=`echo $continue | sed -e "s/^$new_py_file//"`

        trace "do_continue(): new_py_file = $new_py_file"
        trace "do_continue(): new_py_file_args = $new_py_file_args"

        if set_curr_py_file $new_py_file; then
            py_file_args=$new_py_file_args
            do_edit_run=t
        else
            echo "Could not find/create file: $new_py_file."
            break
        fi
    esac

    return 0
}

#------------------------------------------------------------
create_file() {
#------------------------------------------------------------
    file_arg=$1
    if [ ! -s $file_arg ];then
        echo_no_lf "File: $file_arg not found.  Create it? [Y|n]: "
        read create

        if [ -z "$create" -o "$create" = Y -o "$create" = y ];then
            touch $file_arg
            return 0
        else
            return 1
        fi
    fi
}

#------------------------------------------------------------
set_curr_py_file() {
#------------------------------------------------------------
    trace "set_curr_py_file(): entry"

    curr_py_file_save=$curr_py_file

    py_file_dn=`dirname $1`
    py_file_bn=`basename $1 .py`
    curr_py_file=${py_file_dn}/${py_file_bn}
    curr_py_file=`append_suffix $curr_py_file .py`

    found_py_file=`find_py_file $curr_py_file`

    if [ -n "$found_py_file" ];then
        curr_py_file=$found_py_file
    else
        trace "set_curr_py_file(): curr_py_file not found."

        case $1 in
        /*)
            trace "set_curr_py_file(): full path file name $1"
            if [ ! -d $py_file_dn ];then
                echo "Cannot create file: $1.  Directory does not exist."
                curr_py_file=$curr_py_file_save
                return 1
            fi
            ;;
        esac

        if create_file $curr_py_file; then
            return 0
        else
            curr_py_file=$curr_py_file_save
            return 1
        fi
    fi

    return 0
}

#------------------------------------------------------------
process_args() {
#------------------------------------------------------------
    trace "process_args(): entry"

    unset py_file_arg_in

    if [ $# -gt 0 ];then
        if [ $# -eq 1 -a $1 = "-h" ];then
            usage;exit 1
        fi

        if [ $# -eq 1 -a $1 = "-help" ];then
            cat_man_page;exit 1
        fi
    fi

    while [ $# -ge 1 ];do
        arg=$1
        trace "process_args(): arg = $arg"

        case $arg in
        -*)
            if [ -z "$py_file_arg_in" ]; then
                python_opts="$python_opts $arg"
            fi
            ;;  
        *)
            if [ -z "$py_file_arg_in" ]; then
                if ! set_curr_py_file $arg; then
                    echo_stderr "Invalid filename argument: $arg"
                fi
                py_file_arg_in=t
            else
                py_file_args="$py_file_args \"$arg\""
            fi

            ;;  
        esac

        shift
    done

    if [ -z "$curr_py_file" -a -n "$py_file_arg_in" ];then
        while [ -z "$curr_py_file" ];do
            msg_str="Enter File Name [args] (or Enter to exit): "
            echo_no_lf "$msg_str"
            read inp_file

            if [ -z "$inp_file" ];then
                do_cleanup; exit 1
            fi

            inp_py_file=`echo $inp_file | sed -e 's/^ .*//'|cut -d' ' -f1`
            inp_py_file_args=`echo $inp_file | sed -e "s/^$inp_py_file//"`
            trace "process_args(): inp_py_file_args = $inp_py_file_args"

            if set_curr_py_file $inp_py_file -a -n $curr_py_file ;then
                py_file_args=$inp_py_file_args
                trace "process_args(): inp_py_file_args = $inp_py_file_args"
                break
            fi
        done
    elif [ ! "$curr_py_file" ];then
        echo "Defaulting current file to temp file: $tmp_py_file."
        touch $tmp_py_file
        if ! set_curr_py_file $tmp_py_file;then
            echo_stderr "$PN: Cannot create tempory file: $tmp_py_file."
        fi
    fi

    if [ -z "$python_opts" ];then
        if [ -n "$PYTHONOPTIONS" ];then
            python_opts=$PYTHONOPTIONS
        fi
    fi

    trace "process_args(): return 0"

    do_edit_run=t
    return 0
}

#------------------------------------------------------------
word_in_string() {
#------------------------------------------------------------
    wis_word_arg=$1
    shift
    for word in $*;do
        if [ "$word" = "$wis_word_arg" ]; then
            return 0
        fi
    done

    return 1
}

#------------------------------------------------------------
do_cleanup() {
#------------------------------------------------------------
    #---
    #--- Delete all empty output files
    #---

    for file in $edited_file_list;do
        trace "do_cleanup(): processing: $file"

        if [ ! -s "${file}.out" ];then
            rm -f ${file}.out
        fi
        if [ ! -s "${file}.err" ];then
            rm -f ${file}.err
        fi
    done
}

#------------------------------------------------------------
echo_stderr() {
#------------------------------------------------------------
    echo "$*" >&2
}

#------------------------------------------------------------
echo_no_lf() {
#------------------------------------------------------------
    echo "$*" | tr -d '\012'
}

#------------------------------------------------------------
append_suffix() { # -- arg1 = word, arg2 = desired suffix
#------------------------------------------------------------
    if [ $# -lt 2 ];then
        echo_stderr "$PN: append_suffix(): invalid arg count: $#"
        exit 1
    fi
    case $1 in
        *$2) echo $1;;
        *) echo "${1}${2}"
    esac
}

#------------------------------------------------------------
trace() {
#------------------------------------------------------------
    if [ -n "$SH_TRACE" ];then
        echo_stderr "$PN TRACE: $*"
    fi
}

#------------------------------------------------------------
usage() {
#------------------------------------------------------------
    cat <<- EOF >&2
usage: $PN [python options] [python_file_name] [python file arguments]

       $PN -h

       $PN -help
EOF
}


#------------------------------------------------------------
#--- Init. Variables
#------------------------------------------------------------
unset edited_file_list python_opts py_file_args curr_py_file

PN=`basename $0`

if [ -z "$PAGER" ];then
    PAGER=more
fi

if [ -z "$PYTHONEXEC" ];then
    python_exec=python
else
    python_exec=$PYTHONEXEC
fi
if [ -z "$PYTHONeditor" ];then
    tmp_py_file=/tmp/${PN}$$
else
    tmp_py_file=$PYTHONTEMPFILE
fi

if [ -z "$PYTHONEDITOR" ];then
    py_editor=vi
else
    py_editor=$PYTHONEDITOR
fi

if [ -z "$PYTHONTEMPFILE" ];then
    tmp_py_file=${PN}$$
    #tmp_py_file=/tmp/${PN}$$
else
    tmp_py_file=$PYTHONTEMPFILE
fi

tmp_py_file=`append_suffix $tmp_py_file .py`

if [ -n "$SHELL" ];then
    shell_exec=$SHELL
else
    shell_exec=ksh
fi

trap "echo_stderr $PN: Trap! Exiting.; do_cleanup; exit 1" 1 2 3

main "$@"




More information about the Python-list mailing list