在bash中高效获取命令输出的一致语法?
Bash具有命令替换语法在bash中高效获取命令输出的一致语法?,bash,shell,return,command-substitution,Bash,Shell,Return,Command Substitution,Bash具有命令替换语法$(f),允许捕获 命令的标准值f。如果该命令是可执行的,则可以 –无论如何,创建新流程是必要的。但是如果命令是 使用此语法的shell函数为 我系统上的每个子shell。这足以造成明显的延误 在内部循环中使用时,尤其是在交互上下文中,如 命令完成或$PS1 常见的优化是用于返回值, 但它以可读性为代价:意图变得不那么清晰,并且 shell函数和 可执行文件。我在下面添加选项及其弱点的比较 为了获得一致、可靠的语法,我想知道bash是否 允许捕获外壳函数和可执行输出的任何
$(f)
,允许捕获
命令的标准值f
。如果该命令是可执行的,则可以
–无论如何,创建新流程是必要的。但是如果命令是
使用此语法的shell函数为
我系统上的每个子shell。这足以造成明显的延误
在内部循环中使用时,尤其是在交互上下文中,如
命令完成或$PS1
常见的优化是用于返回值,
但它以可读性为代价:意图变得不那么清晰,并且
shell函数和
可执行文件。我在下面添加选项及其弱点的比较
为了获得一致、可靠的语法,我想知道bash是否
允许捕获外壳函数和可执行输出的任何功能
类似,同时避免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.用命令替换 慢,但可读性和一致性:这对读者一开始并不重要 查看
get
是shell函数还是可执行文件
2.对所有函数使用相同的全局变量
模糊了$person
应该包含的内容。或者
get lastname
local lastname="$R"
get firstname
local firstname="$R"
person="$lastname, $firstname"
但这太冗长了
3.每个函数具有不同的全局变量
- 更具可读性的作业,但
- 如果某个函数被调用两次,我们回到(2)
- 设置变量的副作用并不明显
- 很容易意外地使用错误的变量
- 可读性更强,允许轻松返回多个值
- 仍然与捕获可执行文件的输出不一致
- 注意:动态变量名称的赋值应使用
而不是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.
[1] 如果希望它高效,shell函数不能通过标准输出返回结果。如果他们这样做了,就没有办法得到它,只能在子shell中运行函数并通过内部管道捕获输出,而这些操作有点昂贵(在现代系统上只有几毫秒) 当我关注shell脚本并且需要最大限度地提高它们的性能时,我使用了一种约定,其中函数
foo
将通过变量foo
返回其结果。即使在POSIX shell中也可以这样做,它有一个很好的特性,即它不会覆盖您的局部变量,因为如果foo
是一个函数,那么您已经保留了该名称
然后我有了这个bx_r
getter函数,它运行一个shell函数,并将其输出保存到一个变量中,该变量的名称由第一个参数给出,或者如果第一个参数是一个非法变量名称的单词,它将输出输出到stdout(如果单词恰好是一个空单词,即“”,则没有换行符)
我对它进行了修改,以便它可以与命令或函数统一使用
这里不能使用类型builtin来区分两者,因为
类型通过stdout=>返回其结果,您需要捕获该结果,这将再次施加分叉惩罚
因此,当我要运行函数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 nameref
对于bash v4,您可以使用:
NameRef仍然可以与外部范围中的变量冲突。对nameref使用长名称,就像我在这里使用下划线、函数名、两个下划线,然后是变量名。关于最后一个代码段:
eval“$VARNAME=\$LOCALVALUE”
应该做您想做的事情(最好使用小写名称,除非您处理的是导出
ed变量)。使用足够现代的Bash shell,您可以使用全局关联数组,并将返回值分配给与确切函数名匹配的键。现在,人们可以认为全球化是一种邪恶的行为,尤其是如果你想从事工作或合作例行公事的话。人们用Bash做了很多扭曲的事情,包括对象和类。最后,如果Bash或shell速度不够快,或者不允许对任务进行清晰编码,那么它可能就是工作的错误语言。printf-v
甚至比declare
@LéaGris更安全。我想的主要是$PS1
提示符,但又想了想,甚至可以将其委托给python脚本或编译的可执行文件…使用declare
-是的,但是使用declare-n
,而不是declare-g
。您的解决方案的核心类似于我的变体(3),但是bx_r函数和您建议的扩展实际上会使它保持一致。@kdb我正确地查看了它并编辑了答案。最初的bx_r也做了缓存。这一个跳过了缓存,但它也增加了处理命令的能力。@kdb这个bx_r告诉我们的方式
get_lastname
get_firstname
person="$lastname $firstname"
get LN lastname
get FN firstname
person="$LN, $FN"
$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.
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
get() {
declare -n _get__res
_get_res="$1"
case "$2" in
firstname) _get_res="Kamil"; ;;
lastname) _get_res="Cuk"; ;;
esac
}
get LN lastname
get FN firstname
person="$LN, $FN"