I'm trying to use watch to look at the output of a shell script, executing /bin/bash and holding the script itself in a heredoc.
The booger is only executing once though. It gives the correct output, then watch refreshes and the screen goes blank. Upon exiting watch there are no errors listed.
I can't figure out where the issue is since it's getting difficult to debug with watch > bash > heredoc > ugly code.
The good news is that ugly code in the heredoc works fine.
function show_users { 
    [[ -z $1 ]] && watchtime=1 || watchtime=$1
    [[ -z $2 ]] && export userToShow="mydefaultuser" || export userToShow=$2
    echo "Setting up watch for user ${userToShow}"
    watch -n $watchtime --no-title /bin/bash <<-'EOF'
        #Show finger results of requested user
        finger ${userToShow}
        #show list of users su'd into requested user
        echo "************************************************************************************"
        echo "users logged in as ${userToShow}"
        #get the parent PIDS of any process belonging to requested user
        #into a list that can be read by grep
        parentPIDs=$(ps -ef | grep "su - ${userToShow}" | grep -v 'grep\|finger' | awk 'NR>1{printf " %s \\|",parentpid}{parentpid=$3}END{printf " %s\n", parentpid}')
        #get usersnames associated to those parent PIDS
        parentUsers=$(ps -ef | grep "${parentPIDs}" | grep -v "grep\|${userToShow}" | awk '{print $1}' | sort | uniq)
        #finger each of these users and get their full name
        while IFS= read -r line ; do
            printf "%s: " $line
            parentName=$(finger $line | awk -F":" 'NR==1{print $3}')
            echo $parentName
        done <<< "${parentUsers}"
        #show tree for all proceses being run by requested user up to root.
        echo "************************************************************************************"
        ps -ef --forest | egrep -e "sshd:|-ksh|$userToShow" | grep -v grep | awk 'root==1{print ""} NR>1{print line} {line=$0;root=($1=="root") ? 1 : 0}'
    EOF
}
called like:
show_users 2 "username"
You can put the code in a function. With export -f func you make the function definition available to subprocesses of the current script, so that you can then say
watch bash -c func
In OP's example:
# don't use bash-only (IMHO ugly) function syntax
get_user_info () {
    local userToShow=$1
    # No need for braces around variable names unless disambiguation is required
    finger "$userToShow"
    echo "lots of ugly asterisks"
    echo "users logged in as ${userToShow}"
    # avoid grep | grep | awk
    # field specifier can probasbly be made more strict
    # (match only $2 instead of $0)?
    parentPIDs=$(ps -ef |
        awk -v user="$userToShow" 'NR>1 && ($0 ~ "su - " user) {
                printf " %s \\|",parentpid}
            {parentpid=$3}
            END{printf " %s\n", parentpid}')
    # Would be better if parentPIDs was a proper regex
    # Assumes you are looking for the PPID in column 3
    parentUsers=$(ps -ef |
        awk -v pids="$parentPIDs" 'parentPIDs ~$3 {print $1}' |
        # prefer sort -u over sort | uniq
        sort -u)
    while IFS= read -r line ; do
        printf "%s: " "$line"
        # No need to capture output just to echo it
        finger "$line" | awk -F":" 'NR==1{print $3}'
    done <<< "${parentUsers}"
    echo "Another ugly lot of asterisks"
    # Again, the regex can probably be applied to just one field
    ps -ef --forest |
        awk -v re="sshd:|-ksh|$userToShow"  '$0 !~ re { next }
            root==1{print ""}
            NR>1{print line}
            {line=$0;root=($1=="root" || $3==1) ? 1 : 0}'
}
export -f get_user_info
show_users () {
     # Avoid complex [[ -z ... ]]; use defaults with ${var-"value if unset"}
     # Mark these as local to avoid polluting global namespace
     local watchtime={$1-1}
     local userToShow=${2-mydefaultuser}
     # no need to export these variables
     echo "$mycommand"
     echo "Setting up watch for user ${userToShow}"
     watch -n $watchtime --no-title bash -c get_user_info "$userToShow"
}
watch repeatedly runs a specified command with its arguments.
The heredoc, and more generally the effect of redirect operators are not part of the command.
So watch cannot re-generate the heredoc.
And once the heredoc is consumed by the first run of bash,
well, there will be nothing left for the second.
There's a dirty hack you could try at the bottom of this answer.
But my recommended solution is to save the content of the heredoc in a temporary file.
That's reasonably simple to do and robust.
Save the file in a temporary file, created by mktemp.
Setup a trap to catch the interrupt and perhaps other signals to make sure the temporary file gets cleaned up.
Run watch bash "$tmpfile".
This is simple and will work.
You could put a script in a variable and then run with watch like this:
watch "bash -c '$var'"
Or like this:
watch "bash -c \"$var\""
But the severe caveat is that the first version will break if var contains ', and the second version will break if var contains ".
So these would work with only the most basic kind of scripts,
and certainly not the one in your example.
This is clearly not an option, I just added here for the sake of completeness.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With