bash:将结果赋值给变量时换行符数量不同

4

假设我想查看已经运行的程序副本数量。我可以像下面这样做:

ps ax | grep -c "$0"

这个命令本身会产生预期的结果。但是如果我尝试将输出分配给一个变量,它会增加1!无论我怎么尝试:

var=$(ps ax | grep "$0" | sed -n '$=')
var=`ps ax | grep -c "$0"`

请问有人能向我展示正确捕获输出的方法吗?

同时了解为什么会出现这种情况也是很好的。

更新 在得到@fedorqui的第一次回应后,我意识到我的表述不够清晰。让我详细说明一下:

我在同一个bash脚本中运行了上面的三个命令。当我运行第一个命令时,它会打印出数字2:程序本身和带有该程序作为参数的grep进程。当我在变量赋值内运行相同的命令时,数字3被存储。

请注意,我使用了两种不同的行计数方法,即grep和sed。在两种情况下,它们都返回了错误答案3,而不是正确答案2。

以下是一个合并的示例,可以在test.sh文件中尝试:

echo -n "without assignment: "
ps ax | grep -c "$0"
var=$(ps ax | grep "$0" | sed -n '$=')
echo "using sed method: $var"
var=`ps ax | grep -c "$0"`
echo "using grep method: $var"

我的Debian系统的结果:

without assignment: 2
using sed method: 3
using grep method: 3

问题再次提出:为什么会发生这种情况,如何预防或解决?

对我来说,使用 ./script.sh 运行脚本可以解决问题。如果我使用 bash script.sh 运行,会多出一个进程。 - fedorqui
另外,我想知道:你为什么想知道你的脚本有多少个实例正在运行?如果你想避免并发,还有其他更安全的方法。例如,在启动时创建一个虚拟目录,这样另一个实例看到它就会确定该脚本正在运行。完成后,删除该目录即可。 - fedorqui
@fedorqui 基本上是锁文件的概念,是吧?也许我应该走这个路线。我很想看一些关于锁文件与进程监控的教材... 不是为了这个项目而是一般性的。 - Eric Jensen
这里有一个关于在shell脚本中正确加锁的讨论,非常有趣。我原本认为创建目录是一种原子操作,是最好的方法,但在那里我看到了许多其他(也许更好的)解决方案。 - fedorqui
@fedorqui 看起来对我来说是一个完美的重复。var=$(ps | grep -e "$0") 运行了一个子shell,它也被返回。 - tripleee
显示剩余3条评论
3个回答

3

引用Siegex的话:

因为grep进程本身也会被ps返回。

你可以采取以下方法之一:

通过将搜索字符中的一个字符放入不改变功能的字符类[ ]来“欺骗”grep,以使其不匹配自身:

或者,在这种情况下,

使用管道符号将输出传递给grep -v grep,这样该进程就不会匹配:

var=$(ps ax | grep -v grep | grep "$0")

看一个例子。这里我们有一个进程sleep

$ sleep 20 &
[1] 5602

如果我们在ps的输出中检查它,它会出现两次!

$ ps -ef| grep sleep
me   5602  5433  0 09:49 pts/2    00:00:00 sleep 20
me   5607  5433  0 09:49 pts/2    00:00:00 grep --colour=auto sleep

我们可以使用字符类:

$ ps -ef| grep [s]leep
me   5602  5433  0 09:49 pts/2    00:00:00 sleep 20

或者通过grep过滤出grep进程:

$ ps -ef| grep sleep | grep -v grep
me   5602  5433  0 09:49 pts/2    00:00:00 sleep 20

1
如果$0是grep,会怎样呢?;-) - blackSmith
@blackSmith 很好的观点!尽管我认为这种情况不会经常发生,因为 grep 只会运行一会儿,而不是我们通常用 ps 检查的长时间运行的进程。 - fedorqui
@EricJensen 这很奇怪。我用 sleep 200 & 和你的代码检查了一下 sleep 而不是 $0,它总是返回2。能否提供更多细节? - fedorqui
@fedorqui,我错过了打开子shell的任务。另一个答案先说了这个,所以我接受了它,但我也给了你点赞。 - Eric Jensen
@blackSmith 很好的观点,我可以这样做,或者只需将结果与“3”进行比较,这是该程序在那一时刻进程表中一个实例的正常出现次数。我主要想了解为什么会发生这种情况。 - Eric Jensen
显示剩余8条评论

1
  • 命令替换本身在子shell中运行,因此这是一个bash进程

  • 你对bash ($0)的搜索即grep -c bash也会在那个时候出现在进程表中,所以这是另一个包含字符串bash的进程(grep)。请注意,这可能不会在运行时显示在进程表中,这取决于您的系统有多忙。

  • 而您实际上有两个(或更多)实际的bash进程(会话),据推测是其余部分

您可以使用正则表达式技巧来消除假阳性,即从计数中去除grep

ps ax | grep -c "[b]ash"

它仍将在执行命令替换时计算子shell:
var=$(ps ax | grep -c "[b]ash")

所以您需要手动从这个计数中减去一个。
示例:
$ var=$(ps ax | grep -c "bash")    
$ echo $var
4

$ var=$(ps ax | grep -c "[b]ash")   
$ echo $var
3

你是说使用var=''或var=$()会运行另一个bash shell吗?所以如果这些命令在脚本内运行,那么就会有两个同时运行的脚本?这可能是答案,但我仍然需要一种解决方案来搜索脚本本身正在运行的实例。有比搜索$0更好的想法吗?每次程序运行时都需要确保之前的实例没有仍在运行。 - Eric Jensen
@EricJensen 在 $(foobar) 中,命令 foobar 会在子 shell 中运行。您可以通过 $$ 查找当前进程的 PID,例如您的脚本。由于您的脚本将作为 bash 的参数运行,因此您应该搜索脚本名称并与 PPID 和 $$ 结合使用以获取所需内容。 - heemayl

0

你的命令也会算上grep命令行。

ps ax | grep -v grep | grep -c "$0"

应该从计数中省略grep。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接