Bash $0 与终端使用的命令不匹配

Bash $0 does not match command used at terminal

在我制作的 bash 脚本中,我试图向用户打印一些信息(以响应特定错误),建议他们在命令中添加一个开关。我认为 [=11=] 总是包含用户在终端作为命令键入的文本,所以我的 bash 看起来像这样:echo "try '[=12=] -s $@' instead."

我发现当我从相对路径(例如 ./[=27=)调用该脚本时,[=11=] 是我预期的 "./script.sh"。但是,如果 script.sh 在我的 PATH 中并且我用 script.sh 调用它,[=11=] 而不是 "script.sh"。它最终成为脚本文件的绝对路径。

我可以使用 basename [=18=] 来纠正第二种情况,但这会搞砸第一种情况。有没有办法找出用户为启动脚本文件的命令键入的确切文本?

不,您通常无法找出用户输入的内容。

用户可能有:

  • 改为键入一个函数或包装器,它运行您的命令和其他命令,并且可能不传递参数。
  • 用非常非谍影重重 shell 的语言输入内容,例如 Shelly 的 run_ "./script" ["--foo"]
  • 单击桌面菜单而不是键入内容。
  • 在 Android 电视上的 Java 片段 运行 中的不可重复流中执行脚本。

规范行为是调用者将 argv[0] 设置为 "a filename string that is associated with the process being started" (POSIX),不管它是什么意思,然后你在 sh-类似用法说明。

对于脚本来说不幸的是,调用者的 argv[0] 在指向 shebang 解释器的间接寻址中丢失了,而最终将 [=14=] 设置为其正在解释的文件名参数。

不过还是不错的。重要的是你用 basename "[=15=]""[=16=]" 来标识你自己,并指出参数错误的原因。用户有责任将更改合并到他们的特定工作流程中。

我认为答案是否定的

如果我没理解错的话,你想做的是创建一个 user_entry 变量,这样你就可以写:

echo "try '$user_entry -s $@' instead"

我会在下面讨论为什么我认为你不能这样做,但首先,让我们试着理解这种行为。很简单,[=32=]是给执行脚本的子shell的命令。

您描述的发生的原因是执行脚本运行是子shell中的脚本。在这个子shell中,变成[=32=]</code>、<code>的参数被解决并最终确定。要查看此内容,请尝试以下操作。这是我写的一个程序,它将展示我希望能解释事物的概念。 (它还会处理您描述的 "additional flag" 内容。)

转到不在您的 PATH 中的目录并保存以下程序。我叫它try_it.sh

#/bin/bash

echo "Welcome to the program!"

#print out all of the arguments as given in the subshell
for (( i=0;i<=$#;i++ )); do
  echo "Argument ${i} is: ${!i}"
                         # bash's inderection feature, allows [=11=], , , ...

done #endof:  for (( i=0;i<=$#;i++ ))

if [[  != "-s" ]]  ; then
  echo "try '[=11=] -s $@' instead"
fi #endof: if [[  != "-s" ]]

echo "Working directory: $(pwd)"
echo '$'"0 = [=11=]"
echo "Everything before the script name and last / : "\
'${'"0"'%/*}'" = "${0%/*}
echo "Just the script name: "'${'"0"'##*/}'" = "${0##*/}

while true; do
  echo "Hit Ctrl+C to stop this program's execution."
  sleep 10000 # infinite loop, with instructions on how to get out
done #endof:  while true

运行 chmod +x try_it.sh 以确保它是可执行的。

现在,如果可能(这将使事情更容易看清),请关闭所有 shells(终端)。

打开一个新终端(我们称它为 Terminal1)并输入 ps。我希望将以下内容作为描述完全相同操作的另一种方式是有意义的:

Terminal1> ps

这个 ps 告诉您 运行 在 Linux 内核上正在运行哪些进程。您应该在输出中看到一个 bash 和一个 ps。输出中的 1 bash 告诉你 1 shell 是打开的,我希望你能从你的屏幕上看到。

打开第二个终端,我们称之为终端 2。

返回 Terminal1 并输入

Terminal1> ps

您现在应该在输出中看到两个 bash 和一个 ps。 (可能还有其他的东西,但是不多于两个bash,也不少。)2个bash告诉你,2个shell是开放的,希望你可以从你的屏幕上看到。

好的。我们来算一下[=32=]的过程。我不打算把所有的输出放在这里。希望看到屏幕上的输出(当您尝试 运行 描述脚本时,)您可以弄清楚如何创建 user_entry 变量。我实际上不认为你能做到这一点,但我会尽力让你知道发生了什么。

我们去Terminal2吧。将目录更改为 try_it.sh 脚本的位置。

Terminal2> cd /path/to/script/

运行 脚本(在 Terminal2 中)为

Terminal2> ./try_it.sh 1 2 3

这是我看到的:

$ ./try_it.sh 1 2 3
Welcome to the program!
Argument 0 is: ./try_it.sh
Argument 1 is: 1
Argument 2 is: 2
Argument 3 is: 3
try './try_it.sh -s 1 2 3' instead
Working directory: /home/me/other_dir
[=16=] = ./try_it.sh
Everything before the script name and last / : ${0%/*} = .
Just the script name: ${0##*/} = try_it.sh
Hit Ctrl+C to stop this program's execution.

(暂时不要推送"Ctrl+C"。)注意我的/path/to/script//home/me/other_dir/

Return 到 Terminal1 并再次 运行 ps

Terminal1> ps

您应该看到 3 bashs。您有 2 个打开的 shell 和一个子 shell。请注意,无限循环允许您 "see" 子 shell。此外,您在 ps 的输出中看到的 sleep 使 "Hit Ctrl+C to stop this program's execution." 不会在屏幕上重复。

在这里,我们可以看到对子shell的命令"given"是./try_it.sh[=32=]是给子的命令shell.

好的,在 Terminal2 上 Ctrl+C。继续,在 Terminal1 上 ps

Terminal1> ps

只有 2 bash 秒。该子shell已关闭

现在,尝试以下操作:

Terminal2> /path/to/script/try_it.sh 1 2 3

Terminal1> ps

bash个数:3;给 sub-shell 的命令:/path/to/script/try_it.sh

试试这个:

Terminal2> try_it.sh 1 2 3

你得到一个错误。当可执行文件的路径未添加到 PATH 时,需要有相对或绝对路径才能执行的脚本。

让我们把脚本放在 PATH 中。对我来说(在 Cygwin 上),这是下一个命令。 确保您知道在您的系统上添加到 PATH 的正确方法。不要不检查就用这个!

export PATH="$(pwd):$PATH"

别担心,下次您关闭 Terminal2 时,这将从您的路径中消失。

现在,当您还在 /path/to/script、运行

Terminal2> try_it.sh

Terminal1> ps

bash个数:3;给 sub-shell 的命令:/path/to/script/try_it.sh

"magic of Linux" 已遍历 PATH 中的目录,寻找具有可执行文件 try_it.sh 的目录。 Linux 魔法连接正确的目录和脚本名称,然后将该完整命令传递给子 shell.

让我们在/path/to/script/

中创建另一个目录
Terminal2> mkdir another_directory
Terminal2> cd another_directory

让我们运行它有一个相对路径。

Terminal2> ../try_it.sh 1 2 3

Terminal1> ps

bash个数:3;给 sub-shell 的命令:../try_it.sh

更疯狂的相对路径。我的 /path/to/script/ 实际上是 /home/me/other_dir/,所以我将按照如下方式创建一个疯狂的相对路径,注意我在 another_directory 目录中。

Terminal2> ../../other_dir/another_directory/../try_it.sh 1 2 3

Terminal1> ps

bash个数:3;给子shell的命令:../../other_dir/another_directory/../try_it.sh

我们已经将脚本的路径放入 PATH,所以让我们从 another_directory

中尝试以下操作
Terminal2> try_it.sh

Terminal1> ps

bash个数:3;给 sub-shell 的命令:/path/to/script/try_it.sh

现在,绝对路径。这是来自 another_directory

的 运行
Terminal2> /path/to/script/try_it.sh 1 2 3

Terminal1> ps

bash个数:3;给 sub-shell 的命令:/path/to/script/try_it.sh

再一次,[=32=]是给子shell的命令。


我对您问题的回答的看法

你问了,

Is there a way to find out exactly what text the user typed for the command that started the script file?

我认为答案是否定的。如果您按照我所展示的步骤进行操作,我希望您能稍微理解为什么会出现这种行为。我认为,在大多数情况下,您可以找出用户输入的内容。但是,我无法区分这两个命令(一旦 /path/to/script 已添加到您的 PATH

Terminal2> try_it.sh 1 2 3

Terminal2> /path/to/script/try_it.sh 1 2 3

也许你能想出一种方法来区分。如果是这样,post 它作为答案。


注意:参见this SO post and this link(搜索"Substring Removal")以获得关于${#%/*}${##*/}的解释。感谢那些人的帮助,所以我不用写解释了。


纯属娱乐

让我们看看当有人 使用 -s 标志时,我是否真的以正确的行为编程。

$ /path/to/script/try_it.sh -s 1 2 3
Welcome to the program!
Argument 0 is: /home/dblack/other_dir/try_it.sh
Argument 1 is: -s
Argument 2 is: 1
Argument 3 is: 2
Argument 4 is: 3
Working directory: /home/dblack/other_dir
[=30=] = /home/dblack/other_dir/try_it.sh
Everything before the script name and last / : ${0%/*} = /home/dblack/other_dir
Just the script name: ${0##*/} = try_it.sh
Hit Ctrl+C to stop this program's execution.

成功了。万岁!