如何强制 bash 取消分配没有名称的大括号扩展变量?

如何强制 bash 取消分配没有名称的大括号扩展变量?

为了标杆管理,我运行命令:

for i in {1..100000000}; do 
  echo "$i" line >> file
done

重击扩大大括号并将列表存储1 2 3 4 5 6 ... 100000000在内存中。

我认为这会在某个时刻以某种方式释放。毕竟它是一个临时变量。几天过去了,该bash进程仍然占用 17.9GB 内存。

我可以强制 bash 清除这些临时变量吗?我无法使用unset,因为我不知道变量名。 (显然unset i没有帮助)

当然,一种解决方案是关闭 shell 并打开新的 shell。

我还在 bash 邮件列表上询问了这个问题,并得到了有用的回复切特·拉姆齐:

这不是内存泄漏。 Malloc 实现不需要将内存释放回内核; bash malloc(和其他)仅在有限的情况下这样做。使用 mmap 或 sbrk 从内核获取并通过 malloc 实现保存在缓存中的内存不构成泄漏。泄漏是指应用程序或 ma​​lloc 本身不再有句柄的内存。

malloc() 基本上是应用程序和内核之间的缓存。它可以决定何时以及如何将内存返还给内核。

答案1

所以,我在测试中做了这件事,是的,它消耗了大量内存。我也特意使用了较小的数字。我可以想象bash连续几天占用这些资源可能会有点令人恼火。

ps -Fp "$$"; : {1..10000000}; ps -Fp "$$"

UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 32601  4241  0  3957  3756   4 08:28 pts/1    00:00:00 bash -l
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 32601  4241 59 472722 1878712 4 08:28 pts/1   00:00:28 bash -l

正如您所看到的,这对进程消耗的资源有很大的影响。好吧,我会尽力澄清这一点,但是 - 据我所知 - 它将需要用另一个外壳进程替换外壳进程。

首先,我将设置一个标记变量只是为了显示它与我一起。注意:这不是export编辑。

var='just
testing
'\''
this stuff
'\'''

接下来我将执行$0.这与长时间运行的守护进程必须偶尔执行以刷新其状态的操作相同。这是有道理的。

第一种方法:此处文档

我将使用当前 shell 为新编辑的 shell 进程构建一个 Heredoc 输入文件描述符exec,其中包含所有当前 shell 声明的变量。也许可以用不同的方式完成,但我不知道bash.

新的 shell 是用-login 开关调用的 - 它将确保您的profile/rc文件是按平常方式获取的 - 以及当前设置并存储在特殊 shell 参数中的任何其他 shell 选项$-。如果您觉得-login 不是正确的方法,那么使用-i开关至少应该让rc文件运行。

exec "${0#-}" "-l$-" 3<<ENV
$(set)
ENV

好的。这只花了一秒钟。效果如何?

. /dev/fd/3 2>/dev/null
echo "$var"; ps -Fp "$$"

just
testing
'
this stuff
'
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 32601  4241 12  4054  3800   5 08:28 pts/1    00:00:29 bash -lhimBH

很好,就像看起来那样。<&3挂在新的 shell 进程的输入上,直到它被读取 - 所以我这样做.并获取它。它可能会包含一些默认的只读变量,这些变量已经通过其rc文件等在新 shell 中设置,因此会出现一些错误 - 但我将其转储到2>/dev/null.完成此操作后,如您所见,我拥有所有旧 shell 进程的变量 - 包括我的标记$var

第二种方法:环境变量

在对此事进行了一两次谷歌之后,我认为这可能是另一种值得考虑的方式。我最初考虑过这个,但是显然是错误的基于这样的信念,即对单个环境变量的值存在内核强制的任意长度限制,因此不考虑此选项 - 类似于阿格伦或者莱恩麦克斯 (这可能会影响这一点)但对于单个值来说较小。不过,我的正确说法是execve当总环境太大时,调用将不起作用。因此,我认为只有在您可以保证当前环境足够小以允许调用的情况下,才应首选此方法exec

事实上,这已经足够不同了,我会一次性重做一遍。

ps -pF "$$"; : {1..10000000}; ps -pF "$$"

UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241  0  3957  3788   3 14:28 pts/1    00:00:00 bash -l
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241 38 472722 1878740 3 14:28 pts/1   00:00:11 bash -l

我在第一轮中未能做的一件事是迁移 shell 函数。不算你自己跟踪它们(这可能是最好的方法),据我所知,没有 shell 可移植的方法可以做到这一点。bash不过,确实允许这样做,因为函数的工作方式与可移植的 shell 变量的工作declare -f方式大致相同。set要使用第一种方法执行此操作,您只需在此处文档中; declare -f添加即可。set

我的标记变量将保持不变,但这是我的标记函数:

chk () {
    printf '###%s:###\n%s\n' \
        \$VAR "${var-NOT SET}" \
        PSINFO "$(ps -Fp $$)" \
        ENV\ LEN "$(env | wc -c)"
}

因此,我不会向新 shell 提供文件描述符,而是将其传递给两个环境变量:

varstate=$(set) fnstate=$(declare -f) exec "${0#-}" "-l$-"

好的。所以我刚刚更换了正在运行的 shell,那么现在怎么办?

chk
bash: chk: command not found

当然。但...

{   echo '###EVAL/UNSET $FNSTATE###'
    eval "$fnstate"; unset fnstate
    chk
    echo '###EVAL/UNSET $VARSTATE###'
    eval "$varstate"; unset varstate
    chk
}

输出

###EVAL/UNSET $FNSTATE###
###$VAR:###
NOT SET
###PSINFO:###
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241 10  3991  3736   1 14:28 pts/1    00:00:12 bash -lhimBH
###ENV LEN:###
6813
###EVAL/UNSET $VARSTATE###
bash: BASHOPTS: readonly variable
bash: BASH_VERSINFO: readonly variable
bash: EUID: readonly variable
bash: PPID: readonly variable
bash: SHELLOPTS: readonly variable
bash: UID: readonly variable
###$VAR:###
just
testing
'
this stuff
'
###PSINFO:###
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241 10  4056  3772   1 14:28 pts/1    00:00:12 bash -lhimBH
###ENV LEN:###
2839

答案2

不会。Bash 永远不会将其为任何目的分配的内存返回给操作系统。(但如果我错了,请纠正我。)

然而,如果有必要,bash 会重新使用内存用于其他目的,如果没有,内核会将其交换出来,因此它实际上不会在 RAM 中。

相关内容