des*_*ert 36 shell bash zsh shell-script variable
在bash脚本中,我需要来自/proc/文件的各种值。到现在为止,我有几十行像这样直接 grep 文件:
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
Run Code Online (Sandbox Code Playgroud)
为了提高效率,我将文件内容保存在一个变量中并进行了搜索:
a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'
Run Code Online (Sandbox Code Playgroud)
而不是多次打开文件,这应该只打开一次并grep变量内容,我认为这会更快 - 但实际上它更慢:
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
Run Code Online (Sandbox Code Playgroud)
dash和也是如此zsh。我怀疑/proc/文件的特殊状态是一个原因,但是当我将 的内容复制/proc/meminfo到常规文件并使用它时,结果是相同的:
a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'
Run Code Online (Sandbox Code Playgroud)
使用 here 字符串来保存管道使其稍微快一点,但仍然不如文件快:
bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real 0m0.803s
user 0m0.619s
sys 0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real 0m1.182s
user 0m1.425s
sys 0m0.506s
Run Code Online (Sandbox Code Playgroud)
为什么打开文件比从变量读取相同内容更快?
Sté*_*las 48
在这里,它不是打开一个文件与读取变量的内容,但更多的分叉额外的进程中。
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfofork 一个执行grep打开的进程/proc/meminfo(一个虚拟文件,在内存中,不涉及磁盘 I/O)读取它并匹配正则表达式。
其中最昂贵的部分是分叉进程并加载 grep 实用程序及其库依赖项、进行动态链接、打开语言环境数据库、磁盘上的数十个文件(但可能缓存在内存中)。
/proc/meminfo相比之下,关于阅读的部分是微不足道的,内核在那里生成信息grep所需的时间很少,阅读它的时间也很少。
如果您运行strace -c它,您会看到用于读取的一个open()和一个read()系统调用/proc/meminfo与grep启动时strace -c所做的一切相比是花生(不计算分叉)。
在:
a=$(</proc/meminfo)
Run Code Online (Sandbox Code Playgroud)
在大多数支持该$(<...)ksh 运算符的 shell 中,shell 只是打开文件并读取其内容(并去除尾随的换行符)。bash是不同的并且效率低得多,因为它分叉了一个进程来进行读取并通过管道将数据传递给父进程。但在这里,它已经完成了一次,所以没关系。
在:
printf '%s\n' "$a" | grep '^MemFree'
Run Code Online (Sandbox Code Playgroud)
shell 需要产生两个进程,这两个进程同时运行,但通过管道相互交互。管道的创建、拆除、写入和读取成本很低。更大的成本是产生一个额外的进程。进程的调度也有一些影响。
您可能会发现使用 zsh<<<运算符可以稍微快一点:
grep '^MemFree' <<< "$a"
Run Code Online (Sandbox Code Playgroud)
在 zsh 和 bash 中,这是通过将 的内容写入$a临时文件来完成的,这比产生额外的进程要便宜,但与直接获取数据相比,可能不会给您带来任何好处/proc/meminfo。这仍然比您/proc/meminfo在磁盘上复制的方法效率低,因为临时文件的写入是在每次迭代时完成的。
dash不支持here-strings,但它的heredocs 是用不涉及产生额外进程的管道实现的。在:
grep '^MemFree' << EOF
$a
EOF
Run Code Online (Sandbox Code Playgroud)
外壳创建一个管道,派生一个进程。子进程grep以其标准输入作为管道的读取端执行,父进程在管道的另一端写入内容。
但是管道处理和进程同步仍然可能比直接获取数据更昂贵/proc/meminfo。
内容/proc/meminfo较短,制作时间不长。如果您想节省一些 CPU 周期,您需要移除昂贵的部分:分叉进程和运行外部命令。
喜欢:
IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}
Run Code Online (Sandbox Code Playgroud)
避免bash虽然其模式匹配非常低效。使用zsh -o extendedglob,您可以将其缩短为:
memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}
Run Code Online (Sandbox Code Playgroud)
请注意,这^在许多 shell(至少带有扩展全局选项的 Bourne、fish、rc、es 和 zsh)中很特别,我建议引用它。另请注意,echo不能用于输出任意数据(因此我使用了printf上述内容)。
在您的第一种情况下,您只是使用 grep 实用程序并从 file 中查找某些内容/proc/meminfo,它/proc是一个虚拟文件系统,因此/proc/meminfofile 在内存中,并且它需要很少的时间来获取其内容。
但是在第二种情况下,您正在创建一个管道,然后使用此管道将第一个命令的输出传递给第二个命令,这很昂贵。
区别是因为/proc(因为它在内存中)和管道,看下面的例子:
time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real 0m0.914s
user 0m0.032s
sys 0m0.148s
cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null
real 0m0.938s
user 0m0.032s
sys 0m0.152s
time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real 0m1.016s
user 0m0.040s
sys 0m0.232s
Run Code Online (Sandbox Code Playgroud)