Last active
March 9, 2021 14:45
-
-
Save jlinoff/011c06cc0d1cf6014f0f939ed4b3c2b2 to your computer and use it in GitHub Desktop.
fix-python-headers - bash tool to fix python file header references
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Fix the first line of python a file to point to the | |
# new python interpreter. | |
# | |
# This mess required to make sure that script works on | |
# the Mac. Normally we would just use readlink. | |
set -e | |
RootDir=$(cd $(dirname $0) && pwd) | |
source $RootDir/libutils.sh | |
set +e | |
# ======================================================================== | |
# Functions | |
# ======================================================================== | |
function _help() { | |
cat <<EOF | |
USAGE | |
$BASENAME [OPTIONS] [FILES] | |
DESCRIPTION | |
Fix the shebang headers in python files. | |
This tool is useful for modifying python scripts | |
to use a different interpreter by default. | |
OPTIONS | |
-d, --dryrun Do a fry run. | |
Do not change any files. | |
-h, --help This help message. | |
-s HDR, --shebang-header HDR | |
The shebang header for the python file. | |
It must start with '#!'. | |
Here is an examle: | |
-s '#!/opt/cr/bin/python' | |
-v, --verbose Increase the verbosity. | |
Normally the program runs silently. | |
This option allows the user to see | |
what is happening. | |
-v Shows the phases of the operation. | |
-v -v Shows the files being modified. | |
-v -v -v Shows skipped non-python files. | |
-V, --version Print the program version and exit. | |
EXAMPLE | |
# Help | |
\$ $BASENAME -h | |
# Change all my programs to use python3.5. | |
\$ $BASENAME -s '#!/usr/local/bin/python3.5' myprogs/* | |
# Change all my programs to use python3.5 and | |
# show me what is happening. | |
\$ $BASENAME -v -s '#!/usr/local/bin/python3.5' myprogs/* | |
# Change all my programs to use python3.5 and | |
# really show me what is happening. | |
\$ $BASENAME -v -v -s '#!/usr/local/bin/python3.5' myprogs/* | |
# Change all my programs to the currently available python. | |
\$ $BASENAME -s '#!/usr/bin/env python' | |
EOF | |
exit 0 | |
} | |
# ======================================================================== | |
# Main | |
# ======================================================================== | |
BASENAME=$(basename $0) | |
VERSION="1.0.0" | |
HEADER="" | |
FILES=() | |
DRYRUN=0 | |
VERBOSE=0 | |
while (( $# )) ; do | |
OPT="$1" | |
shift | |
case "$OPT" in | |
-d|--dryrun) | |
DRYRUN=1 | |
;; | |
-h|--help) | |
_help | |
;; | |
-s|--shebang-header) | |
HEADER="$1" | |
shift | |
;; | |
-v|--verbose) | |
VERBOSE=$(( VERBOSE + 1 )) | |
;; | |
-V|--version) | |
echo "$BASENAME $VERSION" | |
exit 0 | |
;; | |
-*) | |
_err "Unrecognized option '$OPT'." | |
;; | |
*) | |
FILES+=($OPT) | |
;; | |
esac | |
done | |
# Tell'em who we are. | |
(( VERBOSE )) && _banner "Fixing python shebang headers." | |
# Make sure that the user specified a header with a | |
# shebang. The reason that we enforce the existence | |
# of the shebang is purely for consistency. The user | |
# is replacing the entire header, not just a single | |
# part of it. | |
[[ -z "$HEADER" ]] && _err "Shebang header not specified." | |
[[ ! "$HEADER" =~ "#!" ]] && _err "Shebang header missing shebang (#!)." | |
# Walk through all of the files and directories specified | |
# and fix them. | |
# Ignore files that are not python executables. | |
# Provide a verbose mode that allows the developer | |
# to see what is going on (-v -v). | |
for FILE in ${FILES[@]} ; do | |
PYFILES=() | |
if [[ -e $FILE ]] ; then | |
if [[ -d $FILE ]] ; then | |
# This is a directory, process all of the files in it. | |
#PYFILES=($(file $FILE/* | grep -i 'python script text' | awk -F: '{print $1}')) | |
# Cannot reliably use the file command, it fails under certain | |
# conditions. Awk works much better. It is also very fast. | |
PYFILES=($(awk 'FNR==1 && /#!.*python/ {print FILENAME ": PYTHON : " $0; }; FNR>1 {nextfile}' $FILE/* | awk -F: '{print $1}')) | |
elif [[ -r $FILE ]] ; then | |
PYFILES=($(awk 'FNR==1 && /#!.*python/ {print FILENAME ": PYTHON : " $0; }; FNR>1 {nextfile}' $FILE | awk -F: '{print $1}')) | |
if (( ${#PYFILES} == 0 )) ; then | |
(( VERBOSE > 2 )) && _warn "File is not a python file: $FILE." | |
fi | |
else | |
(( VERBOSE )) && _warn "File is not readable, skipping: $FILE." | |
fi | |
else | |
(( VERBOSE )) && _warn "File does not exist: $FILE." | |
fi | |
# Now process the python files and update if necessary. | |
for PYFILE in ${PYFILES[@]} ; do | |
if [[ ! -w $PYFILE ]] ; then | |
(( VERBOSE )) && _warn "File is not writable: $FILE" | |
continue | |
fi | |
CURR_HEADER=$(head -1 $PYFILE) | |
if echo "$CURR_HEADER" | grep -q '#[ ]*!' ; then | |
if [[ "$CURR_HEADER" == "$HEADER" ]] ; then | |
(( VERBOSE > 1 )) && _info "Skipping $PYFILE, already updated." | |
else | |
if (( DRYRUN == 0 )) ; then | |
# This is not a dryrun, change the first line of | |
# the file. | |
(( VERBOSE > 1 )) && _info "Updating $PYFILE." | |
# There some odd cases where surrounding quotes are | |
# added. This causes execution to fail. The quote | |
# stripping removes them. | |
sed -i -e "1c$HEADER" $PYFILE | |
sed -i -e '1s/"//g' -e "1s/'//g" $PYFILE | |
else | |
(( VERBOSE > 1 )) && _info "Updating $PYFILE, dryrun, no change." | |
fi | |
fi | |
else | |
(( VERBOSE > 1 )) && _warn "Skipping $PYFILE, no header." | |
fi | |
done | |
done | |
# Tell them we done if they are interested. | |
(( VERBOSE )) && _info "Done." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Utilities for setup scripts. | |
# | |
# ================================================================ | |
# Functions | |
# ================================================================ | |
function __msg() { | |
local LineNo=$1 | |
local Type=$2 | |
local Code="$3" | |
shift | |
shift | |
shift | |
printf "$Code" | |
printf "%-28s %-7s %5s: " "$(date +'%Y-%m-%d %H:%M:%S %z %Z')" "$Type" $LineNo | |
echo "$*" | |
printf "\033[0m" | |
} | |
# Print an info message to stdout. | |
function _info() { | |
__msg ${BASH_LINENO[0]} "INFO" "\033[0m" $* | |
} | |
# Print an info message to stdout in green. | |
function _info_green() { | |
__msg ${BASH_LINENO[0]} "INFO" "\033[32m" $* | |
} | |
# Print an info message to stdout in red. | |
function _info_red() { | |
__msg ${BASH_LINENO[0]} "INFO" "\033[31m" $* | |
} | |
# Print an info message to stdout in bold. | |
function _info_bold() { | |
__msg ${BASH_LINENO[0]} "INFO" "\033[1m" $* | |
} | |
# Print a warning message to stderr and exit. | |
function _warn() { | |
__msg ${BASH_LINENO[0]} "WARNING" "\033[34m" $* | |
} | |
# Print an error message to stderr and exit. | |
function _err() { | |
__msg ${BASH_LINENO[0]} "ERROR" "\033[31m" $* >&2 | |
exit 1 | |
} | |
# Print an error message to stderr. | |
# Do not exit. | |
function _err_nox() { | |
__msg ${BASH_LINENO[0]} "ERROR" "\033[31m" $* >&2 | |
} | |
# Decorate a command and exit if the return code is not zero. | |
function _exec() { | |
local Cmd="$*" | |
__msg ${BASH_LINENO[0]} "INFO" "\033[1m" "cmd.cmd=$Cmd" | |
eval "$Cmd" | |
local Status=$? | |
if (( $Status )) ; then | |
__msg ${BASH_LINENO[0]} "INFO" "\033[31m" "cmd.code=$Status" | |
__msg ${BASH_LINENO[0]} "ERROR" "\033[31m" "cmd.status=FAILED" | |
exit 1 | |
else | |
__msg ${BASH_LINENO[0]} "INFO" "\033[32m" "cmd.code=$Status" | |
__msg ${BASH_LINENO[0]} "INFO" "\033[32m" "cmd.status=PASSED" | |
fi | |
} | |
# Execute a command quietly. | |
# Only decorate if an error occurs. | |
function _exeq() { | |
local Cmd="$*" | |
eval "$Cmd" | |
local Status=$? | |
if (( $Status )) ; then | |
__msg ${BASH_LINENO[0]} "INFO" "\033[1m" "cmd.cmd=$Cmd" | |
__msg ${BASH_LINENO[0]} "INFO" "\033[31m" "cmd.code=$Status" | |
__msg ${BASH_LINENO[0]} "ERROR" "\033[31m" "cmd.status=FAILED" | |
exit 1 | |
fi | |
} | |
# Decorate a command. Do not exit if the return code is not zero. | |
function _exec_nox() { | |
local Cmd="$*" | |
__msg ${BASH_LINENO[0]} "INFO" "\033[1m" "cmd.cmd=$Cmd" | |
eval "$Cmd" | |
local Status=$? | |
if (( $Status )) ; then | |
__msg ${BASH_LINENO[0]} "INFO" "\033[31m" "cmd.code=$Status" | |
__msg ${BASH_LINENO[0]} "ERROR" "\033[31m" "cmd.status=FAILED (IGNORED)" | |
else | |
__msg ${BASH_LINENO[0]} "INFO" "\033[32m" "cmd.code=$Status" | |
__msg ${BASH_LINENO[0]} "INFO" "\033[32m" "cmd.status=PASSED" | |
fi | |
return $Status | |
} | |
# Banner. | |
function _banner() { | |
echo | |
echo "# ================================================================" | |
echo "# $*" | |
echo "# ================================================================" | |
} | |
# Get the version number. | |
function _get_version() { | |
local VERSION='1.0.0' | |
echo $VERSION | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment