注:实验环境为 - CentOS 7 - Bash

Login & Non-Login

  • 经常看到一个概念 Login Shell 或者叫登录 Shell,但很难从名称理解它的含义,从网上也很少找到详细的解释,今天来整理一下 Login Shell 的概念以及如何区分 Login ShellNon-Login Shell

  • 先来看看平常我们使用 SSH 协议登录 Linux 服务器时的样子:

1
2
3
4
5
6
Connecting to 10.10.8.137:22...
Connection established.
To escape to local shell, press Ctrl+Alt+].

Last login: Wed Oct 18 21:55:30 2023 from 10.10.8.1
[root@localhost ~]#
  • 可以看到有一个 Last login 的时间和 IP
  • 这时,有两种命令可以断开 SSH 连接:
    • logout:在用户登录 Shell 会话时,用于安全地注销用户并终止该会话。
    • exit:退出当前 Shell 环境,并返回到上一级 Shell 或关闭终端会话。
1
2
3
4
5
6
7
8
[root@localhost ~]# logout
Connection closed.
Disconnected from remote host(CentOS 7) at 00:28:31.

[root@localhost ~]# exit
logout
Connection closed.
Disconnected from remote host(CentOS 7) at 00:28:49.
  • 可以看到两者的具体作用上有细微差别,比如在使用 bash 命令开启一个子 Shell 时,却不能使用 logout 退出,只能使用 exit
1
2
3
4
5
6
7
8
[root@localhost ~]# bash
[root@localhost ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 52446 52442 0 15:52 pts/1 00:00:00 -bash
root 52486 52446 0 15:52 pts/1 00:00:00 bash
root 52515 52486 0 15:52 pts/1 00:00:00 ps -f
[root@localhost ~]# logout
bash: logout: not login shell: use `exit'
  • 这是为什么?很简单,因为 bash 的子 Shell 是一个 Non-login,对于一个非登录 Shell 来说,这个 Shell 没有用户登录又何来用户登出呢。

Login Shell

  • sudo 命令中有个参数 -i,用于运行一个 Login Shell
1
2
3
4
5
[root@localhost ~]# sudo --help | grep 'login'
-i, --login run login shell as the target user; a command may also be specified

[root@localhost ~]# sudo -i echo $0
-bash
  • su 命令中也有类似的参数:
1
2
3
4
5
6
7
[root@localhost ~]# su --help | grep 'login'
-, -l, --login make the shell a login shell

[root@localhost ~]# su - centos7
Last login: Thu Oct 19 00:37:52 CST 2023 on pts/2
[centos7@localhost ~]$ echo $0
-bash

  • 关于 Login Shell 这部分定义,可以从 Bash 手册里看到:
1
2
A login shell is one whose first character of argument zero is a -, or one started with the --login option.
# 登录 Shell 是 &0 的第一个字符是 - 或以 ——login 选项开头的 Shell。
  • 可以看到 ---login 两个条件二者取其一。

Login Shell 条件一

  • 先看前面半句:first character of argument zero is a -,即一个 Shellargument zero 的第一个字符是个连字符 -。
  • 在这里 argument zero 其实指的是 $0

注:$0 是 Shell 的一个参数,这个参数保存的是 bash 脚本的名称,bash 初始化的时候会设置 $0 这个变量。

  • bash 中打印一下:
1
2
[root@localhost ~]# echo $0
-bash
  • 可以看到,打印出来脚本名称是 -bash,第一个字符是 -,说明这个是一个 Login Shell
  • 使用 ps 命令查看进程,也可以看到 bash 的进程名称为 -bash
1
2
3
4
[root@localhost ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 5586 5578 0 00:32 pts/2 00:00:00 -bash
root 5957 5586 0 00:43 pts/2 00:00:00 ps -f
  • 如果打印出来第一个字符不是 -,例如:
1
2
3
4
5
6
[root@localhost ~]# bash
[root@localhost ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 2133 2125 0 20:50 pts/0 00:00:00 -bash
root 2205 2133 0 20:52 pts/0 00:00:00 bash
root 2238 2205 0 20:52 pts/0 00:00:00 ps -f
  • 不过,这也不能说明这不是 Login Shell 还要看后半句。

Login Shell 条件二

  • Shell 启动的时候有 --login 参数,尝试一下:
1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# bash --login
[root@localhost ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 2133 2125 0 20:50 pts/0 00:00:00 -bash
root 2255 2133 0 20:53 pts/0 00:00:00 bash --login
root 2287 2255 0 20:53 pts/0 00:00:00 ps -f
[root@localhost ~]# logout
[root@localhost ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 2133 2125 0 20:50 pts/0 00:00:00 -bash
root 2288 2133 0 20:54 pts/0 00:00:00 ps -f
  • 可以使用 logout 命令,用 Docker 试一试:
1
2
3
4
5
6
7
[root@localhost ~]# docker run -it centos:7 /bin/bash --login
[root@3341ceb0da2a /]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 12:55 pts/0 00:00:00 /bin/bash --login
root 19 1 0 12:55 pts/0 00:00:00 ps -f
[root@3341ceb0da2a /]# logout
[root@localhost ~]#
  • 可以看到,如果带上 --login,那它就是一个 Login shell,可以使用 logout 退出 Shell

Login & Non-Login 区别

  • Login Shell(登录 Shell)和 Non-Login Shell(非登录 Shell)是两种不同类型的 Shell 环境,它们在启动时加载的配置文件以及行为方面存在一些区别。
  • Login Shell(登录 Shell):
    • 当用户登录到系统时,通常会启动一个登录 Shell
    • 登录 Shell 会加载一系列的配置文件,用于设置用户的环境(具体文件在第三章说明)。
  • Non-Login Shell(非登录 Shell):
    • 当用户在已登录的会话中打开新的终端窗口或启动一个新的 Shell 时,会使用非登录 Shell
    • 非登录 Shell 通常是为了执行用户的命令而启动的,不需要执行登录过程的设置。
    • 非登录 Shell 也会加载一系列的配置文件,用于设置用户的环境(具体文件在第三章说明)。

Interactive & Non-Interactive

  • 交互模式就是在终端上执行,Shell 等待你的输入,并且立即解释执行你提交的命令。
  • 这种模式被称作交互式,是因为 Shell 与用户进行交互。
  • 这种模式也是大多数用户非常熟悉的:登录、执行一些命令、退出,当你退出后,Shell 也终止了。

注:通常来说,Login Shell 都是 Interactive Shell。

  • 同理,Shell 也可以运行在另外一种模式:非交互式模式。
  • 非交互模式以 Shell Script(非交互)方式执行。
  • 在这种模式下,Shell 不与你进行交互,而是读取存放在文件中的命令,并且依此解释执行它们。
  • 当它读到文件的结尾 Shell 也就终止了。

Interactive Shell

  • 关于 Interactive Shell 这部分定义,可以从 Bash 手册里看到:
1
2
An  interactive  Shell  is  one started without non‐option arguments (unless -s is specified) and without the -c option,  whose  standard input  and  error  are both connected to terminals (as determined by isatty(3)), or one started with the -i option.  PS1 is  set  and  $- includes  i  if  bash  is  interactive, allowing a Shell script or a startup file to test this state.
# 一个交互式 Shell 是在没有非选项参数(除非指定了 -s 选项)和 -c 选项的情况下启动的 Shell,其标准输入和错误都连接到终端,或者使用 -i 选项启动的Shell。如果 bash 是交互式的,PS1 被设置并且 $- 包含 i,允许 Shell 脚本或启动文件测试此状态。
  • 上面那句话出现了一个单词:non‐option arguments(非选项参数),这是什么?

option & argument

  • Linux 中,命令行使用空格分割为一个个的 argument,例如:

img

  • 以上图为例,命令执行时,在 ls 脚本的内部,可以使用 $0 来获取到 ls 这个字符串。
  • 同理,$1 可以获取到 -l,使用 $2 来获取到 -a 选项等等。
  • arguments 就是 $0$1$2,以空格分割命令行后的每个部分。
  • -l-a 这种可以改变 ls 命令的行为的,叫做选项(即 option),它们一般会被写在命令使用文档中:

img

  • 那非选项参数是哪个?
    • 上述命令中参数 /path/to/folder 既不是 -l 选项的值,也不是 -a 选项的值,它不属于任何一个 option,所以它是一个 non-option arguments

Interactive Shell 条件一

  • 看下手册里面的几个单词:PS1 is set,这个 PS1 是啥?
    • $PS1 用于自定义命令提示符的外观和内容,以便增强 Shell 的可视化和交互性。
  • 说明是个 Interactive Shell 的话,变量 $PS1 应该是有输出值的:
1
2
[root@localhost ~]# echo $PS1
[\u@\h \W]\$
  • 使用 bash 命令的 -c 选项来执行上述命令:

注:bash -c 是在当前 Shell 进程中启动一个新的 Bash 子进程,并在该子进程中执行指定的命令或脚本。

1
2
[root@localhost ~]# bash -c "echo \$PS1"

  • 可以理解为,执行 bash -c "echo \$PS1" 命令的时候,实际上由当前 Shell 启动了一个非交互式 Shell,然后让这个非交互式去执行 echo $PS1 命令。

Interactive Shell 条件二

  • 再看下手册里面的几个单词:$- includes i,这个 $- 又是个啥?
    • $- 是一个特殊变量,用于表示当前 Shell 的选项和状态。
    • Bash Shell 是交互式的时候,$- 会包含字母 i,表示 Shell 是交互式的。
  • 说明是个 Interactive Shell 的话,变量 $- 的输出值应该包含 i
1
2
[root@localhost ~]# echo $-
himBH
  • 使用 bash 命令的 -c 选项来执行上述命令:
1
2
[root@localhost ~]# bash -c 'echo $-'
hBc

Interactive Shell 条件三

  • 前面已知了两个条件,还有一个容易被忽略的非选项参数。
  • 既然已经知道了 non-option arguments 的含义,我们来简单验证一下:
1
2
3
4
5
6
7
8
[root@localhost ~]# cat demo
#!/bin/bash
echo $-
[root@localhost ~]# bash demo
hB
[root@localhost ~]# bash
[root@localhost ~]# echo $-
himBH
  • 还可以使用 bash -i 进行交互式 Shell 创建:
1
2
[root@localhost ~]#  bash -i demo
himB

注:bash -i 是在当前 Shell 进程中启动一个新的 Bash 子进程,并使该子进程成为一个交互式 Shell。

总结

\ Interactive Shell Non-Interactive Shell
$PS1 $PS1 值为空只表示没有设置命令提示符,但并不足以确定 Shell 的交互性。
$PS1 存在一定可认为当前 Shell 是一个交互式 Shell
当前 Shell 是非交互式的,$PS1 的值一定为空。
$- 如果 $- 的值包含字母 i,那么当前 Shell 是一个交互式 Shell 如果 $- 的值不包含字母 i,那么当前 Shell 是一个非交互式 Shell
Non-argument(排除 -i 参数) 没有非选项参数 有非选项参数
-i-c -i:开启一个交互式的子 Shell 去执行命令 -c:开启一个非交互式的子 Shell 去执行命令

Linux Bash Env-Conf

这里我们可以想一个问题,以前我想的少,现在想得多了:

  • Linux 的环境配置文件包括 /etc/profile~/bash_profile~/.bashrc 等,它们通常会在用户登录时被执行,这些配置文件是每次登录都会调用吗?

  • 为什么有时候 /etc/profile 没有生效?这些配置文件的执行顺序是怎么样的?

注:这里主要基于 Bash,其他 Shell 可能不适用。

Bash 配置文件调用逻辑

  • 可以参考大佬的一张图:
img
  • 总结一下就是:
    • 如果一个 ShellLogin Shell 时,/etc/profile 会被调用,随后按顺序查找 ~/.bash_profile~/.bash_login~/.profile,并执行最先找到的一个。
    • 如果是交互式的 Non-Login Shell,则会调用 ~/.bashrc
    • 如果是非交互式的 Non-Login Shell,则会执行环境变量 $BASH_ENV 中的脚本(如果存在,我也没有实践过)。

特殊情况

  • 当然,凡是无绝对,上面这些被调用的脚本可能去调用其它脚本。
  • 例如:/etc/profile 可能去调用 /etc/profile.d/ 目录中的脚本。
  • CentOS~/.bash_profile 会调用 ~/.bashrc,查看下代码:
1
2
3
4
5
6
7
[root@localhost ~]# head -n 6 ~/.bash_profile 
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
  • 所以在 CentOS 中,Login Shell 的配置文件加载是这样的:

    • /etc/profile
    • ~/.bash_profile
    • ~/.bashrc
  • 启动 Shell 时可以使用 --noprofile--norc 等选项使 Shell 不去调用对应的配置文件或者指定调用的配置文件,很少用到不展开讨论。

  • 如果启动 Shell 时使用 sh 命令(sh/bin/bash 的一个软链接)情况又不同,如下:

img

  • 使用 . 或者 source 去执行一个脚本,是在当前 Shell 中执行脚本,不会启动新的 Shell,所以不会去加载任何配置文件,没有上面这些流程。

注:

  • . 和 source 命令都用于在当前 Shell 环境中执行脚本,使脚本中的变量、函数、别名等定义在当前 Shell 中生效。
  • source 命令是 . 命令的一个 Bash 内建命令别名。