查看bash脚本的输出

时间:2021-11-27 13:58:04

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.

我正在尝试使用watch来查看shell脚本的输出,执行/bin/bash,并将脚本本身保存在一个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.

booger只执行一次。它给出正确的输出,然后观察刷新,屏幕变为空白。退出时,没有错误列出。

I can't figure out where the issue is since it's getting difficult to debug with watch > bash > heredoc > ugly code.

我不知道问题出在哪里,因为用watch > bash > bbdoc >丑陋的代码调试变得越来越困难。

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"

2 个解决方案

#1


2  

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

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

watch bash -c func

In OP's example:

在OP的例子:

# 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"
}

#2


1  

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.

watch反复运行带有参数的指定命令。赫里多克,更一般地说,重定向操作符的影响不是命令的一部分。因此,watch不能重新生成“异端”。一旦赫里多克被第一次bash消耗掉,那么,第二次就什么都没有了。

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.

将文件保存在一个由mktemp创建的临时文件中。设置一个陷阱来捕获中断和其他信号,以确保临时文件被清理干净。观察运行bash“临时美元”。这很简单,也会起作用。

Dirty "solution" with (severe) caveats, don't do this!

You could put a script in a variable and then run with watch like this:

你可以在变量中放入一个脚本,然后像这样运行watch:

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.

但严重的警告是,如果var包含',第一个版本就会崩溃,如果var包含',第二个版本就会崩溃。这些只适用于最基本的脚本,当然也不适用于你的例子。

This is clearly not an option, I just added here for the sake of completeness.

这显然不是一个选项,我只是为了完整性而添加到这里。

#1


2  

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

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

watch bash -c func

In OP's example:

在OP的例子:

# 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"
}

#2


1  

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.

watch反复运行带有参数的指定命令。赫里多克,更一般地说,重定向操作符的影响不是命令的一部分。因此,watch不能重新生成“异端”。一旦赫里多克被第一次bash消耗掉,那么,第二次就什么都没有了。

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.

将文件保存在一个由mktemp创建的临时文件中。设置一个陷阱来捕获中断和其他信号,以确保临时文件被清理干净。观察运行bash“临时美元”。这很简单,也会起作用。

Dirty "solution" with (severe) caveats, don't do this!

You could put a script in a variable and then run with watch like this:

你可以在变量中放入一个脚本,然后像这样运行watch:

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.

但严重的警告是,如果var包含',第一个版本就会崩溃,如果var包含',第二个版本就会崩溃。这些只适用于最基本的脚本,当然也不适用于你的例子。

This is clearly not an option, I just added here for the sake of completeness.

这显然不是一个选项,我只是为了完整性而添加到这里。