为什么它不工作,而覆盖IFS和读取多数据信息变量?



我想读取ps命令的结果和proc号到两个变量中,但是所有的输出都分配给第一个变量。

my shell如下所示

#!/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_numn"
echo -e "ret: $ret"

结果如下:

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

谢谢你的帮助。

直接的问题是,您遇到了早期版本的bash处理未加引号的字符串的错误(参见这个问题)。你可以通过双引号来避免它:

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

…但请不要这样做;整个方法过于复杂,容易出问题。

  • 在讨论更重要的问题之前,我建议使用$( )而不是反引号进行命令替换;它们更容易阅读,并避免了反引号所带来的一些解析问题。

  • 引用所有可能被误解的内容。在grep noah.*super中,shell将尝试将noah.*super转换为匹配文件名的列表。它不太可能找到任何匹配,但如果它找到了,脚本将以非常奇怪的方式中断。所以用grep 'noah.*super'代替。

  • 是否有pgrep命令可用?如果是这样,用它代替所有的ps | grep | grep的东西。

  • 退出/返回状态用于报告状态(即成功/失败,可能还有失败的内容),不返回数据。如果找到的进程数超过255(因为状态只有一个字节,所以这是它所能容纳的最大值),那么返回找到的进程数就会遇到麻烦。如果有256个进程,该函数将返回0。如果有300个,它将返回44。等。返回数据作为输出,而不是像这样滥用返回状态。

  • 另外,最好让函数通过标准输出(stdout)产生输出,而不是像这个这样用标准输出(stderr)。如果您需要将输出的副本通过$( )之类的内容,那么之后将其重定向回stdout。无论如何,我倾向于使用stderr以外的东西,以避免在输出流中混入任何实际错误。下面是一个使用FD #3的例子(顺便说一句,如果可能的话,在函数中使用local变量):

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

…或者只捕获输出,然后对其执行多个操作:

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 "#$?"也容易出现故障;在这里,您接受返回状态(它应该是输出而不是返回状态),并将其转换为输出的一部分(它本来应该是这样的)。你这样做是为了让你可以用read将它们重新分割成单独的数据位。如果您确实需要捕获某些内容的输出和返回状态,请分别捕获它们:

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

    (顺便说一句,像这样的简单赋值的右侧是少数几个可以在过程或变量替换周围省略双引号的地方之一。但是使用双引号并没有什么害处,而且条件反射性地双引号比记住安全位置列表更容易,所以我在这里继续使用双引号。

  • 不要使用function关键字,这是不标准的。请使用funcname() { definition... }

  • 我会避免使用echo -e——不同版本的echo(包括bash内置的不同选项)将以不同的方式对待-e。有些人会将其视为解释输出中的转义序列的含义,但有些人会将其作为输出的一部分打印出来(!)。要么就避免使用:

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

    或者使用printf并将转义内容放在格式字符串中:

    printf 'proc_num: %snnret: %sn' "$proc_num" "$ret"
    

    …或…

    printf '%sn' "proc_num: $proc_num" "" "ret: $ret"
    

我该怎么做呢?我的首选是将进程数计算完全移出status函数:

#!/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"

如果您确实需要让函数计算该计数,我也会让它负责将其添加到其输出中(并且可能使用进程替换而不是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)
...

最后注意:通过shellcheck.net运行你的脚本——它会发现许多常见的问题(比如不正确的引用)。

最新更新