是否有任何理由避免获取 shell 的配置文件?

是否有任何理由避免获取 shell 的配置文件?

我最近在这里读到了一个答案,其中包括以下建议:

然而,一般来说,⚠️你不应该source ~/.zshrc根据点文件中的内容,这可能会导致各种问题。

我向OP询问了为什么这可能是一个问题的例子,并被告知:

因为当你的 shell 启动时它就已经被获取了,并且命令的运行顺序可能很重要。作为一个简单的示例,放入文件ls() { command ls -x "$@" }; alias ls='ls -AF'.zshrc,重新启动 shell,然后source ~/.zshrc.现在你得到一个错误zsh: defining function based on alias ls'。这只是一个相当无害的例子。事情可能会变得更加棘手

这听起来确实合理,尽管我无法用 重现该错误zsh。尽管它似乎有道理,但我很难想象这可能会导致任何严重问题的情况当打开一个干净的新会话时,这些问题也不会发生。我能想到的最糟糕的情况是收到错误消息,而且我什至不知道如何实现这种情况。

那么,是否有充分的理由避免在更改 shell 文件后手动获取 shellrc文件,以便将更改导入到当前 shell 会话中?

我知道我已经这样做bash很多年了:我会定期添加一个功能,或者对我的功能进行更改~/.bashrc,然后. ~/.bashrc获取它。真的有可能导致严重问题的情况吗?我想我们可以找到一些边缘情况,但是这些足以证明这种同情性警告是合理的吗?也许有一些zsh我作为用户没有遇到过的特定情况bash

我对涵盖任何 bourne-family shell(bash、sh、zsh、ksh 等)的答案感兴趣。

答案1

关于定义函数和同名别名的 init 文件的示例,假设init.sh使用这些函数和别名定义(加上调用 alias+function 进行演示):

foo() { echo "foo: $1"; }
alias foo='foo bar'
foo

bash 4.4 和 5.0:

第一次加载定义工作正常。如果是这样.bashrc,那么 shell 启动时就会发生这种情况。

$ . init.sh
foo: bar

再次尝试失败:

$ . init.sh
bash: init.sh: line 1: syntax error near unexpected token `('
bash: init.sh: line 1: `foo() { echo "foo: $1"; }'

引用或转义函数名称也不起作用:

$ \foo() { echo "foo: $1"; }
bash: `\foo': not a valid identifier

你需要unalias foo先进入init.sh.或者使用function foo { ... },它会忽略别名,因为foo它不是命令行上的第一个单词。

Bash 实际上在那里扩展了别名,因此如果别名只是将一个单词更改为另一个单词,它将更改定义的函数的名称:

$ alias foo=foobar; unset -f foo foobar
$ foo() { echo hi; }
$ typeset -p -f foo
bash: typeset: foo: not found
$ typeset -p -f foobar
foobar () 
{ 
    echo hi
}

无论别名的内容如何,​​当前版本的 Zsh 都会给出错误:

% . ./init.sh
foo: bar
% . ./init.sh
./init.sh:1: defining function based on alias `foo'
./init.sh:1: parse error near `()'

不过,在 zsh 中引用或转义函数名称是可行的。

并不总是这样,例如,在 zsh 5.3.1 中,当(重新)定义函数时,别名将被扩展,如果别名扩展为多个单词,您将默默地获得该函数的额外副本。 (如果它扩展为仅一个单词,这就是函数获得的名称。)例如,对于上面的第一个示例别名, 和 都foobar被定义:

% alias foo='foo bar'
% foo() { echo hi; }
% typeset -p -f foo
foo () {
        echo hi
}
% typeset -p -f bar
bar () {
        echo hi
}

当然,如果没有同名的函数和别名,这一切都不会发生。相反,我们可以在函数中包含所有所需的功能(特别是因为函数在某些方面都比别名更好)。


我无法想象这可能会导致任何严重问题的情况

我也是。

如果 init 脚本的早期部分使用命令 A 执行了一些可能危险的操作(该命令本身不一定是危险的,它可以在某种条件下使用),然后同一脚本的后续部分重新定义了 A,那么您可能会遇到问题作为函数或别名,破坏了第一次使用。

然而,这在其他方面也很脆弱。例如,重新组织文件以将函数定义移动到顶部也会破坏它,从而产生相同的危险行为。最好不要让脚本依赖于函数/别名定义的顺序,尤其是。运行有潜在危险的命令时。

答案2

假设你的 中有这样的东西.bashrc

foo=$(grep -m1 A /etc/os-release)
PS1="$foo %u $"
grep () { command grep -H "$@"; }

这是一种在提示符中显示发行版名称并grep始终打印文件名的快速方法,仅此而已。如果我重新获取该文件的源,我还会在提示符中获取该文件的名称。有人可能会说,这并不意外。现在想象一下,这些行之间有几十个其他的 shell 配置,甚至可能位于不同的文件中,这些文件又由主 rc 文件来源。 (最近有一个例子,某些软件为命令创建了别名env,这可能会破坏任意数量的使用。)然后就很难预测可能会出现什么问题。

我想说,即使是自己编写的,重新获取 bashrc 也是很棘手的。足够长的 bashrc 可能会与自身产生棘手的交互。

答案3

如果您不熟悉任何 shell 的初始化对您的系统的作用,请不要使用它!真的不。不要进行 UTBLT! (在学习工具之前使用工具)(在学习东西之前使用东西)。

大多数 shell 都提供一个-x选项来显示每个命令的执行情况。阅读man您的页面$SHELL

“写得好”,恕我直言,shell 初始化脚本检查环境变量、标志文件等以确定它们是否已经运行,并“做正确的事情”。

相关内容