Shell Script (!) For Cyclically Editing/Running Python Modules
Tim Cargile
tecargile at hotmail.com
Thu Jan 9 03:01:07 EST 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