如何在 bash 中通过 ref 将参数传递给脚本

How to pass an argument to a script by ref in bash

提问人:Adriano_Pinaffo 提问时间:8/6/2023 最后编辑:Adriano_Pinaffo 更新时间:8/8/2023 访问量:109

问:

请注意,我说的是将参数 by-ref 传递给脚本,而不是函数。我已经检查了这里和这里,他们讨论了使用函数传递参数。

我想要的是调用一个脚本,将一个参数传递给它,并使参数成为 shell 中的变量,并在脚本中分配值(这正是命令发生的情况,其中 RESPONSE 成为当前 shell 中的变量,其值由命令分配)。read RESPONSEread

我知道使用这样的函数(使用):local -n

#!/bin/bash

function boo() {
  local -n ref=$1
  ref="new"
}

SOME_VAR="old"
echo $SOME_VAR
boo SOME_VAR
echo $SOME_VAR

或者像这样(使用):eval

function booeval() {
  ref=$1
  eval $(echo $ref=\"new\")
}

SOME_VAR="old"
echo $SOME_VAR
booeval SOME_VAR
echo $SOME_VAR

按预期工作。但是(或/)不能在函数之外工作,并且似乎在子shell中运行,当脚本结束时,变量不会在shell中生存。我想要这样的东西:localdeclaretypedefeval

$ cat /tmp/test.sh
#!/bin/bash

ref=$1
eval $(echo "export $ref=\"new value\"")
$ /tmp/test.sh VAR
$ echo $VAR

$

有什么想法吗?

bash shell eval 传递引用

评论

3赞 Gordon Davisson 8/6/2023
您可以通过使用命令运行脚本(该命令或多或少像函数一样运行其内容)来执行此操作,但不能在正常运行脚本时运行。当您正常运行脚本时,它会在子进程中运行,该子进程无法访问父 shell 的变量(环境变量除外,但即使在那里,它也有不能影响原始脚本的副本)。此外,参数本质上只是文本字符串,而不是更复杂或更灵活的东西。source
0赞 Adriano_Pinaffo 8/7/2023
@GordonDavisson源的东西在 shell 中不起作用。我的意思是,它会的,但我不能依赖用户在运行程序之前运行源命令
1赞 pjh 8/7/2023
@Adriano_Pinaffo,一个选项是让您的程序检查它是否正在获取,如果不是,则退出并显示有用的错误消息。参见 BashFAQ/109(如何判断我的脚本是来源(带点)还是执行的?

答:

2赞 jhnc 8/6/2023 #1

正如您无疑知道的那样,在子 shell 中分配的内容将保留在子 shell 中。

正如注释中所指出的,一种解决方案是没有子 shell:如果脚本是 d,则将保留赋值。但是,这需要仔细编写脚本,以免产生不必要的副作用。source

如果正常调用脚本(作为子 shell),您可能能够解决复杂 IPC 传递值的问题,但在我看来,您可以通过简单地让脚本输出值而不是尝试将其分配给任何内容来完全绕过复杂性:

var=$(script)

您可以使用第一种方法将其包装起来,以便可以传入变量名称:

assign_to(){
    local -n ref=$1
    shift
    ref=$("$@")
}

例如:

$ myref=myvar
$ assign_to $myref date +%Y
$ declare -p myvar
declare -- myvar="2023"
$

评论

0赞 Adriano_Pinaffo 8/7/2023
但是在这里,您在函数中使用了本地 -n,这在脚本中是行不通的,因为我们谈到了整个子 shell。我想要类似在子 shell 上运行但为在主 shell 中传递的变量分配一个值的命令read
1赞 pjh 8/7/2023
@Adriano_Pinaffo,read 是一个 Bash 内置命令。它不在子 shell 中运行。这就是为什么它可以修改 shell 变量的原因。
0赞 jhnc 8/7/2023
我的函数在父函数中运行,因此如果它在子函数中运行,它会失败并不重要。正如我在第三段中所说,欢迎您尝试做一些更复杂的事情。
0赞 jhnc 8/7/2023
功能稍强(脚本调用可以包括重定向等):myref=myvar; mapfile -d '' $myref < <(date +%Y); declare -p myvar
0赞 Adriano_Pinaffo 8/8/2023
@pjh它确实是有道理的,它是一个 bash 命令,而不是 bash 调用的程序。该命令位于 bash 手册页中。谢谢你指出来
0赞 Ljm Dullaart 8/7/2023 #2

不支持“按引用”。bash

有很多方法可以解决它,使用 f.e. STDIO 或使用单独的文件。

如果使用单个变量,则 STDIO 的工作原理很简单:

parent_shell_var=$(bash script)

并且只是您要导出的值。echo

文件可以按以下方式使用: 父脚本:

#!/bin/bash
hop=0
echo $hop
bash child_script tempfile
. tempfile
rm tempfile
echo $hop

child_script:

#!/bin/bash
echo "hop=1" > "$1"

这些是非常简单的示例,可以替代参数的 by-ref 传递。这些适用于单独的脚本和下标。

0赞 Adriano_Pinaffo 8/8/2023 #3

感谢大家为回答这个问题所做的努力。我最终将选择使用作为参数传递的参数来设置环境变量。只要我做sudo,它就会很好用。gdb

这并不是一个没有非常谨慎的解决方案,因为你会调用内部 bash 函数,事情可能会搞砸。但总的来说,它会运行得很好。

检查一些取消设置只读变量的方法,我偶然发现使用 gdb 直接调用该函数。因此,我应用了相同的逻辑,并从脚本中设置了一个新变量。它“有点”有效,但并不顺利:unbind_variable()

$ cat /tmp/test.sh
#!/bin/bash

ref="$1"
value="Value assigned from inside the script"
bashpid=$(ps -oppid,pid,cmd | grep $0 | grep -v grep | cut -f1 -d' ')

sudo gdb -ex 'call (int) setenv("'"$ref"'","'"$value"'",1)' --pid=$bashpid --batch 2>/dev/null 1>&2

当我称它为:

$ declare -p VARPASSED
bash: declare: VARPASSED: not found
$ /tmp/test.sh VARPASSED
$ declare -p VARPASSED
declare -x VARPASSED="Value assigned from inside the script"

现在,传递的变量已正确分配给当前 shell。问题是 gdb 需要附加到当前的 shell,这有点烦人。他们是如何处理命令的?sudoread

PS:请在获取 bash pid 的路上减少一些松弛,我知道有更有效的方法可以做到这一点。那不是我的重点;)

评论

1赞 Charles Duffy 8/8/2023
要求用户在命令的输出中设置父级中的变量对于世界其他地方来说已经足够了,包括广泛部署的工具,例如 - 所以在现实世界中,这是用户忍受的约束;作为回报,他们获得了使用标准接口并可移植工作的工具,包括在 GDB 不可用的环境中(想想 MacOS,或者出于安全原因禁用 ptrace 的系统)。evalssh-agent
0赞 Adriano_Pinaffo 8/8/2023
eval无法在父级中设置变量,这就是问题所在
0赞 Charles Duffy 8/8/2023
这就是为什么 ssh-agent 和类似的工具需要 - 正如我所说 - 用户来评估他们的输出。如果您从未使用过 ssh-agent,请查看它的工作原理。几十年来,它的开发团队一直告诉用户,除非他们运行,否则他们的变量不会被设置。为什么你认为你不能做同样的事情?eval "$(ssh-agent -s)"
0赞 Adriano_Pinaffo 8/11/2023
我真的没有看到这里的比较。 生成一个字符串供 E Val 使用...那不是我想要的ssh-agent -s
1赞 Charles Duffy 8/11/2023
至于代码审查是一种选择——问题不仅在于需要审查和建立对调用 gdb 的代码的信任(尽管这是必不可少的——不仅要确保它不是主动恶意的,还要确保它不会无意中启用注入攻击,而这个答案中的代码实际上容易受到攻击), 而且所有内容都与调用代码具有相同的权限级别。