AJJ*_*AJJ 8 command-line bash environment-variables ps1
据我所知printenv
显示环境变量,但为什么我看不到其他变量,例如PS1
自定义 shell 提示?
究竟是什么printenv
输出,为什么不回升PS1
?是否有更全面的输出命令比printenv
?
那是因为PS1
通常不导出。
环境变量用于设置子进程的执行环境;因为PS1
只有在交互式 shell 中才真正有意义,所以通常没有任何意义导出它 - 它只是一个普通的shell 变量。
如果您启动一个交互式子shell,那么它将PS1
从 shell 的资源文件中读取并设置它,例如~/.bashrc
如果你export PS1
那么你会在printenv
输出中看到它。或者,您可以使用 bash 内置命令查看普通 shell 变量,set
如此处所述如何列出所有变量名称及其当前值?
是否有更全面的输出命令比
printenv
?
printenv
只打印环境变量,这可能被认为是一个优势。但是,如果你想打印shell变量为好,使用echo "$x"
(或者printf '%s\n' "$x"
,这是更强大的)代替printenv x
。
steeldriver对这些问题的解释是有用且正确的,但我在这里以另一种方式呈现该主题。
printenv
是一个外部命令——不是内置在你的 shell 中,而是一个独立于你的 shell 的程序。它显示了自己的环境变量,这些变量是从您用来运行它的 shell 继承的。但是,shell 不会将所有变量传递到其子进程的环境中。相反,它们区分哪些变量是环境变量,哪些不是。(那些不是通常称为shell variables。)
要了解这是如何工作的,请尝试这些命令,它们被包含在内,(
)
因此它们彼此独立1。单独地,当您在没有 的情况下运行这些命令时,这些命令中的每一个的工作方式都是相同的(
)
,但是您在之前的命令中创建的变量将仍然存在于以后的命令中。在子shell中运行命令可以防止这种情况。
创建新变量,然后运行外部命令,不会将变量传递到命令的环境中。除非在您已经拥有环境变量的异常情况下x
,此命令不会产生任何输出:
(x=foo; printenv x)
Run Code Online (Sandbox Code Playgroud)
但是,该变量是在 shell 中分配的。此命令输出foo
:
(x=foo; echo "$x")
Run Code Online (Sandbox Code Playgroud)
shell 支持将变量传递到命令环境中而不影响当前 shell 环境的语法。这输出foo
:
x=foo printenv x
Run Code Online (Sandbox Code Playgroud)
(当然,这也适用于子shell——(x=foo printenv x)
但我没有展示它,(
)
因为当您使用该语法时,没有为当前shell设置任何内容,因此无需使用子shell来防止后续命令被做作的。)
这打印foo
,然后打印bar
:
(x=bar; x=foo printenv x; echo "$x")
Run Code Online (Sandbox Code Playgroud)
导出变量时,它会自动传递到从同一 shell 运行的所有后续外部命令的环境中。该export
命令执行此操作。你可以使用它,你定义变量之前,您需要定义后,或者你甚至可以定义变量中的export
命令本身。所有这些打印foo
:
(x=foo; export x; printenv x)
Run Code Online (Sandbox Code Playgroud)
(export x; x=foo; printenv x)
Run Code Online (Sandbox Code Playgroud)
(export x=foo; printenv x)
Run Code Online (Sandbox Code Playgroud)
没有unexport
命令。即使您可以在设置之前导出变量,取消设置变量也会取消导出它,也就是说这不会打印任何内容,而不是打印bar
:
(x=foo; export x; unset x; x=bar; printenv x)
Run Code Online (Sandbox Code Playgroud)
但是在导出变量后更改变量的值确实会影响导出的值。这打印foo
,然后bar
:
(export x=foo; printenv x; x=bar; printenv x)
Run Code Online (Sandbox Code Playgroud)
与其他进程一样,您的 shell 本身从其父进程继承环境变量。此类变量最初存在于您的 shell 环境中,并且它们会自动导出——或者保持导出状态,如果您选择这样考虑的话。这会打印foo
(请记住,在其环境中使用set toVAR=val cmd
运行):cmd
VAR
val
x=foo bash -c 'printenv x'
Run Code Online (Sandbox Code Playgroud)
子进程中设置的变量不会影响父进程,即使它们被导出。这打印foo
(不是bar
):
(x=foo; bash -c 'export x=bar'; echo "$x")
Run Code Online (Sandbox Code Playgroud)
一个子shell也是一个子进程2;这也打印foo
:
(x=foo; (export x=bar); echo "$x")
Run Code Online (Sandbox Code Playgroud)
这应该更清楚为什么我将这些命令中的大部分包含(
)
在子shell中运行它们。
但是,子shell 是特殊的。与其他子进程不同,例如在运行外部命令时创建的子进程,例如printenv
或bash
,子shell 继承了其父shell 的大部分状态。特别是,子shell 甚至会继承未导出的变量。就像(x=foo; echo "$x")
印刷品一样foo
,也是如此(x=foo; (echo "$x"))
。
未导出的变量仍然不会在子shell 中导出——除非你导出它——所以,就像(x=foo; printenv x)
什么都不打印一样,(x=foo; (printenv x))
.
子外壳是一种特殊的子进程,它是外壳。并非所有作为壳的子进程都是子壳。通过运行创建的外壳bash
是不是一个子shell,它不继承未导出的变量。所以这个命令打印一个空行(因为echo
即使在使用空参数调用时也会打印一个换行符):
(x=foo; bash -c 'echo "$x"')
Run Code Online (Sandbox Code Playgroud)
PS1
不是环境变量(通常不应该是一个)最后,至于为什么像PS1
shell这样的提示变量是shell变量而不是环境变量,原因如下:
PS1
到新的外壳通常会失败,因为外壳通常会重置PS1
.第 3 点值得更多解释,但如果您从不尝试创建PS1
环境变量,那么您可能真的不需要知道细节。
PS1
。当非交互式 Bash shell 启动时,它总是3 unsets PS1
。这将打印一个空行(不是foo
):
PS1=foo bash -c 'echo "$PS1"'
Run Code Online (Sandbox Code Playgroud)
要验证它实际上是未设置的,而不仅仅是设置而是空的,您可以运行它,它会打印unset
:
PS1=foo bash -c 'if [[ -v PS1 ]]; then echo set; else echo unset; fi'
Run Code Online (Sandbox Code Playgroud)
为了验证这是否是独立于其他启动特性,你可以尝试的任意组合传递--login
,--norc
或--posix
之前-c
,或设置BASH_ENV
一些脚本的路径(例如,),或者如果你通过。在任何情况下,非交互式 Bash shell 都不会无法取消设置。BASH_ENV=~/.bashrc PS1=foo bash ...
ENV
--posix
PS1
这意味着如果您导出PS1
并运行一个本身运行交互式 shell的非交互式 shell,它不会设置为PS1
您最初设置的值。出于这个原因——也因为除了 Bash(如 Ksh)之外的其他 shell 的行为方式并不完全相同,而且你PS1
为 Bash编写的方式并不总是适用于这些 shell——我建议不要尝试创建PS1
环境变量. 只需编辑~/.bashrc
以设置您想要的任何提示。
PS1
。相反,如果您取消设置 PS1
并运行交互式 Bash shell,即使您通过传递 阻止它从启动脚本运行命令--norc
,它仍会自动设置 PS1
为默认值。运行env -u PS1 bash --norc
为您提供了一个交互式 Bash shell,PS1
设置为\s-\v\$
. 由于 Bash 扩展\s
为 shell 的名称和\v
版本号,这bash-4.3$
在 Ubuntu 16.04 LTS 上显示为提示。请注意,将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 是否是交互式的常用方法。这就是为什么它是对非交互式击炮弹如此重要的4至取消设置 PS1
自动。如第6.3.2节,这是 Shell Interactive 吗?的Bash 参考手册说:
[S]启动脚本可能会检查变量
PS1
;它在非交互式 shell 中未设置,并在交互式 shell 中设置。
要了解这是如何工作的,请参阅那里的示例。或者查看 Ubuntu 中的实际用途。默认情况下,/etc/profile
在 Ubuntu 中包括:
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
Run Code Online (Sandbox Code Playgroud)
/etc/bash.bashrc
,当 shell 是非交互式的时,它什么都不做,它有:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
Run Code Online (Sandbox Code Playgroud)
为了实现相同的目标,/etc/skel/.bashrc
在创建用户帐户时将其复制到用户的主目录中(因此您的帐户~/.bashrc
可能类似),具有:
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
Run Code Online (Sandbox Code Playgroud)
这是另一个常见的方法来检查,如果shell是交互:查看是否通过获得的文本扩展的特殊参数 -
(通过写$-
)包含字母i
。通常这具有完全相同的效果。但是,假设您没有修改上面显示的默认出现在 Ubuntu 中 Bash 启动脚本中的代码,并且:
PS1
为环境变量,并且然后/etc/profile
(如果它是登录 shell)或/etc/bash.bashrc
不会运行他们通常为交互式 shell 运行的命令。~/.bashrc
还是会。
如果您想通过使用来检查 shell 是否是交互式的,PS1
并且即使在PS1
设置但为空时也能得到正确的答案,您可以使用[[ -v PS1 ]]
或[ -v PS1 ]
/test -v PS1
代替。但是请注意,[[
关键字以及-v
对[
和test
shell 内置函数的测试是 Bash 特有的。并非所有其他 Bourne 风格的 shell 都接受它们。所以,你应该不会像脚本中使用它们~/.profile
,并/etc/profile
可能在其他shell执行(或者通过显示管理器,当你登录图形),除非你有别的东西在检查什么shell运行脚本,只执行的Bash特有的命令当该 shell 是 Bash 时(例如,通过检查$BASH_VERSION
)。
1 本文详细解释了子shell。Bash 参考手册的3.2.4.3 Grouping Commands解释了(
)
语法。
2请注意,在某些情况下,即使(
)
不使用语法,命令也会在子 shell 中运行。例如,当您在管道中使用分隔命令|
时,Bash 在子shell 中运行每个命令(除非设置了lastpipe
shell 选项)。
3子壳除外。可以说这甚至不是一个例外,因为子shell不会按照我们谈论它时的通常意义上的意思“启动”。(它们实际上并没有显着的初始化行为。)请注意,当您bash
在 Bash shell 中运行-- 带或不带参数时,会创建一个是 shell 的子进程,但它不是子 shell。
4请注意,并非所有shell——甚至不是所有Bourne 风格的shell——都具有这种行为。但是 Bash 确实如此,而且 Bash 代码(包括启动脚本中的代码)依赖它是很常见的。