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).