如何从脚本内部获取 BASH 脚本的完整调用命令(而不仅仅是参数)

How to get the complete calling command of a BASH script from inside the script (not just the arguments)

提问人:ClimateUnboxed 提问时间:4/14/2016 最后编辑:CommunityClimateUnboxed 更新时间:4/15/2016 访问量:9418

问:

我有一个 BASH 脚本,它有一长串参数和两种调用方式:

my_script --option1 value --option2 value  ... etc

my_script val1 val2 val3 .....  valn 

该脚本反过来编译并运行一个大型 FORTRAN 代码套件,该套件最终生成一个 netcdf 文件作为输出。我已经在 netcdf 输出全局属性中拥有所有元数据,但如果还包括用于创建该实验的完整运行命令,那就太好了。因此,收到 netcdf 文件的其他用户只需重新输入 run 命令即可重新运行实验,而不必将所有选项拼凑在一起。

所以说,在我的 BASH 脚本中,我如何从父 shell 中获取最后一个输入的命令并将其放入变量中?也就是说,剧本在问“我是怎么被叫的?

我可以尝试从选项列表中将其拼凑在一起,但是很长的选项列表和两种接口方法会使它变得漫长而艰巨,我相信有一个简单的方法。

我发现这个有用的页面:

BASH:回显上次运行的命令

但这似乎只能在脚本本身中执行最后一个命令。提问者还提到了历史的使用,但答案似乎暗示历史只有在程序完成后才会包含命令。

如果你们中的任何人有任何想法,非常感谢。

Linux bash 外壳

评论


答:

13赞 mklement0 4/14/2016 #1

您可以尝试以下操作:

myInvocation="$(printf %q "$BASH_SOURCE")$((($#)) && printf ' %q' "$@")"

$BASH_SOURCE引用正在运行的脚本(调用时),是参数数组; 确保仅在传递至少 1 个参数时执行以下命令; 解释如下。$@(($#)) &&printfprintf %q

  • 虽然这并不总是命令行的逐字副本,但它是等效的 - 您获得的字符串可作为 shell 命令重用。

  • Chepner在评论中指出,这种方法只能捕捉到原始论点最终扩展为的内容

    • 例如,如果原始命令是 ,则不会按原样反映这些参数,而是包含 shell 将它们扩展为的内容;例如,my_script $USER "$(date +%s)"$myInvocationmy_script jdoe 1460644812

    • Chepner 还指出,获取父进程接收到的实际原始命令行将(几乎)是不可能的如果你知道一个方法,请告诉我。

      • 但是,如果您准备要求用户在调用脚本时执行额外的工作,或者您可以让他们通过您定义的别名调用您的脚本(这显然很棘手),那么有一个解决方案;见底部。

请注意,使用 printf %q 对于保留参数之间的边界至关重要 - 如果原始参数嵌入了空格,则类似操作将导致不同的命令。
还可以防止参数中嵌入的其他 shell 元字符(例如 )。
$0 $*printf %q|

printf %q引用给定的参数以作为 shell 命令中的单个参数重用,应用必要的引用;例如:

 $ printf %q 'a |b'
 a\ \|b

a\ \|b从 shell 的角度来看,等于单引号字符串,但此示例显示了生成的表示形式不一定与输入表示形式相同。'a |b'

顺便说一句,ksh 和 zsh 也支持 printf %q,在这种情况下,ksh 实际上输出了 'a |b'


如果您准备修改脚本的调用方式,可以将 $BASH_COMMAND作为额外参数传递: 包含当前正在执行的命令的原始 [1] 命令行
为了简化脚本内部的处理,请将其作为第一个参数传递(请注意,需要使用双引号才能将值保留为单个参数):
$BASH_COMMAND

my_script "$BASH_COMMAND" --option1 value --option2

在脚本中:

# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
myInvocation=$1    # Save the command line in a variable...
shift              # ... and remove it from "$@".

# Now process "$@", as you normally would.

不幸的是,在确保以这种方式调用脚本时,只有两种选择,而且它们都是次优的:

  • 最终用户必须以这种方式调用脚本 - 这显然是棘手和脆弱的(但是,您可以在脚本中检查第一个参数是否包含脚本名称和错误,如果不是)。

  • 或者,提供一个别名,用于包装 $BASH_COMMAND 的传递,如下所示:

    • alias my_script='/path/to/my_script "$BASH_COMMAND"'
    • 棘手的部分是,必须在所有最终用户的 shell 初始化文件中定义此别名,以确保它可用
    • 此外,在脚本中,您必须执行额外的工作才能将命令行的别名扩展版本重新转换为其别名形式:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
# Here we also re-transform the alias-expanded command line to
# its original aliased form, by replacing everything up to and including
# "$BASH_COMMMAND" with the alias name.
myInvocation=$(sed 's/^.* "\$BASH_COMMAND"/my_script/' <<<"$1")
shift              # Remove the first argument from "$@".

# Now process "$@", as you normally would.

可悲的是,通过脚本或函数包装调用是一种选择,因为真正只报告当前命令的命令行,在脚本函数包装器的情况下,该命令行将是该包装器中的行。$BASH_COMMAND


[1] 唯一扩展的是别名,因此,如果您通过别名调用脚本,您仍会在 $BASH_COMMAND 中看到底层脚本,但这通常是可取的,因为别名是特定于用户的。
所有其他参数,甚至输入/输出重定向,包括进程替换 <(...) 原样反映。

评论

2赞 chepner 4/14/2016
也许与 OP 的请求更相关,这不会区分 和 .父进程作为输入接收的原始字符串将很难恢复,这并非不可能。cmd "$(echo user)"cmd user
2赞 Gordon Davisson 4/14/2016
避免尾随空格的简单方法:使用(即引号参数之前的空格,而不是引号后的空格)作为参数,并删除两个 s 之间的空格。printf ' %q' ...$(printf ...)
0赞 Michael Vehrs 4/15/2016
好吧,你总是可以尝试类似的东西,它输出的东西像 。$ echo a b c -- $(history 1)a b c -- 704 echo a b c -- $(history 1)
0赞 mklement0 4/15/2016
@MichaelVehrs:这是一种很有前途的方法,但它使用起来更简单、更快速、更可靠(历史记录扩展可能会被关闭)。但是,问题在于最终用户必须自己传递这个额外的参数,或者您必须为他们提供一个别名来包装这个额外的步骤。请看我的更新。$BASH_COMMAND
7赞 choroba 4/14/2016 #2

"$0"包含脚本的名称,包含参数。"$@"

评论

3赞 mklement0 4/14/2016
@AdrianTompkins:仅当您的参数没有嵌入空格时才有效(这在您的特定情况下可能不是问题,但这不是重新创建命令的通常可靠的方法)。从更切身的角度来看,通常是 中更好的选择,因为它在获取脚本时也能正确反映脚本,这与 不同。$0 $@$BASH_SOURCEbash$0
3赞 Michael Vehrs 4/14/2016 #3

你的意思是这样的吗?echo $0 $*

评论

1赞 Michael Vehrs 4/14/2016
在 linux 上,您还可以执行类似 .tr '\0' ' ' < /proc/$$/cmdline
4赞 mklement0 4/14/2016
$0 $*仅当没有参数嵌入空格或其他 shell 元字符时才有效。当通过 shebang 行直接调用脚本时,该方法似乎不包含命令行(仅在我的 Ubuntu 14.04 系统上报告);仅当您显式调用脚本时,它才有效/proc/$$/cmdlinebash<space>bash my_script ...
0赞 Michael Vehrs 4/14/2016
是吗?它适用于 RHEL 7.0(内核 3.10.0)上的 shebang。
0赞 mklement0 4/15/2016
这很奇怪 - 不知道为什么它们会有所不同(Ubuntu 14.04 内核是 3.13.0-85-generic)。这是一种有趣的技术,但鉴于它在功能上等同于 ,出于简单性、性能和可移植性的原因,后者更可取。也就是说,重要的是它并不强大$0 $*$0 $*
1赞 Charles Duffy 6/24/2021
@MichaelVehrs,会更可靠(回复:支持带有引号、空格、通配符等的参数)......在一定程度上可以做到可靠,这是有限的。BashFAQ #28 讨论了它的局限性。printf '%q ' "$0" "$@"; printf '\n'$0