如何从脚本内部获取BASH脚本的完整调用命令(不仅仅是参数) [英] How to get the complete calling command of a BASH script from inside the script (not just the arguments)

查看:94
本文介绍了如何从脚本内部获取BASH脚本的完整调用命令(不仅仅是参数)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个BASH脚本,该脚本具有很长的参数集和两种调用方式:

I have a BASH script that has a long set of arguments and two ways of calling it:

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

my_script val1 val2 val3 .....  valn 

反过来,此脚本编译并运行大型的FORTRAN代码套件,最终生成netcdf文件作为输出.我已经在netcdf输出全局属性中拥有所有元数据,但是最好还包含用于创建该实验的完整运行命令.这样,另一个收到netcdf文件的用户只需重新输入运行命令即可重新运行实验,而不必拼凑所有选项.

This script in turn compiles and runs a large FORTRAN code suite that eventually produces a netcdf file as output. I already have all the metadata in the netcdf output global attributes, but it would be really nice to also include the full run command one used to create that experiment. Thus another user who receives the netcdf file could simply reenter the run command to rerun the experiment, without having to piece together all the options.

这是一个很长的说法,在我的BASH脚本中,如何获取从父shell输入的最后一个命令并将其放入变量中?即脚本正在询问我怎么称呼?"

So that is a long way of saying, in my BASH script, how do I get the last command entered from the parent shell and put it in a variable? i.e. the script is asking "how was I called?"

我可以尝试从选项列表中将其拼凑起来,但是非常长的选项列表和两个接口方法会使这变得冗长而艰巨,并且我相信有一种简单的方法.

I could try to piece it together from the option list, but the very long option list and two interface methods would make this long and arduous, and I am sure there is a simple way.

我找到了这个有用的页面:

I found this helpful page:

BASH:回显上一次运行的命令

,但这似乎只能使脚本本身内的最后执行的命令有效.询问者还提到了历史记录的使用,但是答案似乎暗示历史记录仅在程序完成后才包含命令.

but this only seems to work to get the last command executed within the script itself. The asker also refers to use of history, but the answers seem to imply that the history will only contain the command after the programme has completed.

非常感谢,如果您有任何想法.

Many thanks if any of you have any idea.

推荐答案

您可以尝试以下操作:

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

$BASH_SOURCE是指正在运行的脚本(被调用),而$@是参数数组. (($#)) &&确保仅在传递了至少1个参数后才执行以下printf命令; printf %q如下所述.

$BASH_SOURCE refers to the running script (as invoked), and $@ is the array of arguments; (($#)) && ensures that the following printf command is only executed if at least 1 argument was passed; printf %q is explained below.

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

  • While this won't always be a verbatim copy of your command line, it'll be equivalent - the string you get is reusable as a shell command.

chepner 在评论中指出,这种方法仅会捕获原始参数最终扩展为 :

chepner points out in a comment that this approach will only capture what the original arguments were ultimately expanded to:

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

  • For instance, if the original command was my_script $USER "$(date +%s)", $myInvocation will not reflect these arguments as-is, but will rather contain what the shell expanded them to; e.g., my_script jdoe 1460644812

chepner还指出,不可能(几乎)获得父进程接收到的实际原始命令行. 请告诉我您是否知道一种方法.

chepner also points that out that getting the actual raw command line as received by the parent process will be (next to) impossible. Do tell me if you know of a way.

  • 但是,如果您准备在调用脚本时要求用户做额外的工作,或者可以让他们通过您定义的别名来调用脚本-这显然很棘手-解决方案;见底部.

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

Note that use of printf %q is crucial to preserving the boundaries between arguments - if your original arguments had embedded spaces, something like $0 $* would result in a different command.
printf %q also protects against other shell metacharacters (e.g., |) embedded in arguments.

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

printf %q quotes the given argument for reuse as a single argument in a shell command, applying the necessary quoting; e.g.:

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

从外壳程序的角度来看,

a\ \|b与单引号字符串'a |b'等效的,但是此示例显示了结果表示如何不一定与输入表示相同.

a\ \|b is equivalent to single-quoted string 'a |b' from the shell's perspective, but this example shows how the resulting representation is not necessarily the same as the input representation.

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

Incidentally, ksh and zsh also support printf %q, and ksh actually outputs 'a |b' in this case.

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

If you're prepared to modify how your script is invoked, you can pass $BASH_COMMANDas an extra argument: $BASH_COMMAND contains the raw[1] command line of the currently executing command.
For simplicity of processing inside the script, pass it as the first argument (note that the double quotes are required to preserve the value as a single argument):

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.

不幸的是,要确保以这种方式调用脚本,只有两个选项,并且它们都不是最优的:

Unfortunately, there are only two options when it comes to ensuring that your script is invoked this way, and they're both suboptimal:

  • 最终用户必须以这种方式调用脚本-很明显,它脆弱而脆弱(不过,您可以检查一下在脚本中,第一个参数是否包含脚本名称和错误输出(如果没有).

  • The end user has to invoke the script this way - which is obviously tricky and fragile (you could however, check in your script whether the first argument contains the script name and error out, if not).

或者,提供一个别名,该别名包装$BASH_COMMAND 的内容,如下所示:

Alternatively, provide an alias that wraps the passing of $BASH_COMMAND as follows:

  • alias my_script='/path/to/my_script "$BASH_COMMAND"'
  • 棘手的部分是,必须在所有最终用户的外壳初始化文件中定义此别名,以确保其可用..
  • 此外,在脚本中,您还需要做一些额外的工作才能将命令行的别名扩展版本重新转换为别名形式:
  • alias my_script='/path/to/my_script "$BASH_COMMAND"'
  • The tricky part is that this alias must be defined in all end users' shell initialization files to ensure that it's available.
  • Also, inside your script, you'd have to do extra work to re-transform the alias-expanded version of the command line into its aliased form:
# 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.

遗憾的是,通过 script function 包装调用不是 选项,因为$BASH_COMMAND实际上只报告 current 命令的命令行,在脚本或函数包装器的情况下,将是该包装器的 inside 行.

Sadly, wrapping the invocation via a script or function is not an option, because the $BASH_COMMAND truly only ever reports the current command's command line, which in the case of a script or function wrapper would be the line inside that wrapper.

[1]唯一要扩展的是别名,因此,如果通过别名调用脚本,您仍然会看到 $BASH_COMMAND中的基础脚本,但是由于别名是用户特定的,因此通常是理想的选择.
所有其他参数甚至输入/输出重定向,包括进程替换<(...) 均按原样反映.

[1] The only thing that gets expanded are aliases, so if you invoked your script via an alias, you'll still see the underlying script in $BASH_COMMAND, but that's generally desirable, given that aliases are user-specific.
All other arguments and even input/output redirections, including process substitutiions <(...) are reflected as-is.

这篇关于如何从脚本内部获取BASH脚本的完整调用命令(不仅仅是参数)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆