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

标签 linux bash shell

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

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

或者
my_script val1 val2 val3 .....  valn 

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

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

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

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

BASH: echoing the last command run

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

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

最佳答案

您可以尝试以下操作:

myInvocation="$(printf %q "$BASH_SOURCE")$((($#)) && printf ' %q' "$@")"
$BASH_SOURCE指正在运行的脚本(如调用),和 $@是参数数组; (($#)) &&确保以下 printf仅当至少传递 1 个参数时才执行命令; printf %q解释如下。
  • 虽然这并不总是您的命令行的逐字副本,但它是等效的 - 您获得的字符串可作为 shell 命令重用。
  • chepner在评论中指出这种方法 只会捕获原始参数最终扩展为 的内容:
  • 例如,如果原始命令是 my_script $USER "$(date +%s)" , $myInvocation不会按原样反射(reflect)这些参数,而是包含 shell 将它们扩展到的内容;例如,my_script jdoe 1460644812
  • chepner 还指出 获取父进程接收到的实际原始命令行将是(几乎)不可能的 .如果你知道一种方法,请告诉我。
  • 但是,如果您准备要求用户在调用您的脚本时做额外的工作,或者您可以让他们通过您定义的别名来调用您的脚本——这显然很棘手——有一个解决方案;见底部。

  • 请注意 使用 printf %q对于保留参数之间的边界至关重要 - 如果您的原始参数嵌入了空格,例如 $0 $*将导致不同的命令。printf %q还可以防止嵌入参数中的其他 shell 元字符(例如, | )。
    printf %q引用给定的参数作为 shell 命令中的单个参数重用,应用必要的引用;例如。:
     $ printf %q 'a |b'
     a\ \|b
    
    a\ \|b相当于单引号字符串 'a |b'从 shell 的角度来看,但此示例显示了结果表示如何不一定与输入表示相同。

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

    关于linux - 如何从脚本内部获取 BASH 脚本的完整调用命令(不仅仅是参数),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36625593/

    相关文章:

    Python ctypes 在 Linux 上从 libc 调用 reboot()

    python - 使用 Bash 脚本从文件夹名称中删除前 4 个字母

    linux - 查找mac地址的等效windows命令

    linux - 为什么在分组命令中执行简单命令不会fork子shell进程,而复合命令会做

    linux - 如何编写 shell 脚本通过 2 台计算机到第三台计算机的隧道

    regex - 如何删除文件开头的以 "//"开头的行(例如文件头)?

    linux - 使用 bash 返回码进行编程是一种好方法吗?

    mysql - 执行mysql查询后捕获退出代码

    bash - 我需要我的脚本来编辑和强制写入只读文件

    macos - 如果有一个冒号( :) in the directory name, 我怎么能把它添加到 $PATH?