关于args:Bash $ 0与终端使用的命令不匹配

Bash $0 does not match command used at terminal

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

我发现,当我从相对路径调用该脚本时,如/script.sh,$0是我所期望的"./script.sh"。但是,如果script.sh在我的路径中,我用script.sh来调用它,那么$0不是"script.sh"。它最终成为脚本文件的绝对路径。

我可以用basename $0来纠正第二个案例,但这会把第一个案例搞砸。有没有办法确切地知道用户为启动脚本文件的命令键入了什么文本?


不,您通常无法了解用户键入的内容。

用户可能有:

  • 而是键入一个函数或包装器,它运行您的命令和其他命令,并且不能传递参数。
  • 在一个高度非Bourne外壳中输入一些东西,比如Shelly的run_"./script" ["--foo"]
  • 单击桌面菜单而不是键入内容。
  • 在一个运行在Android TV上的Java代码段中从一个不可重复的流中执行脚本。

规范行为是让调用者将argv[0]设置为"与正在启动的进程相关联的文件名字符串"(posix),不管它是什么意思,并让您将其回送到类似sh的用法描述中。

不幸的是,对于脚本,调用者的argv[0]在指向shebang解释器的间接过程中丢失了,相反,shebang解释器将$0设置为它要解释的文件名参数。

不过还是不错的。重要的是你要用basename"$0""$0"来识别自己,并指出参数错误的原因。用户有责任将更改合并到其特定的工作流中。


我认为答案是否定的。好的。

如果我理解正确,您要做的就是创建一个user_entry变量,这样您就可以编写:好的。

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

我将讨论为什么我认为你不能低于,但首先,让我们试着理解行为。很简单,$0是给执行脚本的子shell的命令。好的。

您所描述的发生这种情况的原因是执行脚本在子shell中运行脚本。在这个子shell中,成为$0$1$2的论点得到了解决和定稿。要查看此信息,请尝试以下操作。这是我写的一个程序,它将展示我希望能解释的概念。(它还将处理您描述的"附加标志"内容。)好的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#/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 $0, $1, $2, ...

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

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

echo"Working directory: $(pwd)"
echo '$'"0 = $0"
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以确保它是可执行的。好的。

现在,如果可能的话(这将使事情更容易看到),关闭所有外壳(终端)。好的。

打开一个新的终端(我们称之为终端1)并键入ps。我希望写下以下内容作为描述完全相同行为的另一种方式是有意义的:好的。

1
Terminal1> ps

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

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

返回终端1并键入好的。

1
Terminal1> ps

现在应该在输出中看到两个bash和一个ps。(可能还有其他的东西,但不超过两个bash和不少于两个)2个bash告诉你,2个外壳是打开的,我希望你能从屏幕上看到。好的。

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

我们去终点站2。将目录更改为try_it.sh脚本的位置。好的。

1
Terminal2> cd /path/to/script/

将脚本(在terminal2中)运行为好的。

1
Terminal2> ./try_it.sh 1 2 3

我看到的是:好的。

1
2
3
4
5
6
7
8
9
10
11
12
$ ./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
$0 = ./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/。好的。

返回终端1,再次运行ps。好的。

1
Terminal1> ps

你应该看到3个bashs。你有两个打开的外壳和一个子外壳。请注意,无限循环允许您"看到"子shell。另外,你在ps的输出中看到的sleep使"点击ctrl+c停止程序的执行"不会在屏幕上重复。好的。

在这里,我们可以看到"给予"子壳的命令是./try_it.sh$0是给子shell的命令。好的。

好的,Ctrl+C在终点2。到1号终点站,到1号终点站。好的。

1
Terminal1> ps

只有2个bashs。子壳已关闭。好的。

现在,尝试以下操作:好的。

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

Terminal1> ps

bashs:3;给子shell的命令:/path/to/script/try_it.sh。好的。

试试这个:好的。

1
Terminal2> try_it.sh 1 2 3

你会得到一个错误。如果到可执行文件的路径没有添加到PATH中,则需要有到脚本的相对或绝对路径才能执行。好的。

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

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

别担心,下次你关闭终端2时,这会从你的路径上消失。好的。

现在,当你还在/path/to/script的时候,跑好的。

1
2
3
Terminal2> try_it.sh

Terminal1> ps

bashs:3;给子shell的命令:/path/to/script/try_it.sh。好的。

"Linux的魔力"已经通过PATH中的目录,寻找具有可执行try_it.sh的目录。LinuxMagic连接正确的目录和脚本名,然后将完整的命令传递给子shell。好的。

让我们在/path/to/script/中再编一个目录好的。

1
2
Terminal2> mkdir another_directory
Terminal2> cd another_directory

让我们用相对路径来运行它。好的。

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

Terminal1> ps

bashs:3;给子shell的命令:../try_it.sh。好的。

更疯狂的相对路径。我的/path/to/script/实际上是/home/me/other_dir/,所以我会做一个疯狂的相对路径,如下所示,注意我在another_directory目录中。好的。

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

Terminal1> ps

bashs:3;给子shell的命令:../../other_dir/another_directory/../try_it.sh。好的。

我们已经把脚本的路径放到了PATH中,所以让我们试试another_directory中的以下内容。好的。

1
2
3
Terminal2> try_it.sh

Terminal1> ps

bashs:3;给子shell的命令:/path/to/script/try_it.sh。好的。

现在,绝对路径。这是从another_directory运行的。好的。

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

Terminal1> ps

bashs:3;给子shell的命令:/path/to/script/try_it.sh。好的。

再一次,$0是给子壳的命令。好的。


我对你问题答案的看法

你问,好的。

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

Ok.

我认为答案是否定的。如果你按照我所展示的步骤来做,我希望你能理解一点为什么你会有这样的行为。我认为,在大多数情况下,您可以找到用户键入的内容。但是,我看不出这两个命令之间有什么区别(在/path/to/script添加到您的PATH之后)好的。

1
Terminal2> try_it.sh 1 2 3

和好的。

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

也许你能想出一个方法来区分。如果是,请将其作为答案发布。好的。


注:请参阅本文和此链接(搜索"子串删除"),以获取有关${#%/*}${##*/}的说明。多亏了那些人,所以我不用写解释了。好的。


只是为了好玩好的。

让我们看看我是否真的为某人使用-s标志时的正确行为进行了编程。好的。

1
2
3
4
5
6
7
8
9
10
11
12
$ /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
$0 = /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.

它奏效了。万岁!好的。好啊。