Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/bash/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在bash中高效获取命令输出的一致语法?_Bash_Shell_Return_Command Substitution - Fatal编程技术网

在bash中高效获取命令输出的一致语法?

在bash中高效获取命令输出的一致语法?,bash,shell,return,command-substitution,Bash,Shell,Return,Command Substitution,Bash具有命令替换语法$(f),允许捕获 命令的标准值f。如果该命令是可执行的,则可以 –无论如何,创建新流程是必要的。但是如果命令是 使用此语法的shell函数为 我系统上的每个子shell。这足以造成明显的延误 在内部循环中使用时,尤其是在交互上下文中,如 命令完成或$PS1 常见的优化是用于返回值, 但它以可读性为代价:意图变得不那么清晰,并且 shell函数和 可执行文件。我在下面添加选项及其弱点的比较 为了获得一致、可靠的语法,我想知道bash是否 允许捕获外壳函数和可执行输出的任何

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)
  • 设置变量的副作用并不明显
  • 很容易意外地使用错误的变量
4.使用全局变量,其名称作为参数传递
  • 可读性更强,允许轻松返回多个值
  • 仍然与捕获可执行文件的输出不一致
  • 注意:动态变量名称的赋值应使用
    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"