您当前的位置: 首页 >  编程语言

编写可以从任何地方执行的shell脚本的正确方法~开头不要移动目录!

介绍

有一些文章描述了在开头移动当前目录的代码,好像这是一种***实践,以便您可以任何地方运行 shell 脚本,但事实并非如此。例如,以下代码是错误代码。

sc 理论 pt.嘘
# スクリプトのある場所にカレントディレクトリを移動してはいけない
cd "$(dirname "$0")"

# 上記のやたらと面倒な書き方
WORKDIR=$(cd "$(dirname "$0")" && pwd)
cd "$WORKDIR"

我不应该写吗?如果您问我,我认为您可以在了解原因后将其写为“草率”,但这不是***做法。

为什么这个写错了?

如果这个 shell 脚本安装在/usr/local/bin 目录下呢?如果你想一想,你就会明白。这是正确的。 /usr/local/bin 因为它试图在目录下写入文件。此目录通常只能由 root 写入,这很不方便,而且最重要的是,您永远不应该将数据写入此位置以供程序使用。 (注:我说的是写数据的前提)

想想操作系统标准命令和其他 CLI 命令是什么样的。您不会将数据写入命令所在的目录(/bin 或/usr/bin),对吗?例如,执行touch foo 时创建的文件在哪里?

当前目录是。

许多标准的操作系统命令在程序执行前基于当前目录读取和写入指定路径中的文件。尽管有例外,但作为一般规则,当前目录在程序执行期间不会更改。

不要共享 shell 脚本位置和数据目录!

为了简单起见,我说不要移动目录,但正确的说法是你不应该使用你的 shell 脚本的位置作为你的数据目录。由于包含 shell 脚本的目录可能不可写,因此将数据目录分开是有意义的。

只要脚本位置和数据目录分开,您就可以移动目录。具体而言,模式如下。

可以移动目录的示例 移动到指定目录时

如果您转到指定目录而不是转到 shell 脚本位置,那很好。例如:

$ mycmd --flag ./datadir
我的命令
#!/bin/bash

# オプションの処理(手抜き): ハイフンで始まる引数を飛ばす
while [[ "${1:-}" = -* ]]; do shift; done

if [ $# -gt 0 ]; then
  cd "$1"
fi

这用于移动到由命令参数指定的目录 (./datadir)。此 shell 脚本的其他用途。

$ ./script/mycmd ./script # スクリプトがあるディレクトリをデータディレクトリとして使う場合
$ mycmd .                 # カレントディレクトリをデータディレクトリとして使う場合
$ mycmd                   # 省略した場合はカレントディレクトリをデータディレクトリとして使う

您可能会发现第一次使用很麻烦。我可以理解您为什么要省略它,因为您想将脚本所在的目录用作数据目录。我也“手工”做。我觉得你做的马虎也没关系。但是这篇文章是关于它是否是***实践,偷工减料也可以,但这不是一个好的实践。

第二种用法是将当前当前目录用作数据目录的一种方式。你可以改写./。这样的用法可以看作docker build .的一个例子。

第三种用法是 2 的简写,如果省略数据目录,则使用当前目录。这种用法可以在git 看到。例如git init将当前目录初始化为git目录。

此模式的另一个变体是将输出目录指定为选项。 tar 命令使用这样的模式,下面的命令在展开归档时移动到-C 选项指定的目录,然后执行展开过程。

$ tar xzf archive.tar.gz -C /tmp/

另一种可能的模式是在环境变量或配置文件中指定数据目录并移动到该目录。无论如何,重要的是“不要将shell脚本所在的目录用作数据目录,而是以另一种方式指定”。

移动以执行子命令或加载库时

例如,假设一个 shell 脚本没有在一个文件中完成并且具有以下结构。子命令myprog-sub 和common.sh 是从myprog 中读取的,带有相对路径。

/home/user/bin/myprog
├ libexec/
│   └ myprog-sub
└ lib/
    └ common.sh

现在,为了能够运行

user@hostname:~$ bin/myprog

一种可能的方法是将当前目录移动到/home/user/bin/myprog。然后您可以执行子命令并加载库,如下所示。

我的程序
cd "$(dirname "$0")"
libexec/myprog-sub
. lib/common.sh

没问题(此时),因为我们不共享脚本位置和数据目录。

此时没有问题,但是如果你尝试将文件输出到当前目录(相对于路径),就会出现问题。例如:

user@hostname:~$ bin/myprog -o ./datadir

在这种情况下,您应该期望输出为您的主目录下的/home/user/datadir。但是当前目录已移至/home/user/bin/myprog,因此不会发生这种情况。如果shell脚本没有引用路径,移动到脚本所在的位置也没问题,但如果没有,如果当前目录被移动了就会有问题。如果你尽力而为,即使移动当前目录也可以让它工作,但这并没有多大意义。

最后,我觉得这个方法可以根据条件做,但是是不好的方法,***不移动当前目录的情况下写成如下。

我的程序
script_dir="$(dirname "$0")"
"$script_dir/libexec/myprog-sub"
. "$script_dir/lib/common.sh"

虽然不推荐,但也可以重写环境变量PATH。我不推荐它的原因是因为我认为在这个级别更改全局环境变量PATH 是不合适的,并且我担心如果该目录中有额外的文件可能会导致意外行为。如果您有充分的理由,我认为您可以更改PATH。

我的程序
script_dir="$(dirname "$0")"
PATH="$script_dir/libexec:$script_dir/lib:$PATH"
myprog-sub
. common.sh
补充提防从符号链接执行

cd "$(dirname "$0")" 似乎可以从任何地方运行,但实际上,如果您通过符号链接启动它,它可能最终位于与您预期不同的目录中。例如:假设您有由git 管理的脚本和文件,并且您想从~/bin/myprog 运行它们。

/home/user/bin/myprog ・・・ /home/user/workspace/myprog/myprog へのシンボリックリンク

/home/user/workspace/myprog
├ myprog ・・・ 本体
└ README.md

如果此时环境变量PATH包含/home/user/bin/,你可能会认为myprog可以在任何地方运行。但是,在这种情况下,带有 shell 脚本的目录是 /home/user/bin/,这是带有符号链接的目录,而不是带有主体的目录。

如果您不在乎您的数据文件是否为/home/user/bin/ 或更少,那很好,但通常您需要/home/user/workspace/myprog。特别是在执行子命令和加载库时,这些文件应该在/home/user/workspace/myprog 下。

要在通过符号链接运行时找到正确的目录,您需要从符号链接中找到实际的正文,例如readlink -f。我不认为这是一个很难的故事,所以我不会在这里讨论如何写它。

可在任何地方运行的 shell 脚本的***实践

编写可以在任何地方执行的 shell 脚本的基础很简单。如下只需设计 CLI 接口,以便将读取和写入数据的目录作为参数传递等。是。要读取和写入的目录作为参数给出,因此无需将当前目录更改为 shell 脚本所在的位置。

$ myprog ./datadir

创建一个无法从任何地方执行的shell脚本的真正原因不是因为你没有移动到shell脚本所在的目录,因为你使用的是shell脚本所在的目录作为数据目录是。尝试编写 shell 脚本所在的位置的想法是一种反模式。在 shell 脚本的开头更改当前目录对于反模式来说是一个蹩脚的解决方法。您会发现将 shell 脚本位置与数据目录分开也适用于版本控制,例如 git。

最后

正如我在开头所写的,如果你想偷工减料,你可以使用“cutting workaround”。但这不是***实践。为什么你认为使用它可以,但你指出这不是***实践?因为它最终会结束。

即使你觉得这很奇怪,如果人们一直告诉你“***从一开始就去脚本位置”,是这样吗?我错了。如果不是文章一开始就说移动目录是一种反模式,错误的想法会传播开来,阻碍正确的理解,所以我是这样指出的。正确理解您或其他人在做什么是很重要的。

同样,***做法是不要将当前目录更改为 shell 脚本所在的位置。首先,“当前”目录的用法应该是指当前目录。仔细看看其他命令是如何设计的。将当前目录更改为 shell 脚本所在的位置是“经验法则”。

原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308633018.html