#!/bin/sh # # The purpose of this shell script is to do things that I find # difficult with AWK. Namely, it provides usage information when the # usual "-h" or "--help" is requested, and it starts the right version # of AWK depending on the operating system. # # Because of the design, we now have to have some way of pointing this # shell-script wrapper to actual AWK script that does the work related # to the "ps" command. If you put this shell script in the same # directory as the AWK script and if you start this shell script # either directly or indirectly through a symbolic link in your path, # this shell script is able to find the AWK script. # # You don't have to use this shell script if you don't want to; # instead, you could start it up with "awk -f pstree.awk". # # Paul Serice # paul@serice.net # usage () { if [ $# -ne 1 ] ; then echo "$progname: Internal Error: Invalid args for usage(): \"$@\"" >&2 exit 1 fi # I don't know why but it seems the "eval" command holds the # escaped '\' in the buffer when it evaluates the next character. # Because it to is a backslash, the next character also looks like # an escaped backslash. Thus, multiple '\\\\' will telescope down # to a single escaped backslash. We need two back-to-back escaped # backslashes. I'm going to get this by using two "eval" # commands. (The eval is necessary to correctly interpret "&1" # and "&2" as passed in via "$1" as a file descriptor instead of # as a file. I've resorted to this because not all systems # support "/dev/stdout" and "/dev/stderr" files.) # I would also like to add that getting the quotes just right was # a major pain. They are wrapped in single quotes in the "eval" # statement below. So, when you want to see a single quote, you # have to switch to using double quotes. This involves a single # quote in your string to close the one started from the "eval" # command. Then you need two double quotes and whatever goes in # them. Lastly, you need to restart the single quote from the # "eval" so that the trailing single quote from the same "eval" # has something to match up with. (There's got to be a better # way.) help_str="\ Usage: $progname \\n\ \\n\ Options:\\n\ -f or --file : File that holds the $progname AWK\\n\ script. The default value is that\\n\ it is in the same directory as the\\n\ shell-script wrapper with the same\\n\ name as the shell-script wrapper\\n\ except \".awk\" is appended. So,\\n\ if the wrapper is\\n\ \"/$progname\",\\n\ the AWK script is\\n\ \"/$progname.awk\".\\n\ \\n\ -h or --help : Help message will be displayed.\\n\ \\n\ -i or --interpreter : Interpreter to use for parsing the\\n\ AWK script. The default value for\\n\ your operating system should already\\n\ work.\\n\ \\n\ -v branch_prefix= : String that is used to illustrate\\n\ a branch in the process tree. The\\n\ default value is \"|__\". A common\\n\ override is '\\\\\\\\_ '. (Yes, the\\n\ single quotes are important, and\\n\ yes, awk needs you to escape the\\n\ backslash.)\\n\ \\n\ -v branch_prefix_secondary= : The secondary branch prefix is used\\n\ to denote continuation. By default,\\n\ its value is the first character of\\n\ the branch_prefix flag above.\\n\ Typically, you will want this value\\n\ to be \"|\". Remember, this is the\\n\ pipe symbol for the shell and needs\\n\ to be quoted or escaped.\\n\ \\n\ -v uname= : String used to customize the\\n\ behavior of this awk script based\\n\ on the operating system. It\\n\ defaults to the output of the\\n\ \"uname\" command. Values that are\\n\ currently known are bsd, cygwin,\\n\ darwin, irix, linux, and sunos.\\n\ \\n\ --version : Display the current version of\\n\ $progname.\\n\ " if [ "$1" -eq 1 ] ; then printf "$help_str" else printf "$help_str" 1>&2 fi } # Some machines don't have a "readlink" command. read_link_via_ls() { if [ $# -ne 1 ] ; then echo "$progname: Internal Error: Invalid args for" \ "readlink_via_ls: $@" >&2 exit 1 fi \ls -l "$1" | sed -e 's|.*-> ||' } # Some machines don't have a "realpath" command. follow_link_via_ls # does not pretend to be "realpath" because it will leave all sorts of # cruft in the path string, but it should eventuall reach a regular # file. follow_link_via_ls() { if [ $# -ne 1 ] ; then echo "$progname: Internal Error: Invalid args for" \ "follow_link_via_ls: $@" >&2 exit 1 fi local_iteration_count=0 local_iteration_max=1024 # Prime the pump. local_tmp= local_rv="$1" while [ $local_iteration_count -lt $local_iteration_max ] ; do if [ ! -h "$local_rv" ] ; then break fi local_tmp=`read_link_via_ls "$local_rv"` # The "read_link_via_ls" above could result in an absolute # path or a relative one. The Solaris /bin/sh does not # support "${PSTREE_AWK_SCRIPT#/}". So, use grep instead. echo "$local_tmp" | grep '^/' >/dev/null 2>&1 if [ $? -ne 0 ] ; then # The path given by readlink was relative. So, we have a # little more work to do because we need to prepend the # same path used to reach the original symlink. local_tmp=`dirname "$local_rv"`/"$local_tmp" fi local_rv="$local_tmp" local_iteration_count=`expr $local_iteration_count + 1` done if [ $local_iteration_count -ge $local_iteration_max ] ; then echo "$progname: Error: Symbolic link nesting is too deep when" \ "following \"$1\"." >&2 exit 1 fi echo "$local_rv" } # Default location for the pstree awk script is in the same directory # as this shell-script wrapper. The user can override with the "-f" # option. set_pstree_awk_script() { if [ $# -ne 1 ] ; then echo "$progname: Internal Error: Invalid args for" \ "set_pstree_awk_script: $@" >&2 exit 1 fi # Allow the user to override via the PSTREE_AWK_SCRIPT environment # variable. if [ -z "$PSTREE_AWK_SCRIPT" ] ; then # Read the link and portably construct the path to the # PSTREE_AWK_SCRIPT. This is done because some systems # have neither readlink nor realpath. PSTREE_AWK_SCRIPT=`follow_link_via_ls "$1"` # Strip off any trailing ".sh" and replace it with ".awk". # The "${PSTREE_AWK_SCRIPT%.sh}" notation is not # recognized by /bin/sh under Solaris. PSTREE_AWK_SCRIPT=`echo "$PSTREE_AWK_SCRIPT" \ | sed -e 's:\.sh$::' -e 's:$:.awk:'` fi } verify_exec () { if [ $# -ne 1 ] ; then echo "$progname: Internal Error: Invalid args for" \ "verify_exec(): \"$@\"" exit 1 fi type "$1" >/dev/null 2>&1 if [ $? -ne 0 ] ; then echo "$progname: Error: Unable to find executable for \"$1\"." >&2 exit 1 fi } # # Script starts here. # # Return value for "exit". rv= # Did an error occur while parsing the command line flags? command_line_parsing_error= # Did the user request the version be displayed? requested_version= # Set $progname. progname="pstree" verify_exec "basename" progname=`basename "$0"` verify_exec "dirname" verify_exec "expr" verify_exec "ls" verify_exec "printf" verify_exec "ps" verify_exec "sed" verify_exec "sort" verify_exec "uname" # Find default version of awk for the OS in question while still # allowing the user to override from the command line with the -i (for # "interpreter") option. : ${PSTREE_AWK:="awk"} if [ "`uname`" = "SunOS" ] ; then PSTREE_AWK="nawk" fi # # Parse the command-line flags. # last_option_examined= while [ $# -gt 0 ] ; do last_option_examined="$1" # Honor the "--" convention as breaking the list of arguments we # parse. if [ "$1" = "--" ] ; then shift break; fi # User is requesting a non-default location for the pstree AWK # script. if [ "$1" = "-f" ] || [ "$1" = "--file" ] ; then shift if [ "$1" ] ; then PSTREE_AWK_SCRIPT="$1" shift continue else command_line_parsing_error=t break fi fi # User is requesting help. if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then usage "1" rv=0 shift break fi # User wants to try a non-default AWK interpreter. if [ "$1" = "-i" ] || [ "$1" = "--interpreter" ] ; then shift if [ "$1" ] ; then PSTREE_AWK="$1" shift continue else command_line_parsing_error=t break fi fi # The short option "-v" gets in the way of awk's "-v =" # command-line options. So, just honor the long "--version". if [ "$1" = "--version" ] ; then requested_version=t shift break fi # Check for AWK options the user wants to pass to the AWK # interpreter. Remember what those options are. if [ "$1" = "-v" ] ; then shift if [ "$1" ] ; then # The extra space on the first iteration won't hurt # anything. awk_options="$awk_options -v '$1'" shift continue else command_line_parsing_error=t break fi fi command_line_parsing_error=t break; done if [ "$command_line_parsing_error" ] ; then echo >&2 echo "$progname: Error: Invalid command line argument:" \ "\"$last_option_examined\"." >&2 usage "2" rv=1 else # Verify the version of AWK the user wants to use. It is needed # for "read_link_via_ls" which is called from # "set_pstree_awk_script". verify_exec "$PSTREE_AWK" set_pstree_awk_script "$0" if [ -r "$PSTREE_AWK_SCRIPT" ] ; then if [ "$requested_version" ] ; then version=`$PSTREE_AWK '{ if( ($1 == "#") && ($2 == "Version:") ) { print $3; exit(0); } }' < "$PSTREE_AWK_SCRIPT"` echo "$progname: $version" rv=0 fi else echo >&2 echo "$progname: Error: Unable to read main AWK script" \ "\"$PSTREE_AWK_SCRIPT\"." >&2 usage "2" rv=1 fi if [ "$rv" ] ; then exit $rv fi # The Solaris /bin/sh does not support arrays. This means I had # to revert to using $awk_options as a list. In order to get # things to start correctly, and "eval" was required. # # Also, I was having trouble with Solaris again in getting it to # accept ${awk_options+awk_options}. The problem is that the # value of this variable always leads with a "-v", and the "-" was # causing a "bad substitution" error. The solution was to just # use and "if" statement here. if [ "$awk_options" ] ; then exec_cmd="\"$PSTREE_AWK\" -f \"$PSTREE_AWK_SCRIPT\"" exec_cmd="$exec_cmd -v progname=\"$progname\"" exec_cmd="$exec_cmd ${awk_options}" exec_cmd="$exec_cmd \${1+\"$@\"}" else exec_cmd="\"$PSTREE_AWK\" -f \"$PSTREE_AWK_SCRIPT\"" exec_cmd="$exec_cmd -v progname=\"$progname\"" exec_cmd="$exec_cmd \${1+\"$@\"}" fi # Finally, start the awk script. eval "$exec_cmd" fi