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

时间:2016-04-14 14:05:28

标签: linux bash shell

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

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

my_script val1 val2 val3 .....  valn 

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

所以这是一个很长的路要走,在我的BASH脚本中,如何从父shell输入最后一个命令并将其放入变量?即剧本问“我怎么叫?”

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

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

BASH: echoing the last command run

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

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

3 个答案:

答案 0 :(得分:8)

您可以尝试以下操作:

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

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

  • 虽然这不会一直是您命令行的逐字副本,但它等同于 - 您获得的字符串是可以作为shell命令重用。

  • chepner在评论中指出,此方法仅会捕获原始参数最终扩展为 的内容:

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

    • chepner还指出,获取父进程收到的实际原始命令行将是(旁边)不可能的请告诉我你是否知道某种方式。

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

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

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

 $ 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真的只有报告当前命令的命令行,在脚本或函数包装器的情况下,将是 in 包装器中的行。

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

答案 1 :(得分:3)

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

答案 2 :(得分:1)

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