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 bash
s。您有 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.
成功了。万岁!
在我制作的 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 bash
s。您有 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.
成功了。万岁!