为什么在printenv中没有像PS1这样的变量?

从我可以告诉printenv显示环境变量,但为什么我没有看到其他变量如PS1自定义shell提示符?

究竟什么是printenv输出,为什么它不接收PS1 ? 有没有比printenv更全面的输出命令?

那是因为PS1通常不会导出。

环境变量用于设置子进程的执行环境; 因为PS1在交互式shell中才真正具有重要性,所以通常没有任何点导出它 – 它只是一个普通的shell变量

如果你启动一个交互式子shell ,那么它将从shell的资源文件中读取并设置它的PS1 ,例如~/.bashrc

如果export PS1 ,则会在printenv输出中看到它。 或者,您可以使用bash内置set来查看纯shell变量,如此处所述如何列出所有变量名称及其当前值?

有没有比printenv更全面的输出命令?

printenv只打印环境变量 ,这可能被认为是一个优势。 但是如果你想打印shell变量,也可以使用echo "$x" (或printf '%s\n' "$x" ,这是更强大的 )而不是printenv x

steeldriver对这些问题的解释是有用和正确的,但我在这里以另一种方式提出这个主题。

printenv是一个外部命令 – 没有内置到你的shell中,而是一个独立于shell的程序。 它显示了自己的环境变量,它是从用于运行它的shellinheritance环境变量。 但是, shell不会将所有变量传递到其子进程的环境中 。 相反,他们保持区分哪些变量是环境变量而哪些变量不是。 (那些不经常调用shell变量 。)


壳变量

要查看其工作原理,请尝试这些命令,这些命令包含在( )因此它们彼此独立地运行。 单独地,当您在没有 ( ) 情况下运行它们时,这些命令中的每一个都是相同的,但是您在先前命令中创建的变量仍将存在于以后的命令中。 在子shell中运行命令可以防止这种情况。

创建新变量,然后运行外部命令,不会将变量传递到命令的环境中。 除非您已经拥有环境变量x的特殊情况,否则此命令不会产生任何输出:

 (x=foo; printenv x) 

但是,变量在shell中分配的。 这个命令输出foo

 (x=foo; echo "$x") 

shell支持语法将变量传递到命令的环境中, 而不会影响当前shell的环境。 这输出foo

 x=foo printenv x 

(当然,这也适用于子shell– (x=foo printenv x)但是我没有使用( )显示它,因为当你使用该语法时,没有为你当前的shell设置任何内容,所以使用子shell是不必要的,以防止后续命令受到影响。)

这打印foo ,然后打印bar

 (x=bar; x=foo printenv x; echo "$x") 

出口

导出变量时,它会自动传递到从同一shell运行的所有后续外部命令的环境中。 export命令执行此操作。 在定义变量之后,可以在定义变量之前使用它,或者甚至可以 export命令本身中定义变量。 所有这些打印foo

 (x=foo; export x; printenv x) 
 (export x; x=foo; printenv x) 
 (export x=foo; printenv x) 

没有未unexport命令。 即使您可以在设置变量之前导出变量,取消设置变量也会取消导出它,也就是说这不打印任何内容,而不是打印bar

 (x=foo; export x; unset x; x=bar; printenv x) 

但是在导出变量后更改变量值影响导出值。 打印foo ,然后bar

 (export x=foo; printenv x; x=bar; printenv x) 

与其他进程一样,shell本身从其父进程inheritance环境变量。 这些变量最初存在于shell的环境中,如果您选择以这种方式考虑,它们会自动导出 – 或保持导出状态。 这打印foo (记住, VAR=val cmd运行cmd ,其环境中VAR设置为val ):

 x=foo bash -c 'printenv x' 

子进程中设置的变量不会影响父进程,即使它们已导出。 这打印foo (不是bar ):

 (x=foo; bash -c 'export x=bar'; echo "$x") 

子shell

子shell也是子进程2 ; 这也打印foo

 (x=foo; (export x=bar); echo "$x") 

这应该更清楚为什么我在( )包含大部分这些命令以在子shell中运行它们。

但是,子壳是特殊的。 与其他子进程(如运行printenvbash等外部命令时创建的子进程)不同, 子shellinheritance其大部分父shell的状态 。 特别是, 子壳inheritance甚至未导出的变量 。 就像(x=foo; echo "$x")打印foo(x=foo; (echo "$x"))

未导出的变量仍未在子shell中导出 – 除非您将其导出 – 因此,正如(x=foo; printenv x)打印任何内容一样, (x=foo; (printenv x))

子shell是一种特殊的子进程,它是一个shell。 并非所有shell的子进程都是子shell。 通过运行bash创建的shell不是子shell ,也不会inheritance未导出的变量。 因此,此命令打印一个空行(因为即使用空参数调用, echo也会打印一个换行符):

 (x=foo; bash -c 'echo "$x"') 

为什么PS1不是环境变量(通常不应该是一个)

最后,至于为什么像PS1这样的提示变量是shell变量而不是环境变量,原因是:

  1. 它们只需要在shell中,而不是其他程序。
  2. 它们是为每个交互式shell设置的,非交互式shell根本不需要它们。 也就是说,它们不需要inheritance。
  3. 尝试将PS1传递给新shell通常会失败,因为shell通常会重置PS1

第3点值得更多解释,但如果你从未尝试将PS1作为环境变量,那么你可能并不需要知道细节。

当Bash以非交互方式开始时,它会取消PS1

当一个非交互式Bash shell启动时,它总是3个 unset PS1 。 这会打印一个空白行(不是foo ):

 PS1=foo bash -c 'echo "$PS1"' 

要validation它是否实际未设置,而不是仅设置为空,您可以运行此命令,打印未unset

 PS1=foo bash -c 'if [[ -v PS1 ]]; then echo set; else echo unset; fi' 

要validation这是否与其他启动行为无关,您可以尝试在-c之前传递--login , – --norc--posix任意组合,或者将BASH_ENV设置为某个脚本的路径(例如, BASH_ENV=~/.bashrc PS1=foo bash ... ),如果你通过了--posix ENV 。 在任何情况下,非交互式Bash shell都无法取消设置PS1

这意味着如果您导出PS1并运行一个本身运行交互式shell的非交互式shell,它将不会设置您最初设置的PS1值。 出于这个原因 – 还因为除了Bash之外的其他shell(如Ksh)并不都表现出相同的方式,并且为Bash编写PS1的方式并不总是适用于那些shell – 我建议不要尝试使PS1成为环境变量。 只需编辑~/.bashrc即可设置所需的提示。

当Bash以交互方式启动时,它通常会设置或重置PS1

相反,如果您取消设置 PS1并运行交互式Bash shell,即使您通过传递--norc阻止它从启动脚本运行命令,它仍会自动 PS1设置为默认值。 运行env -u PS1 bash --norc为您提供了一个交互式Bash shell, PS1设置为\s-\v\$ 。 由于Bash将\s扩展为shell的名称,将\v扩展为版本号,因此在Ubuntu 16.04 LTS上显示bash-4.3$作为提示。 请注意,将PS1的值设置为字符串与取消设置它不同。 如下所述,运行PS1= bash会为您提供一个具有奇怪启动行为的交互式shell。 在实际使用中,除非您理解并想要该行为,否则应该避免在将PS1设置为空字符串时将其导出。

但是,如果您设置PS1并运行交互式Bash shell – 并且它不会被中间非交互式shell取消 – 它将保留该值…直到像global /etc/profile这样的启动脚本(用于登录) shell)或/etc/bash.bashrc ,或你的每用户~/.profile~/.bash_login ,或~/.bash_profile (全部用于登录shell)或~/.bashrc重置它。

即使您编辑这些文件以防止它们设置PS1/etc/profile/etc/bash.bashrc的情况下,我建议不要做,因为它们影响所有用户 – 你不能真的靠这个。 如上所述,从非交互式shell开始的交互式shell将不具有PS1 ,除非您在非交互式shell中重置并重新导出它。 此外,在执行此操作之前,您应该三思而后行,因为shell代码(包括您可能已定义的shell函数)通常会检查PS1以确定它运行的shell是交互式还是非交互式。

检查PS1是确定当前shell是否是交互式的常用方法。

这就是为什么非交互式Bash shell 4自动取消设置 PS1非常重要的原因。 如第6.3.2节, 这壳牌是否互动? Bash参考手册中说:

[S] tartup脚本可以检查变量PS1 ; 它在非交互式shell中未设置,并在交互式shell中设置。

要了解其工作原理,请参阅其中的示例。 或者查看Ubuntu中的实际用途。 默认情况下,Ubuntu中的/etc/profile包括:

 if [ "$PS1" ]; then if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then # The file bash.bashrc already sets the default PS1. # PS1='\h:\w\$ ' if [ -f /etc/bash.bashrc ]; then . /etc/bash.bashrc fi else if [ "`id -u`" -eq 0 ]; then PS1='# ' else PS1='$ ' fi fi fi 

/etc/bash.bashrc ,当shell是非交互式时,它应该什么都不做,具有:

 # If not running interactively, don't do anything [ -z "$PS1" ] && return 

检查交互性的不同方法的细微之处:

为了实现相同的目标, ~/.bashrc在创建帐户时复制到用户的主目录中(因此~/.bashrc可能类似),具有:

 # If not running interactively, don't do anything case $- in *i*) ;; *) return;; esac 

这是检查shell是否是交互式的另一种常见方法:查看通过扩展 特殊参数获得的文本(通过写$- )是否包含字母i 。 通常这具有完全相同的效果。 但是,假设您没有修改上面显示的代码,默认情况下出现在Ubuntu中的Bash启动脚本中,并且:

  1. 您导出PS1作为环境变量,
  2. 它已设置,但是为值,
  3. 你启动一个交互式Bash shell …

然后/etc/profile (如果它是登录shell)或/etc/bash.bashrc将不会运行它们通常为交互式shell运行的命令。 ~/.bashrc仍然会。

如果你想通过PS1检查shell是否是交互式的,即使PS1设置为空也能得到正确的答案,你可以使用[[ -v PS1 ]][ -v PS1 ] / test -v PS1代替。 但请注意, [[关键字和[test shell内置函数]的-v测试特别适用于Bash。 并非所有其他Bourne风格的shell都接受它们。 所以你不应该在~/.profile/etc/profile这样的脚本中使用它们,这些脚本可能在其他shell中运行(或者以图形方式登录时由显示管理器运行),除非你在脚本中有其他东西来检查shell是什么运行并且只在该shell为Bash时执行特定于Bash的命令(例如,通过检查$BASH_VERSION )。


笔记

1 本文详细介绍了子壳。 3.2.4.3分组 Bash参考手册的命令解释了( )语法。

2请注意,在某些情况下,即使使用( )语法,命令也会在子shell中运行。 例如,当您将命令分隔为| 在一个管道中 ,Bash在子shell中运行它们中的每一个(除非设置了lastpipe shell选项 )。

3除了子壳 。 可以说这甚至不是一个例外,因为在我们讨论这个问题时,子壳不会按照通常的意义“启动”。 (它们实际上没有重要的初始化行为。)请注意,当您在Bash shell中运行bash或不带参数)时,会创建一个shell的子进程,但它不是子shell。

4请注意,并非所有炮弹 – 甚至不是所有Bourne风格的炮弹 -都是这样的。 但Bash确实如此,Bash代码(包括启动脚本中的代码)依赖它是很常见的。