首页 > 系统相关 >shell 的初始化流程

shell 的初始化流程

时间:2022-11-13 20:45:22浏览次数:54  
标签:初始化 shell 读取 流程 bashrc login bash interactive

目录

shell 初始化

众所周知,shell 初始化是一坨巨大的不祥之物。但是如果不了解初始化的过程的话,可能会在编写各种 rc、crontab 时被折磨。所以分享让大家试吃一下。

基本概念

login shell

login shell 是个比较古老的概念,指由 logind 验证用户身份后,便提供一个 login shell 供用户工作。这个 shell 的特殊意义在于,它和用户的会话紧紧绑定在一起,在它开始运行前与它结束运行后都会往 /var/log/wtmp 写入用户的登录记录。除了它以外,所有的被用户手动运行的 shell 都被视作普通的应用程序。

因为大家现在都在 tty7 用各种基于 X 的登录管理器,它们验证用户身份后会提供一个桌面环境,所以 login shell 的概念没啥用了。但是它的一些历史遗留问题还是可能给大家带来困惑。

生成一个 login shell 有两种方法:

  1. 在 shell 后面加上 -l 参数,比如 bash -l

比如,这是一个 login shell:

03:18 Syameimaru-Aya ~
0 bash -l
03:18 Syameimaru-Aya ~
0 logout

而这不是一个 login shell:

03:18 Syameimaru-Aya ~
0 bash
03:18 Syameimaru-Aya ~
0 logout
bash: logout: not login shell: use `exit'
  1. 让 shell 的 argv[0]- 开头。

我们在通过 ssh 远程登录,或者从 ttyN 用 logind 登录时都可以获得 login shell。显然 logind 和 ssh 不应当对 shell 的参数做出假设(即不能假设自己即将运行的程序有一个 -l 参数)。所以他们用改 argv[0] 的方式来通知 shell。

sshd 是这么干的的 ssh/session.c

/*
 * If we have no command, execute the shell.  In this case, the shell
 * name to be passed in argv[0] is preceded by '-' to indicate that
 * this is a login shell.
 */

logind 也是这么干的(在 ttyN 里面试试这些东西):

Debian GNU/Linux bookworm/sid Syameimaru-Aya tty2
Syameimaru-Aya login: jyi
Password:
Linux Syameimaru-Aya 5.19.0-2-amd64 #1 SMP PREEMPT_DYNAMIC Debian 5.19.11-1 (2022-09-24) x86_64 GNU/Linux

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Sep 30 03:06:30 CST 2022 on tty2
03:34 Syameimaru-Aya ~/tmp
0 echo $0
-bash

interactive shell

区分 interactive 与 non-interactive 的意义在于,让 shell 在给人类使用时与执行脚本时表现出不同的行为。

要求标准输入和标准输出都指向终端(用 isatty 系统调用确定)。仅在 interactive shell 里面会打印提示符,同时启用行编辑和 job control 特性,对人类十分友好!

这也解释了为啥用 nc -l -p 2333 -e /bin/bash 搞的丐版远程登录非常难用,因为这不是 interactive shell,没有方便的编辑特性。也能解释为啥 echo echo hello | bash 不会输出提示符而是直接输出命令结果,因为这不是 interactive shell,不会输出提示符。

当然,也可以用 -i 选项暴力启动交互模式。

03:42 Syameimaru-Aya ~
0 echo echo hello | bash -i
    03:43 Syameimaru-Aya ~
    0 echo hello
    hello
    03:43 Syameimaru-Aya ~
    0 exit
03:43 Syameimaru-Aya ~
0

(为了分辨命令的输出,输出部分往右缩进了一些)。

此时 shell 会像正常一样输出提示符,读取输出并且执行。

不同的组合读取配置文件的区别

以 bash 为例:

login:首先是 /etc/profile,接着是 /etc/profile.d/*,最后是 ~/.bash_profile ~/.bash_login ~/.profile 三者按顺序检查,读取第一个可读的文件。(注意没有 ~/.bashrc)在 shell 退出时,还会读取 ~/.bash_logout
non-login:不会读取任何配置。
interactive:依次读取 /etc/bash.bashrc ~/.bashrc
non-interactive:不会读取任何配置。

一般情况下,shell 启动时读取的配置是上列之一,并且 login 优先于 interactive。比如,如果 shell 以 login + interactive 的方式启动,则会读取 /etc/profile/etc/profile.d/*~/.bash_profile~/.bash_login~/.profile,但是并不会考虑 /etc/bash.bashrc~/.bashrc,即使这是一个 interactive shell。

有个仅用于 bash 的例外是,当其以 non-login 且 non-interactive 的方式启动时,它会检查名为 BASH_ENV 的环境变量。如果变量值所表示的文件存在,则会读取该文件作为配置。

这套神秘机制造成的麻烦

~/.bashrc~/.bash_profile 之间的互动

  1. login shell 不会读取 ~/.bashrc,这使得 login shell 不能读取一些配置,很难用。为了解决这个问题,人们决定在 ~/.bash_profile 里引用 ~/.bashrc
  2. 一些人会在 ~/.bashrc 里对命令加入一些保护措施,比如 alias rm='rm -I --preseve-root',使得在同时删除三个以上文件时需要确认才能删除,另外,有些人可能会拿垃圾桶代替 rm
  3. 一些脚本会以 login 的方式执行(通常是运行得非常早的脚本,甚至不能从父进程里继承 PATH),以保证自己能读取 /etc/profile,得到正确的环境变量。

当这三点齐聚时,会发生什么呢?

  1. 安装软件包时,本来应该被彻底删除的临时文件被不明不白地扔进了垃圾箱里,占用不知道多少的空间。
  2. 即使用了 -y 参数来避免安装时的用户输出,仍然有可能因为 rm -I 等命令而需要等待输入。这对一些后台执行的脚本(比如定时自动更新)来说是非常坏的,因为很可能没有用户会来输入一个 y

为了解决这个问题,只好在 ~/.bashrc 前面加上这一句看起来很像魔法咒语的指令:

[[ $- == *i* ]] || return

……使得 bash 在读取 ~/.bashrc 当配置文件时,如果是非交互终端则立即停止读取。

crond 找不到命令,但是自己在终端里操作时又有

为了方便描述,把这个命令叫作 lolcat

  1. 有些人喜欢把 lolcat 放在 ~/.local/bin/
  2. 有些人写 crontab 时喜欢用 lolcat(?)
  3. 他在 ~/.bashrc 里面将 ~/.local/bin/ 加入到 PATH
  4. crond 运行 shell 时为 non-interactive + non-login 模式

会发生什么呢?

  1. 当在终端里试图运行 lolcat 时,因为现有的是 interactive + non-login 模式,所以读取了 ~/.bashrc,正确地设置了路径。
  2. 当在 crond 里运行 lolcat 时,因为是 non-interactive + non-login 模式,没有读取 ~/.bashrcPATH 里没有 ~/.local/bin,找不到 lolcat

所以在写 crontab 时,只好写 bash -lc lolcat

不仅仅是 shell 脚本,C 中的 system()、Python 的 os.system() 以及更多类似物都会遇到这个问题。在终端里直接执行时,会从 bash 中继承 PATH,从而表现出正确的行为。而如果在 crond 内执行,则会出现找不到命令的问题。

更多例子

暂时没遇到……

标签:初始化,shell,读取,流程,bashrc,login,bash,interactive
From: https://www.cnblogs.com/jyi2ya/p/16886877.html

相关文章

  • <四>构造函数初始化列表
    示例代码1点击查看代码classCDate{public:CDate(int_year,int_month,int_day){this->year=_year;this->month=_month;this->d......
  • Spring之容器的启动流程
    1.整体启动流程Spring的启动流程可以归纳为三个步骤:1、初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中2、将配置类的BeanDefinition注册到容器......
  • 装修工程 工作流程
    工作流程:1、通过业务集道获得客户资源2、由业务部/设计总监派发客户给设计师3、设计师获得客户信息肩需联系客户预约量房时间4、到现场进行量房并客户沟通获得客户需求......
  • CSP 202203-1 未初始化警告 C++
    1#include<iostream>2#include<vector>3intmain(){4intx{},y{};5std::cin>>x>>y;//读入第一行6std::vector<std::vector<int>>k......
  • 02-类与对象 进行试验--Java字段初始化的规律
    1.类的构造方法(1)“构造方法”,也称为“构造函数”,当创建一个对象时,它的构造方法会被自动调用。构造方法与类名相同,没有返回值。(2)如果类没有定义构造函数,Java编译器......
  • ffmpeg中声音解码的流程
    声音解码流程:audio初始化fifo初始化frame初始化init_resampler解码:如果有帧初始化转码空间做转码操作resampler放入fifofifo是否大于一帧数据......
  • 洛谷P5309 Ynoi 2011 初始化 题解
    题面。我也想过根号分治,但是题目刷得少,数组不敢开,所以还是看题解做的。这道题目要用到根号分治的思想,可以看看这道题目和我的题解。题目要求处理一个数组a,支持如下操作......
  • Python程序流程控制
    Python程序流程控制1.*程序流程概述在现实生活中,我们看到的流程是多种多样的,如汽车在道路上行驶,要顺序地沿道路前进,碰到交叉路口时,驾驶员就需要判断是转弯还是直行,在环......
  • 服务治理实施流程
    服务管理是整个服务治理体系的基础,它通过建立服务资产库,形成服务资源清单,确定管理对象。服务管理不但管理单个的服务,还管理服务关系;不但管理服务的静态信息,还管理服务的运态......
  • 静态初始化块
    构造方法用于对象的普通属性初始化。静态初始化块,用于类的初始化操作,初始化静态属性。在静态初始化块中不能直接访问非static成员。......