Bash 具有命令替换语法 $(f)
,它允许捕获
命令 f
的 STDOUT。如果该命令是可执行文件,那就没问题
– 无论如何,创建新流程是必要的。但如果命令是
shell 函数,使用此语法会产生大约 25ms 的开销
我系统上的每个子shell。这足以导致明显的延迟
当在内循环中使用时,尤其是在交互式上下文中,例如
命令完成或$PS1
。
常见的优化是使用 global variables instead [1]对于返回值, 但这是以可读性为代价的:意图变得不太清晰,并且 shell 函数和之间的输出捕获突然不一致 可执行文件。我在下面添加了选项及其弱点的比较。
为了获得一致、可靠的语法,我想知道 bash 是否有 任何允许捕获 shell 函数和可执行输出的功能 同样,同时避免使用 shell 函数的子 shell。
理想情况下,解决方案还应包含在子 shell 中执行多个命令的更有效的替代方案,这允许更干净地隔离问题,例如
person=$(
db_handler=$(database_connect) # avoids leaking the variable
query $db_handler lastname # outside it's required
echo ", " # scope.
query $db_handler firstname
database_close $db_handler
)
这样的构造允许代码的读者忽略 $()
内部的所有内容,如果他们对 $person
的格式化细节不感兴趣。
选项比较
1。使用命令替换
person="$(get lastname), $(get firstname)"
缓慢,但可读且一致:一开始对读者来说并不重要
看看 get
是 shell 函数还是可执行文件。
2。所有函数都使用相同的全局变量
get lastname
person="$R, "
get firstname
person+="$R"
模糊了 $person
应该包含的内容。或者,
get lastname
local lastname="$R"
get firstname
local firstname="$R"
person="$lastname, $firstname"
但这非常冗长。
3。每个函数都有不同的全局变量
get_lastname
get_firstname
person="$lastname $firstname"
- 作业更具可读性,但是
- 如果某个函数被调用两次,我们将回到 (2)。
- 设置变量的副作用并不明显。
- 很容易意外地使用错误的变量。
4。使用全局变量,其名称作为参数传递
get LN lastname
get FN firstname
person="$LN, $FN"
- 更具可读性,轻松允许多个返回值。
- 与捕获可执行文件的输出仍然不一致。
注意:对动态变量名称的赋值应使用
declare
完成 而不是eval
:$VARNAME="$LOCALVALUE" # doesn't work. declare -g "$VARNAME=$LOCALVALUE" # will work. eval "$VARNAME='$LOCALVALUE'" # doesn't work for *arbitrary* values. eval "$VARNAME=$(printf %q "$LOCALVALUE")" # doesn't avoid a subshell afterall.
最佳答案
如果您希望 shell 函数高效,则不能通过 stdout 返回结果。如果他们这样做了,就没有办法得到它,只能通过在子 shell 中运行该函数并通过内部管道捕获输出,而这些操作有点昂贵(在现代系统上需要几毫秒)。
当我专注于 shell 脚本并且需要最大限度地提高其性能时,我使用了一种约定,其中函数 foo
将通过变量 foo
返回其结果。即使在 POSIX shell 中您也可以执行此操作,并且它具有一个很好的属性,即它不会覆盖您的本地变量,因为如果 foo
是一个函数,那么您已经保留了该名称。
然后我有这个 bx_r getter 函数,它运行 shell 函数并将其输出保存到名称由第一个参数指定的变量中,或者如果第一个参数是,则将输出输出到 stdout一个非法变量名的单词(如果该单词恰好是一个空单词,则没有换行符,即 '')。
我对其进行了修改,以便它可以与命令或函数统一使用。
您不能使用内置类型来区分两者,因为 type 通过 stdout 返回其结果 => 您需要捕获该结果,这将再次施加 fork 惩罚。
所以,当我要运行函数 foo
时,我要做的是检查是否有相应的变量 foo
(这可以捕获局部变量,但你会如果您将自己限制为正确命名的 shell 函数名称,则可以避免出现这种情况的可能性)。如果有,我假设这就是函数 foo
返回其结果的地方,否则我在 $() 中运行它,捕获其标准输出。
这是带有一些测试代码的代码:
bx_varlike_eh()
{
case $1 in
([!A-Za-z_0-9]*) false;;
(*) true;;
esac
}
bx_r() #{{{ Varname=$1; shift; Invoke $@ and save it to $Varname if a legal varname or print it
{
# `bx_r '' some_command` prints without a newline
# `bx_r - some_command` (or any non-variable-character-containing word instead of -)
# prints with a newline
local bx_r__varname="$1"; shift 1
local bx_r
if ! bx_varlike_eh "$1" || eval "[ \"\${$1+set}\" != set ]"; then
#https://unix.stackexchange.com/a/465715/23692
bx_r=$( "$@" ) || return #$1 not varlike or unset => must be a regular command, so capture
else
#if $1 is a variable name, assume $1 is a function that saves its output there
"$@" || return
eval "bx_r=\$$1" #put it in bx_r
fi
case "$bx_r__varname" in
('') printf '%s' "$bx_r";;
([!A-Za-z_0-9]*) printf '%s\n' "$bx_r";;
(*) eval "$bx_r__varname=\$bx_r";;
esac
} #}}}
#TEST
for sh in sh bash; do
time $sh -c '
. ./bx_r.sh
bx_getnext=; bx_getnext() { bx_getnext=$((bx_getnext+1)); }
bx_r - bx_getnext
bx_r - bx_getnext
i=0; while [ $i -lt 10000 ]; do
bx_r ans bx_getnext
i=$((i+1)); done; echo ans=$ans
'
echo ====
$sh -c '
. ./bx_r.sh
bx_r - date
bx_r - /bin/date
bx_r ans /bin/date
echo ans=$ans
'
echo ====
time $sh -c '
. ./bx_r.sh
bx_echoget() { echo 42; }
i=0; while [ $i -lt 10000 ]; do
ans=$(bx_echoget)
i=$((i+1)); done; echo ans=$ans
'
done
exit
#MY TEST OUTPUT
1
2
ans=10002
0.14user 0.00system 0:00.14elapsed 99%CPU (0avgtext+0avgdata 1644maxresident)k
0inputs+0outputs (0major+76minor)pagefaults 0swaps
====
Thu Sep 5 17:12:01 CEST 2019
Thu Sep 5 17:12:01 CEST 2019
ans=Thu Sep 5 17:12:01 CEST 2019
====
ans=42
1.95user 1.14system 0:02.81elapsed 110%CPU (0avgtext+0avgdata 1656maxresident)k
0inputs+1256outputs (0major+350075minor)pagefaults 0swaps
1
2
ans=10002
0.92user 0.03system 0:00.96elapsed 99%CPU (0avgtext+0avgdata 3284maxresident)k
0inputs+0outputs (0major+159minor)pagefaults 0swaps
====
Thu Sep 5 17:12:05 CEST 2019
Thu Sep 5 17:12:05 CEST 2019
ans=Thu Sep 5 17:12:05 CEST 2019
====
ans=42
5.20user 2.40system 0:06.96elapsed 109%CPU (0avgtext+0avgdata 3220maxresident)k
0inputs+1248outputs (0major+949297minor)pagefaults 0swaps
正如你所看到的,你可以获得统一的调用语法,同时加快速度
由于消除了捕获 ($()
) 的需要,小型 shell 函数的执行量最多增加了约 14 次。
关于bash - 在 bash 中有效获取命令输出的一致语法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57804252/