Fed*_*eli 6 linux bash process-substitution here-string
如果要将系统命令的单行输出读入 Bash shell 变量,则至少有两个选项,如下例所示:
IFS=: read user x1 uid gid x2 home shell <<<$(grep :root: /etc/passwd | head -n1)和
IFS=: read user x1 uid gid x2 home shell < <(grep :root: /etc/passwd | head -n1)这两者有什么区别吗?什么更有效或推荐?
请注意,阅读/etc/passwd文件只是为了举例。我的问题的重点是这里的字符串与过程替换。
Sté*_*las 16
首先请注意,使用readwithout-r是处理输入 where\用于转义字段或行分隔符,而/etc/passwd. 您想在read没有-r.
现在对于这两种形式,请注意它们都不是标准sh语法。<<<来自zsh1991 年。<(...)来自ksh大约 1985 年,尽管ksh最初不支持重定向自/到它。
$(...)也来自 ksh,但已被 POSIX 标准化(因为它取代`...`了 Bourne shell 中设计不良的),因此现在可以跨sh实现移植。
$(code)解释子shell中的代码,输出重定向到管道,同时父进程从管道的另一端读取该输出并将其存储在内存中。然后,一旦该命令完成,该输出,去除尾随换行符(并在 中删除 NUL 字符bash)构成$(...).
如果它$(...)没有被引用并且在列表上下文中,则它受 split+glob 的约束(仅在 zsh 中拆分)。After <<<,它不是一个列表上下文,但仍然是旧版本的bash仍然会做拆分部分(不是 glob),然后用空格连接这些部分。因此,如果使用bash,您可能还想$(...)在用作<<<.
cmd <<< word在 zsh 和旧版本的 bash 中,shell 将word后跟换行符存储到临时文件中,然后将其作为将执行的进程的标准输入,并执行cmd之前删除的临时文件cmd。这与<< EOF70 年代的 Bourne shell发生的情况相同。实际上,它与以下内容完全相同:
cmd << EOF
word
EOF
Run Code Online (Sandbox Code Playgroud)
在 5.1 中,bash 从使用临时文件切换到使用管道,只要该词可以整个放入管道缓冲区(如果不是为了避免死锁,则回退到使用临时文件)并使cmd的 stdin 成为读取结束外壳预先用word.
因此cmd1 <<< "$(cmd2)"涉及一个或两个管道,将 的整个输出cmd2存储在内存中,将其再次存储在另一个管道或临时文件中并破坏 NUL 和换行符。
cmd1 < <(cmd2)功能等同于cmd2 | cmd1. cmd2的输出连接到管道的写入端。然后<(...)扩展到标识另一端的路径,为< that-path您提供到另一端的文件描述符。因此cmd2,cmd1无需外壳对数据执行任何操作即可直接对话。
您在bashshell 中看到这种构造,特别是因为 in bash,与 AT&T ksh 或 zsh 相反,在:
cmd2 | cmd1
Run Code Online (Sandbox Code Playgroud)
cmd1在子shell¹中运行,因此如果cmd1是read,read则只会填充该子shell的变量。
所以在这里,你会想要:
IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored < <(
grep :root: /etc/passwd)
Run Code Online (Sandbox Code Playgroud)
与 一样head是多余的-r,read无论如何只能读取一行²。我添加了一个rest_if_any_ignored以备将来打样,以防将来将新字段添加到 中/etc/passwd,从而导致$shell包含/bin/sh:that-field其他内容。
便携式(在sh)中,您不能这样做:
grep :root: /etc/passwd |
IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored
Run Code Online (Sandbox Code Playgroud)
因为 POSIX 未指定是否read在子外壳中运行(如bash/ dash...)或不(如zsh/ ksh)。
但是,您可以这样做:
IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored << EOF
$(grep :root: /etc/passwd | head -n1)
EOF
Run Code Online (Sandbox Code Playgroud)
(这里恢复head以避免整个grep的输出存储在内存和临时文件/管道中)。
即使效率不高,这也是标准的(尽管正如@muru 所指出的那样,与在分叉进程中运行外部实用程序的成本相比,如此小的输入的差异可能可以忽略不计)。
性能,如果这很重要的话,可以通过使用 shell 的内置特性来完成grep工作。然而,特别是bash,你只能做的是非常小的输入作为壳不适合这样的任务,并要差很多,在比grep。
while
IFS=: read <&3 -r user x1 uid gid name home shell rest_if_any_ignored
do
if [ "$name" = root ]; then
do-something-with "$user" "$home"...
break
fi
done 3< /etc/passwd
Run Code Online (Sandbox Code Playgroud)
¹ 除非设置了lastpipe选项 inbash并且 shell 是非交互式的,就像在脚本中一样
² 另见GNU 实现的-m1or--max-count=1选项,grep它会告诉grep自己在第一次匹配后停止搜索。或便携式等价物:sed '/:root:/!d;q'
在herestrings 中,bash 读入命令替换 ( $(grep :root: /etc/passwd | head -n1))的整个输出以创建 herestring 的内容,然后您告诉它再次使用read.
另一方面,通过进程替换,bash 设置一个管道,然后您读入一次输出。
您正在运行 bash(和其他两个外部命令)以在一行中读取。在这一点上,效率早已被否定。
当我们这样做时,GNUgrep有一个-m选择:
-mNUM
--max-count= NUM第一后停止NUM选择线。
| 归档时间: |
|
| 查看次数: |
775 次 |
| 最近记录: |