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



我有一个BASH脚本,它有一组很长的参数和两种调用方法:

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

my_script val1 val2 val3 .....  valn 

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

因此,在我的BASH脚本中,这是一个很长的问题,我如何从父shell中获得最后一个输入的命令并将其放入变量中?即脚本询问"我是怎么被叫的?"

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

我发现这个有用的页面:

BASH:回显上次运行的命令

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

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

您可以尝试以下操作:

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

$BASH_SOURCE是指正在运行的脚本(调用时),$@是参数数组;(($#)) &&确保只有在传递了至少一个参数的情况下才执行以下printf命令;下面对CCD_ 5进行说明。

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

  • chepner在评论中指出,这种方法只会捕获原始参数最终被扩展到的内容:

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

    • chepner还指出,获取父进程接收到的实际原始命令行是不可能的如果你知道办法,一定要告诉我

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

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

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

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

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

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


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

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中看到底层脚本。但考虑到别名是特定于用户的,这通常是可取的
所有其他参数,甚至输入/输出重定向,包括进程替换<(...),都按原样反映

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

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

最新更新