观察 bash 脚本的输出

Watching the output of a bash script

我正在尝试使用 watch 查看 shell 脚本的输出,执行 /bin/bash 并将脚本本身保存在 heredoc 中。

鼻屎只执行一次。它给出了正确的输出,然后手表刷新并且屏幕变为空白。退出后 watch 没有列出任何错误。

我无法弄清楚问题出在哪里,因为使用 watch > bash > heredoc > ugly code.[=21= 进行调试变得越来越困难]

好消息是 heredoc 中的 ugly code 工作正常。

function show_users { 

    [[ -z  ]] && watchtime=1 || watchtime=
    [[ -z  ]] && export userToShow="mydefaultuser" || export userToShow=

    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=}END{printf " %s\n", parentpid}')

        #get usersnames associated to those parent PIDS
        parentUsers=$(ps -ef | grep "${parentPIDs}" | grep -v "grep\|${userToShow}" | awk '{print }' | 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 }')
            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=[=11=];root=(=="root") ? 1 : 0}'
    EOF
}

像这样称呼:

show_users 2 "username"

watch 重复 运行 指定命令及其参数。 heredoc 和更普遍的重定向运算符的效果是命令的 not 部分。 所以 watch 无法重新生成 heredoc。 一旦 heredoc 被 bash 的第一个 运行 消耗, 好吧,第二个就没什么了。

您可以在这个答案的底部尝试一个肮脏的技巧。 但我推荐的解决方案是将 heredoc 的内容保存在一个临时文件中。 这相当简单且稳健。

将文件保存在由 mktemp 创建的临时文件中。 设置一个 trap 来捕获中断和其他信号,以确保临时文件得到清理。 运行 watch bash "$tmpfile"。 这很简单并且会起作用。

脏 "solution" 带有(严重)警告,不要这样做!

您可以将脚本放在变量中,然后 运行 和 watch 像这样:

watch "bash -c '$var'"

或者像这样:

watch "bash -c \"$var\""

但严重的警告是,如果 var 包含 ',第一个版本将中断,如果 var 包含 ",第二个版本将中断。 所以这些只适用于最基本的脚本, 当然不是你例子中的那个。

这显然不是一个选项,为了完整起见,我只是在此处添加。

您可以将代码放在函数中。使用 export -f func 可以使函数定义可用于当前脚本的子进程,这样您就可以说

watch bash -c func

在 OP 的示例中:

# don't use bash-only (IMHO ugly) function syntax
get_user_info () {
    local userToShow=

    # 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  instead of [=11=])?
    parentPIDs=$(ps -ef |
        awk -v user="$userToShow" 'NR>1 && ([=11=] ~ "su - " user) {
                printf " %s \|",parentpid}
            {parentpid=}
            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 ~ {print }' |
        # 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 }'
    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"  '[=11=] !~ re { next }
            root==1{print ""}
            NR>1{print line}
            {line=[=11=];root=(=="root" || ==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}
     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"
}