0

I want to read result of ps command and the proc number into two variables, but all the output assigned to the first variable.

my shell followed like this

#!/bin/bash

function status() {
    proc_num=`ps -ef | grep noah.*super | grep -v grep | tee /dev/stderr | wc -l`

    return $proc_num
}

IFS=$'#' read -r -d '' ret proc_num <<< `status 2>&1; echo "#$?"`

echo -e "proc_num: $proc_num\n"
echo -e "ret: $ret"

the result followed like this:

proc_num:

ret: root      7140 21935  0 Jul27 ?        00:00:00 /bin/sh -- /noah/modules/cecb4af2fce3393df49e748f86d7a176/supervise.minos-agent --run
root      8213  7140  0 Jul27 ?        00:00:00 /bin/sh -- /noah/modules/cecb4af2fce3393df49e748f86d7a176/supervise.minos-agent --run
root      8919 21935  0 Jul27 ?        00:00:00 /bin/sh -- /noah/modules/cecb4af2fce3393df49e748f86d7a176/supervise.minos-agent --run
root     18530     1  0 17:04 ?        00:00:00 /bin/sh -- /noah/modules/c0b527e8b1ce71007f8164d07195a8a2/supervise.logagent --run
root     21935     1  0 Jul10 ?        00:00:00 /bin/sh -- /noah/modules/cecb4af2fce3393df49e748f86d7a176/supervise.minos-agent --run
root     32278 32276  0  2019 ?        00:00:00 /bin/sh /noah/modules/f314c3a2b201042b9545e255364e9a9d/bin/supervise.noah-ccs-agent --run
root     34836     1  0 Sep18 ?        00:00:00 /bin/sh /noah/modules/488dddfee9441251c82ea773a97dfcd3/bin/supervise.noah-client --run
root     56155     1  0 Jun07 ?        00:00:00 /bin/sh /noah/modules/11e7054f8e14a30bd0512113664584b4/bin/supervise.server_inspector --run
 8

thanks for your help.

Cans404
  • 3
  • 1

1 Answers1

0

The immediate problem is that you're running into a bug in how earlier versions of bash treat unquoted here-strings (see this question). You can avoid it by double-quoting the here-string:

IFS=$'#' read -r -d '' ret proc_num <<< "`status 2>&1; echo "#$?"`"

...but please don't do that; this whole approach is overcomplicated and prone to problems.

  • Before I get to the more significant problems, I'll recommend using $( ) rather than backticks for command substitutions; they're easier to read, and avoid some parsing weirdnesses that backticks have.

  • Quote everything that might be misinterpreted. In grep noah.*super, the shell will try to turn noah.*super into a list of matching filenames. It's unlikely to find any matches, but if it somehow does the script will break in really weird ways. So use grep 'noah.*super' instead.

  • Do you have the pgrep command available? If so, use it instead of all of the ps | grep | grep stuff.

  • Exit/return statuses are for reporting status (i.e. success/failure, and maybe what failed), not returning data. Returning the number of processes found, as you're doing, will run into trouble if the number ever exceeds 255 (because the status is just a single byte, so that's the max it can hold). If there are ever 256 processes, the function will return 0. If there are 300, it'll return 44. etc. Return data as output, rather than abusing the return status like this.

  • Also, it's best to have functions produce output via stdout, rather than stderr as this one's doing. If you need to sneak a copy of the output past something like $( ), redirect it back to stdout afterward. And I'd tend to use something other than stderr anyway, to avoid mixing in any actual errors with the output stream. Here's an example using FD #3 (and BTW use local variables in functions when possible):

     { local proc_num=$(ps -ef | grep 'noah.*super' | grep -v grep | tee /dev/fd/3 | wc -l); } 3>&1
    

...or just capture the output, then do multiple things with it:

    local output="$(ps -ef | grep 'noah.*super' | grep -v grep)"
    echo "$output"
    local proc_num="$(echo "$output" | wc -l | tr -d ' ')"    # tr is to remove spaces from the output
  • status 2>&1; echo "#$?" is also trouble-prone; here you're taking that return status (which should've been output rather than a return status), and converting it to part of the output (which is what it should've been in the first place). And you're doing it so you can then re-split them back into separate bits of data with read. If you ever actually do need to capture both the output and return status from something, capture them separately:

     output="$(status 2>&1)"
     return_status=$?
    

    (BTW, the right side of a simple assignment like this is one of the very few places it's safe to omit double-quotes around a process or variable substitution. But using double-quotes doesn't hurt, and it's easier to just reflexively double-quote than remember the list of safe places, so I went ahead and double-quoted it here.)

  • Don't use the function keyword, it's nonstandard. Just use funcname() { definition... }.

  • I'd avoid using echo -e -- different versions of echo (including the bash builtin complied with different options) will treat -e differently. Some will treat it as meaning to interpret escape sequences in the output, but some will print it as part of the output(!). Either just avoid it:

     echo "proc_num: $proc_num"
     echo
     echo "ret: $ret"
    

    Or use printf and put the escape stuff in the format string:

     printf 'proc_num: %s\n\nret: %s\n' "$proc_num" "$ret"
    

    ...or...

     printf '%s\n' "proc_num: $proc_num" "" "ret: $ret"
    

So, how would I do this? My first preference would be to move the number-of-processes calculation outside of the status function entirely:

#!/bin/bash

status() {
    ps -ef | grep 'noah.*super' | grep -v grep
}

ret="$(status)"
proc_num="$(echo "$ret" | wc -l | tr -d ' ')"    # tr -d ' ' to remove spaces from the string

echo "proc_num: $proc_num"
echo
echo "ret: $ret"

If you do need to have the function compute that count, I'd have it also take care of adding that to its output (and probably use process substitution instead of a here-string):

...
status() {
    local output="$(ps -ef | grep 'noah.*super' | grep -v grep)"
    echo "$output"
    printf '#'
    echo "$output" | wc -l | tr -d ' '
}

IFS='#' read -r -d '' ret proc_num < <(status)
...

Final note: run your scripts through shellcheck.net -- it'spot many common problems (like incorrect quoting).

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151